Subject: Built motion from commit 4253e76.|0.0.66
From: Daniele Cinti <[email-masked]>
Date: Tue, 15 Mar 2016 17:56:28 +0000 (+0100)

Built motion from commit 4253e76.|0.0.66
---

diff --git a/public/assets/plugins/jscripty/js/Actions.js b/public/assets/plugins/jscripty/js/Actions.js
index dbf8254..aeaa420 100644
--- a/public/assets/plugins/jscripty/js/Actions.js
+++ b/public/assets/plugins/jscripty/js/Actions.js
@@ -1 +1,634 @@
\ No newline at end of file
+'use strict';
+/**
+ * $Id: Actions.js,v 1.7 2013-02-14 07:48:01 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs the actions object for the given UI.
+ */
+function Actions(editorUi) {
+	this.editorUi = editorUi;
+	this.actions = new Object();
+	this.init();
+};
+
+/**
+ * Adds the default actions.
+ */
+Actions.prototype.init = function() {
+	var ui = this.editorUi;
+	var editor = ui.editor;
+	var graph = editor.graph;
+
+	// File actions
+	this.addAction('new', function() {
+		ui.showDialog(new NewDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('open', function() {
+		ui.showDialog(new OpenDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('save', function() {
+		ui.saveFile(false);
+	}, null, null, 'Ctrl+S');
+	this.addAction('saveAs', function() {
+		ui.saveFile(true);
+	}, null, null, 'Ctrl+Shift-S');
+	this.addAction('publish', function() {
+		ui.publishFile(false);
+	}, null, null, 'Ctrl+Shift+P');
+	this.addAction('variable', function() {
+		ui.showDialog(new VariableDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('import', function() {
+		ui.showDialog(new ImportDialog(ui).container, 300, 200, true, true);
+	});
+	this.addAction('export', function() {
+		ui.showDialog(new ExportDialog(ui).container, 300, 200, true, true);
+	}, null, null, 'Ctrl+E');
+	this.put('editFile', new Action(mxResources.get('edit'), mxUtils.bind(this,
+		function() {
+			this.editorUi.showDialog(new EditFileDialog(ui).container, 620, 420,
+				true, true);
+		})));
+	this.addAction('rename', function() {
+		ui.showDialog(new RenameDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('print', function() {
+		mxUtils.printScreen(graph);
+	}, null, 'sprite-print', 'Ctrl+P');
+	this.addAction('preview', function() {
+		mxUtils.show(graph, null, 10, 10);
+	});
+
+	// Edit actions
+	this.addAction('undo', function() {
+		editor.undoManager.undo();
+	}, null, 'sprite-undo', 'Ctrl+Z');
+	this.addAction('redo', function() {
+		editor.undoManager.redo();
+	}, null, 'sprite-redo', 'Ctrl+Y');
+	this.addAction('cut', function() {
+		mxClipboard.cut(graph);
+	}, null, 'sprite-cut', 'Ctrl+X');
+	this.addAction('copy', function() {
+		mxClipboard.copy(graph);
+	}, null, 'sprite-copy', 'Ctrl+C');
+	this.addAction('paste', function() {
+		mxClipboard.paste(graph);
+	}, false, 'sprite-paste', 'Ctrl+V');
+	this.addAction('delete', function() {
+		graph.removeCells();
+	}, null, null, 'Delete');
+	this.addAction('duplicate', function() {
+		var s = graph.gridSize;
+		graph.setSelectionCells(graph.moveCells(graph.getSelectionCells(), s, s,
+			true));
+	}, null, null, 'Ctrl+D');
+	this.addAction('selectVertices', function() {
+		graph.selectVertices();
+	}, null, null, 'Ctrl+Shift+V');
+	this.addAction('selectEdges', function() {
+		graph.selectEdges();
+	}, null, null, 'Ctrl+Shift+E');
+	this.addAction('selectAll', function() {
+		graph.selectAll();
+	}, null, null, 'Ctrl+A');
+
+	// Navigation actions
+	this.addAction('home', function() {
+		graph.home();
+	}, null, null, 'Home');
+	this.addAction('exitGroup', function() {
+		graph.exitGroup();
+	}, null, null, 'Page Up');
+	this.addAction('enterGroup', function() {
+		graph.enterGroup();
+	}, null, null, 'Page Down');
+	this.addAction('expand', function() {
+		graph.foldCells(false);
+	}, null, null, 'Enter');
+	this.addAction('collapse', function() {
+		graph.foldCells(true);
+	}, null, null, 'Backspace');
+
+	// Arrange actions
+	this.addAction('toFront', function() {
+		graph.orderCells(false);
+	}, null, null, 'Ctrl+F');
+	this.addAction('toBack', function() {
+		graph.orderCells(true);
+	}, null, null, 'Ctrl+B');
+	this.addAction('group', function() {
+		graph.setSelectionCell(graph.groupCells(null, 0));
+	}, null, null, 'Ctrl+G');
+	this.addAction('ungroup', function() {
+		graph.setSelectionCells(graph.ungroupCells());
+	}, null, null, 'Ctrl+U');
+	this.addAction('removeFromGroup', function() {
+		graph.removeCellsFromParent();
+	});
+	this.addAction('editLink', function() {
+		var cell = graph.getSelectionCell();
+		var link = graph.getLinkForCell(cell);
+
+		if (link == null) {
+			link = '';
+		}
+
+		link = mxUtils.prompt(mxResources.get('enterValue'), link);
+
+		if (link != null) {
+			graph.setLinkForCell(cell, link);
+		}
+	});
+	this.addAction('openLink', function() {
+		var cell = graph.getSelectionCell();
+		var link = graph.getLinkForCell(cell);
+
+		if (link != null) {
+			window.open(link);
+		}
+	});
+	this.addAction('autosize', function() {
+		var cells = graph.getSelectionCells();
+
+		if (cells != null) {
+			graph.getModel().beginUpdate();
+			try {
+				for (var i = 0; i < cells.length; i++) {
+					var cell = cells[i];
+
+					if (graph.getModel().getChildCount(cell)) {
+						graph.updateGroupBounds([cell], 20);
+					} else {
+						graph.updateCellSize(cell);
+					}
+				}
+			} finally {
+				graph.getModel().endUpdate();
+			}
+		}
+	});
+	this.addAction('wordWrap', function() {
+		var state = graph.getView().getState(graph.getSelectionCell());
+		var value = 'wrap';
+
+		if (state != null && state.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') {
+			value = null;
+		}
+
+		graph.setCellStyles(mxConstants.STYLE_WHITE_SPACE, value);
+	});
+	this.addAction('rotation', function() {
+		var value = '0';
+		var state = graph.getView().getState(graph.getSelectionCell());
+
+		if (state != null) {
+			value = state.style[mxConstants.STYLE_ROTATION] || value;
+		}
+
+		value = mxUtils.prompt(mxResources.get('enterValue') + ' (' +
+			mxResources.get('rotation') + ' 0-360)', value);
+
+		if (value != null) {
+			graph.setCellStyles(mxConstants.STYLE_ROTATION, value);
+		}
+	});
+	this.addAction('tilt', function() {
+		var cells = graph.getSelectionCells();
+
+		if (cells != null) {
+			graph.getModel().beginUpdate();
+			try {
+				for (var i = 0; i < cells.length; i++) {
+					var cell = cells[i];
+
+					if (graph.getModel().isVertex(cell) && graph.getModel().getChildCount(
+							cell) == 0) {
+						var geo = graph.getCellGeometry(cell);
+
+						if (geo != null) {
+							// Rotates the size and position in the geometry
+							geo = geo.clone();
+							geo.x += geo.width / 2 - geo.height / 2;
+							geo.y += geo.height / 2 - geo.width / 2;
+							var tmp = geo.width;
+							geo.width = geo.height;
+							geo.height = tmp;
+							graph.getModel().setGeometry(cell, geo);
+
+							// Reads the current direction and advances by 90 degrees
+							var state = graph.view.getState(cell);
+
+							if (state != null) {
+								var dir = state.style[mxConstants.STYLE_DIRECTION] || 'east' /*default*/ ;
+
+								if (dir == 'east') {
+									dir = 'south';
+								} else if (dir == 'south') {
+									dir = 'west';
+								} else if (dir == 'west') {
+									dir = 'north';
+								} else if (dir == 'north') {
+									dir = 'east';
+								}
+
+								graph.setCellStyles(mxConstants.STYLE_DIRECTION, dir, [cell]);
+							}
+						}
+					}
+				}
+			} finally {
+				graph.getModel().endUpdate();
+			}
+		}
+	}, null, null, 'Ctrl+R');
+
+	// View actions
+	this.addAction('actualSize', function() {
+		graph.zoomTo(1);
+	});
+	this.addAction('zoomIn', function() {
+		graph.zoomIn();
+	}, null, null, 'Add');
+	this.addAction('zoomOut', function() {
+		graph.zoomOut();
+	}, null, null, 'Subtract');
+	this.addAction('fitWindow', function() {
+		graph.fit();
+	});
+
+	this.addAction('fitPage', mxUtils.bind(this, function() {
+		if (!graph.pageVisible) {
+			this.get('pageView').funct();
+		}
+
+		var fmt = graph.pageFormat;
+		var ps = graph.pageScale;
+		var cw = graph.container.clientWidth - 20;
+		var ch = graph.container.clientHeight - 20;
+
+		var scale = Math.floor(100 * Math.min(cw / fmt.width / ps, ch / fmt.height /
+			ps)) / 100;
+		graph.zoomTo(scale);
+
+		graph.container.scrollLeft = Math.round(graph.view.translate.x * scale -
+			Math.max(10, (graph.container.clientWidth - fmt.width * ps * scale) / 2)
+		);
+		graph.container.scrollTop = Math.round(graph.view.translate.y * scale -
+			Math.max(10, (graph.container.clientHeight - fmt.height * ps * scale) /
+				2));
+	}));
+	this.addAction('fitPageWidth', mxUtils.bind(this, function() {
+		if (!graph.pageVisible) {
+			this.get('pageView').funct();
+		}
+
+		var fmt = graph.pageFormat;
+		var ps = graph.pageScale;
+		var cw = graph.container.clientWidth - 20;
+
+		var scale = Math.floor(100 * cw / fmt.width / ps) / 100;
+		graph.zoomTo(scale);
+
+		graph.container.scrollLeft = Math.round(graph.view.translate.x * scale -
+			Math.max(10, (graph.container.clientWidth - fmt.width * ps * scale) / 2)
+		);
+		graph.container.scrollTop = Math.round(graph.view.translate.y * scale -
+			Math.max(10, (graph.container.clientHeight - fmt.height * ps * scale) /
+				2));
+	}));
+	this.put('customZoom', new Action(mxResources.get('custom'), function() {
+		var value = mxUtils.prompt(mxResources.get('enterValue') + ' (%)',
+			parseInt(graph.getView().getScale() * 100));
+
+		if (value != null && value.length > 0 && !isNaN(parseInt(value))) {
+			graph.zoomTo(parseInt(value) / 100);
+		}
+	}));
+
+	// Option actions
+	var action = null;
+	action = this.addAction('grid', function() {
+		graph.setGridEnabled(!graph.isGridEnabled());
+		editor.updateGraphComponents();
+	}, null, null, 'Ctrl+Shift+G');
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.isGridEnabled();
+	});
+	action = this.addAction('guides', function() {
+		graph.graphHandler.guidesEnabled = !graph.graphHandler.guidesEnabled;
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.graphHandler.guidesEnabled;
+	});
+	action = this.addAction('tooltips', function() {
+		graph.tooltipHandler.setEnabled(!graph.tooltipHandler.isEnabled());
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.tooltipHandler.isEnabled();
+	});
+	action = this.addAction('navigation', function() {
+		graph.foldingEnabled = !graph.foldingEnabled;
+		graph.view.revalidate();
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.foldingEnabled;
+	});
+	action = this.addAction('scrollbars', function() {
+		graph.scrollbars = !graph.scrollbars;
+		editor.updateGraphComponents();
+
+		if (!graph.scrollbars) {
+			var t = graph.view.translate;
+			graph.view.setTranslate(t.x - graph.container.scrollLeft / graph.view.scale,
+				t.y - graph.container.scrollTop / graph.view.scale);
+			graph.container.scrollLeft = 0;
+			graph.container.scrollTop = 0;
+			graph.sizeDidChange();
+		} else {
+			var dx = graph.view.translate.x;
+			var dy = graph.view.translate.y;
+
+			graph.view.translate.x = 0;
+			graph.view.translate.y = 0;
+			graph.sizeDidChange();
+			graph.container.scrollLeft -= Math.round(dx * graph.view.scale);
+			graph.container.scrollTop -= Math.round(dy * graph.view.scale);
+		}
+	}, !mxClient.IS_TOUCH);
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.container.style.overflow == 'auto';
+	});
+	action = this.addAction('pageView', mxUtils.bind(this, function() {
+		graph.pageVisible = !graph.pageVisible;
+		graph.pageBreaksVisible = graph.pageVisible;
+		graph.preferPageSize = graph.pageBreaksVisible;
+		graph.view.validate();
+		graph.sizeDidChange();
+
+		editor.updateGraphComponents();
+		editor.outline.update();
+
+		if (mxUtils.hasScrollbars(graph.container)) {
+			if (graph.pageVisible) {
+				graph.container.scrollLeft -= 20;
+				graph.container.scrollTop -= 20;
+			} else {
+				graph.container.scrollLeft += 20;
+				graph.container.scrollTop += 20;
+			}
+		}
+	}));
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.pageVisible;
+	});
+	action = this.addAction('connect', function() {
+		graph.setConnectable(!graph.connectionHandler.isEnabled());
+	}, null, null, 'Ctrl+Q');
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.connectionHandler.isEnabled();
+	});
+	action = this.addAction('copyConnect', function() {
+		graph.connectionHandler.setCreateTarget(!graph.connectionHandler.isCreateTarget());
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.connectionHandler.isCreateTarget();
+	});
+
+	// Help actions
+	this.addAction('help', function() {
+		var ext = '';
+
+		if (mxResources.isLanguageSupported(mxClient.language)) {
+			ext = '_' + mxClient.language;
+		}
+
+		window.open(RESOURCES_PATH + '/help' + ext + '.html');
+	});
+	this.put('about', new Action(mxResources.get('about') + ' Cally Square',
+		function() {
+			ui.showDialog(new AboutDialog(ui).container, 320, 280, true, true);
+		}, null, null, 'F1'));
+
+	// Font style actions
+	var toggleFontStyle = mxUtils.bind(this, function(key, style) {
+		this.addAction(key, function() {
+			graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, style);
+		});
+	});
+
+	toggleFontStyle('bold', mxConstants.FONT_BOLD);
+	toggleFontStyle('italic', mxConstants.FONT_ITALIC);
+	toggleFontStyle('underline', mxConstants.FONT_UNDERLINE);
+
+	// Format actions
+	this.addAction('shadow', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_SHADOW);
+	});
+	this.addAction('dashed', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_DASHED);
+	});
+	this.addAction('rounded', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_ROUNDED);
+	});
+	this.addAction('curved', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_CURVED);
+	});
+	this.addAction('style', function() {
+		var cells = graph.getSelectionCells();
+
+		if (cells != null && cells.length > 0) {
+			var model = graph.getModel();
+			var style = mxUtils.prompt(mxResources.get('enterValue') + ' (' +
+				mxResources.get('style') + ')',
+				model.getStyle(cells[0]) || '');
+
+			if (style != null) {
+				graph.setCellStyle(style, cells);
+			}
+		}
+	});
+	this.addAction('setAsDefaultEdge', function() {
+		graph.setDefaultEdge(graph.getSelectionCell());
+	});
+	this.addAction('addWaypoint', function() {
+		var cell = graph.getSelectionCell();
+
+		if (cell != null && graph.getModel().isEdge(cell)) {
+			var handler = editor.graph.selectionCellsHandler.getHandler(cell);
+
+			if (handler instanceof mxEdgeHandler) {
+				var t = graph.view.translate;
+				var s = graph.view.scale;
+				var dx = t.x;
+				var dy = t.y;
+
+				var parent = graph.getModel().getParent(cell);
+				var pgeo = graph.getCellGeometry(parent);
+
+				if (graph.getModel().isVertex(parent) && pgeo != null) {
+					dx += pgeo.x;
+					dy += pgeo.y;
+				}
+
+				handler.addPointAt(handler.state, graph.panningHandler.triggerX / s - dx,
+					graph.panningHandler.triggerY / s - dy);
+			}
+		}
+	});
+	this.addAction('removeWaypoint', function() {
+		// TODO: Action should run with "this" set to action
+		var rmWaypointAction = ui.actions.get('removeWaypoint');
+
+		if (rmWaypointAction.handler != null) {
+			// NOTE: Popupevent handled and action updated in Menus.createPopupMenu
+			rmWaypointAction.handler.removePoint(rmWaypointAction.handler.state,
+				rmWaypointAction.index);
+		}
+	});
+	this.addAction('image', function() {
+		function updateImage(value, w, h) {
+			var select = null;
+			var cells = graph.getSelectionCells();
+
+			graph.getModel().beginUpdate();
+			try {
+				// Inserts new cell if no cell is selected
+				if (cells.length == 0) {
+					var gs = graph.getGridSize();
+					cells = [graph.insertVertex(graph.getDefaultParent(), null, '', gs, gs,
+						w, h)];
+					select = cells;
+				}
+
+				graph.setCellStyles(mxConstants.STYLE_IMAGE, value, cells);
+				graph.setCellStyles(mxConstants.STYLE_SHAPE, 'image', cells);
+
+				if (graph.getSelectionCount() == 1) {
+					if (w != null && h != null) {
+						var cell = cells[0];
+						var geo = graph.getModel().getGeometry(cell);
+
+						if (geo != null) {
+							geo = geo.clone();
+							geo.width = w;
+							geo.height = h;
+							graph.getModel().setGeometry(cell, geo);
+						}
+					}
+				}
+			} finally {
+				graph.getModel().endUpdate();
+			}
+
+			if (select != null) {
+				graph.setSelectionCells(select);
+				graph.scrollCellToVisible(select[0]);
+			}
+		};
+
+		var value = '';
+		var state = graph.getView().getState(graph.getSelectionCell());
+
+		if (state != null) {
+			value = state.style[mxConstants.STYLE_IMAGE] || value;
+		}
+
+		value = mxUtils.prompt(mxResources.get('enterValue') + ' (' + mxResources.get(
+			'url') + ')', value);
+
+		if (value != null) {
+			if (value.length > 0) {
+				var img = new Image();
+
+				img.onload = function() {
+					updateImage(value, img.width, img.height);
+				};
+				img.onerror = function() {
+					mxUtils.alert(mxResources.get('fileNotFound'));
+				};
+
+				img.src = value;
+			}
+		}
+	});
+};
+
+/**
+ * Registers the given action under the given name.
+ */
+Actions.prototype.addAction = function(key, funct, enabled, iconCls, shortcut) {
+	return this.put(key, new Action(mxResources.get(key), funct, enabled, iconCls,
+		shortcut));
+};
+
+/**
+ * Registers the given action under the given name.
+ */
+Actions.prototype.put = function(name, action) {
+	this.actions[name] = action;
+
+	return action;
+};
+
+/**
+ * Returns the action for the given name or null if no such action exists.
+ */
+Actions.prototype.get = function(name) {
+	return this.actions[name];
+};
+
+/**
+ * Constructs a new action for the given parameters.
+ */
+function Action(label, funct, enabled, iconCls, shortcut) {
+	mxEventSource.call(this);
+	this.label = label;
+	this.funct = funct;
+	this.enabled = (enabled != null) ? enabled : true;
+	this.iconCls = iconCls;
+	this.shortcut = shortcut;
+};
+
+// Action inherits from mxEventSource
+mxUtils.extend(Action, mxEventSource);
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setEnabled = function(value) {
+	if (this.enabled != value) {
+		this.enabled = value;
+		this.fireEvent(new mxEventObject('stateChanged'));
+	}
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setToggleAction = function(value) {
+	this.toggleAction = value;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setSelectedCallback = function(funct) {
+	this.selectedCallback = funct;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.isSelected = function() {
+	return this.selectedCallback();
+};
diff --git a/public/assets/plugins/jscripty/js/Dialogs.js b/public/assets/plugins/jscripty/js/Dialogs.js
index f68ae35..11d6772 100644
--- a/public/assets/plugins/jscripty/js/Dialogs.js
+++ b/public/assets/plugins/jscripty/js/Dialogs.js
@@ -1 +1,1152 @@
\ No newline at end of file
+'use strict';
+/**
+ * $Id: Dialogs.js,v 1.5 2013-01-29 17:23:31 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new dialog.
+ */
+
+function createCheckbox(value) {
+	var input = document.createElement('input');
+	input.setAttribute('type', 'checkbox');
+	if (value) {
+		input.setAttribute('checked', true);
+	}
+	return input;
+}
+
+function createDropdownFromApi(path, value, option_name, option_value, editorUi, paginated) {
+	var req = new XMLHttpRequest();
+	req.open('GET', path, false); // `false` makes the request synchronous
+	req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+	req.send(null);
+	var res = [];
+	if (req.status === 200) {
+		res = JSON.parse(req.response);
+	}
+	var input = document.createElement('select');
+	var option = document.createElement('option');
+	option.text = '-- None --';
+	option.value = '0';
+	input.appendChild(option);
+	var selectValues = paginated ? res.rows : res;
+	selectValues.forEach(function(elem) {
+		option = document.createElement('option');
+		option.text = elem[option_name];
+		option.value = elem[option_value];
+		option.selected = (elem[option_value] == value);
+		input.appendChild(option);
+	});
+	input.className = 'form-control select2';
+
+	return input;
+}
+
+function createGroupedDropdownFromApi(path, value, option_name, option_value, editorUi, paginated, associationField) {
+	var req = new XMLHttpRequest();
+	req.open('GET', path, false); // `false` makes the request synchronous
+	req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+	req.send(null);
+	var res = [];
+	if (req.status === 200) {
+		res = JSON.parse(req.response);
+	}
+	var input = document.createElement('select');
+	var option = document.createElement('option');
+	option.text = '-- None --';
+	option.value = '0';
+	input.appendChild(option);
+	var selectValues = paginated ? res.rows : res;
+	var mainFilter = {};
+	var groupFilter = {};
+	mainFilter[associationField] = null;
+	var mainValues = _.filter(selectValues, mainFilter);
+	var groupValues = {};
+	mainValues.forEach(function(elem) {
+		option = document.createElement('option');
+		option.className = 'select-group-father';
+		option.text = elem[option_name].toUpperCase();
+		option.value = elem[option_value];
+		option.selected = (elem[option_value] == value);
+		input.appendChild(option);
+		groupFilter[associationField] = elem[option_value];
+		groupValues = _.filter(selectValues, groupFilter);
+		groupValues.forEach(function(elem) {
+			option = document.createElement('option');
+			option.className = 'select-group-son';
+			option.text = '-' + _.capitalize(elem[option_name]);
+			option.value = elem[option_value];
+			option.selected = (elem[option_value] == value);
+			input.appendChild(option);
+		});
+	})
+	input.className = 'form-control select2';
+
+	return input;
+}
+
+function createDropdownFromArray(array, value) {
+	var input = document.createElement('select');
+
+	for (var item in array) {
+		var option = document.createElement('option');
+		option.text = array[item];
+		option.value = item;
+		if (value > 0 || value != '')
+			option.selected = (value === item) ? true : false;
+
+		input.appendChild(option);
+	}
+
+	input.className = 'form-control select2';
+
+	return input;
+}
+
+function Dialog(editorUi, elt, w, h, modal, closable, onClose) {
+	var dx = 0;
+
+	if (mxClient.IS_IE && document.documentMode != 9) {
+		dx = 60;
+	}
+
+	w += dx;
+	h += dx;
+
+	var left = Math.max(0, Math.round((document.body.scrollWidth - w) / 2));
+	var top = Math.max(0, Math.round((Math.max(document.body.scrollHeight,
+		document.documentElement.scrollHeight) - h) / 3));
+
+	var div = editorUi.createDiv('geDialog');
+	div.className = 'modal fade in center';
+	div.style.display = 'block';
+	div.style.paddingRight = '12px';
+	// div.style.width = w + 'px';
+	// div.style.height = h + 'px';
+	// div.style.left = left + 'px';
+	// div.style.top = top + 'px';
+
+	var divModalDialog = editorUi.createDiv('geModalDialog');
+	divModalDialog.className = 'modal-dialog';
+	divModalDialog.appendChild(elt);
+	div.appendChild(divModalDialog);
+
+	if (this.bg == null) {
+		this.bg = editorUi.createDiv('background');
+		this.bg.className = 'modal-backdrop fade in';
+
+		if (mxClient.IS_QUIRKS) {
+			new mxDivResizer(this.bg);
+		}
+	}
+
+	if (modal) {
+		document.body.appendChild(this.bg);
+	}
+
+	document.body.appendChild(div);
+
+	this.onDialogClose = onClose;
+	this.container = div;
+};
+
+/**
+ * Removes the dialog from the DOM.
+ */
+Dialog.prototype.close = function() {
+	if (this.onDialogClose != null) {
+		this.onDialogClose();
+		this.onDialogClose = null;
+	}
+
+	this.container.parentNode.removeChild(this.container);
+	this.bg.parentNode.removeChild(this.bg);
+};
+
+/**
+ * Constructs a new open dialog.
+ */
+function ImportDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('import') + ' XML');
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-12');
+
+	var textarea = document.createElement('textarea');
+	textarea.style.width = '100%';
+	textarea.style.height = '374px';
+
+	var input = document.createElement('input');
+	input.type = 'file';
+	input.setAttribute('accept', 'text/xml');
+
+	input.addEventListener('change', function readSingleFile(evt) {
+		//Retrieve the first (and only!) File from the FileList object
+		var f = evt.target.files[0];
+		console.log(f);
+		if (f) {
+			if (f.type === 'text/xml') {
+				var r = new FileReader();
+				r.onload = function(e) {
+					var contents = e.target.result;
+					mxUtils.write(textarea, contents);
+				};
+				r.readAsText(f);
+			} else {
+				alert('Failed to load format file');
+			}
+		} else {
+			alert('Failed to load file');
+		}
+	}, false);
+
+	col1.appendChild(input);
+	col1.appendChild(textarea);
+
+	row.appendChild(col1);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('import'), mxUtils.bind(this,
+		function(data) {
+			var doc = mxUtils.parseXml(textarea.value);
+			editorUi.editor.setGraphXml(doc.documentElement);
+			editorUi.hideDialog();
+		}));
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+
+};
+
+/**
+ * Constructs a new about dialog.
+ */
+function AboutDialog(editorUi) {
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('about') + ' Cally Square');
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var img = document.createElement('img');
+	img.style.border = '0px';
+	img.setAttribute('width', '176');
+	img.setAttribute('width', '151');
+	img.setAttribute('src', IMAGE_PATH + '/logo.png');
+	body.appendChild(img);
+	mxUtils.br(body);
+	mxUtils.write(body, 'Powered by Xenialab ' + mxClient.VERSION);
+	mxUtils.br(body);
+	var link = document.createElement('a');
+	link.setAttribute('href', 'http://www.callysquare.com/');
+	link.setAttribute('target', '_blank');
+	mxUtils.write(link, 'www.callysquare.com');
+	body.appendChild(link);
+	mxUtils.br(body);
+	mxUtils.br(body);
+	//--- END BODY
+
+	var close = mxUtils.button(mxResources.get('close'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function SaveDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('saveAs'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var name = 'name';
+	var id = '_' + name;
+	var value = editorUi.editor.getOrCreateFilename();
+
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get(name));
+	col1.appendChild(label);
+
+	var select = document.createElement('input');
+	select.setAttribute('value', value + '_copy');
+	select.setAttribute('id', id)
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), function() {
+		editorUi.saveAs(select.value);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function NewDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('new'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var name = 'name';
+	var id = '_' + name;
+	var value = editorUi.editor.getOrCreateFilename();
+
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get(name));
+	col1.appendChild(label);
+
+	var select = document.createElement('input');
+	select.setAttribute('value', value + '_new');
+	select.setAttribute('id', id)
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('new'), function() {
+		editorUi.new(select.value);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function VariableDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('variable'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var name = 'name';
+	var id = '_' + name;
+	var value = editorUi.editor.getOrCreateFilename();
+
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get(name));
+	col1.appendChild(label);
+
+	var select = document.createElement('input');
+	select.setAttribute('value', 'variable name');
+	select.setAttribute('id', id)
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('new'), function() {
+		editorUi.variable(select.value);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function OpenDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('open'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get('name'));
+	col1.appendChild(label);
+
+	var req = new XMLHttpRequest();
+	req.open('GET', '/api/jscripty/projects', false); // `false` makes the request synchronous
+	req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+	req.send(null);
+	var res = [];
+	if (req.status === 200) {
+		res = JSON.parse(req.response);
+	}
+
+	var select = document.createElement('select');
+
+	for (var j = 0; j < res.length; j++) {
+		var option = document.createElement('option');
+		option.text = res[j].name;
+		option.value = res[j].id;
+		select.appendChild(option);
+	}
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('open'), function() {
+		console.log(select);
+		console.log(select.value);
+		window.open('jscripty/project/' + select.value, '_blank');
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new rename dialog.
+ */
+function RenameDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('rename'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get('name'));
+	col1.appendChild(label);
+	var name = editorUi.editor.filename;
+	var input = document.createElement('input');
+	input.setAttribute('value', name);
+	input.className = 'form-control';
+
+	col2.appendChild(input);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), function() {
+		var req = new XMLHttpRequest();
+
+		req.open("PUT", SAVE_URL + editorUi.editor.data.id, true);
+		req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+		req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+		req.send('name=' + input.value);
+
+		req.onload = function(e) {
+			if (req.status === 200) {
+				editorUi.editor.setStatus('Project successfully renamed');
+				editorUi.editor.filename = input.value;
+
+			} else {
+				editorUi.editor.setStatus(JSON.parse(req.response).errors[0].message)
+			}
+		}
+		req.onerror = function(e) {
+			mxUtils.alert(req.statusText);
+		};
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+/**
+ * Constructs a new edit file dialog.
+ */
+function EditFileDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('edit'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-12');
+
+	var textarea = document.createElement('textarea');
+	textarea.style.width = '100%';
+	textarea.style.height = '374px';
+	textarea.value = mxUtils.getPrettyXml(editorUi.editor.getGraphXml());
+
+	// Enables dropping files
+	if (fileSupport) {
+		function handleDrop(evt) {
+			evt.stopPropagation();
+			evt.preventDefault();
+
+			if (evt.dataTransfer.files.length > 0) {
+				var file = evt.dataTransfer.files[0];
+
+				var reader = new FileReader();
+				reader.onload = function(e) {
+					textarea.value = e.target.result;
+				};
+				reader.readAsText(file);
+			}
+		};
+
+		function handleDragOver(evt) {
+			evt.stopPropagation();
+			evt.preventDefault();
+		};
+
+		// Setup the dnd listeners.
+		textarea.addEventListener('dragover', handleDragOver, false);
+		textarea.addEventListener('drop', handleDrop, false);
+	}
+	col1.appendChild(textarea);
+
+	row.appendChild(col1);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), function() {
+		var doc = mxUtils.parseXml(textarea.value);
+		editorUi.editor.setGraphXml(doc.documentElement);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
+
+/**
+ * Constructs a new export dialog.
+ */
+function ExportDialog(editorUi) {
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body form');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('export') + ' XML');
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var form = editorUi.createDiv('form-horizontal form-row-seperated');
+	var group = editorUi.createDiv('form-group last');
+
+	var label = document.createElement('label');
+	label.className = 'col-sm-4 control-label';
+	mxUtils.write(label, mxResources.get('filename'));
+
+	var input = document.createElement('input');
+	input.setAttribute('value', editorUi.editor.getOrCreateFilename());
+	input.className = 'form-control';
+
+	var div = editorUi.createDiv('col-sm-8');
+	div.appendChild(input);
+
+	group.appendChild(label);
+	group.appendChild(div);
+	form.appendChild(group);
+
+	body.appendChild(form);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('export'), mxUtils.bind(this,
+		function(data) {
+			editorUi.save(false);
+
+			var xml = encodeURIComponent(mxUtils.getXml(editorUi.editor.getGraphXml()));
+			new mxXmlRequest(SAVE_URL + editorUi.editor.data.id + '/download',
+				'filename=' + input.value, 'GET').simulate(document, "_blank");
+			editorUi.hideDialog();
+		}));
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
+
+/**
+ * Giuseppe Careri
+ * Constructs a new general dialog.
+ */
+
+function GeneralDialog(editorUi, cell) {
+	var graph = editorUi.editor.graph;
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body form modal-body-scroll');
+	var footer = editorUi.createDiv('modal-footer');
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('edit') + ' ' + mxResources.get(cell.value
+		.nodeName));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var inputGroup = editorUi.createDiv('form-horizontal form-row-seperated');
+	inputGroup.setAttribute('id', 'inputGroup');
+	var length = cell.value.attributes.length;
+	var form = editorUi.createDiv('form-horizontal form-row-seperated');
+
+	for (var i = 0; i < length; i++) {
+		var name = cell.value.attributes[i].name;
+		var id = '_' + name;
+		var value = cell.value.attributes[i].value;
+
+		var group = editorUi.createDiv((i == length - 1) ? 'form-group last' :
+			'form-group');
+
+		var label = document.createElement('label');
+		label.className = 'col-sm-4 control-label';
+		mxUtils.write(label, mxResources.get(name));
+		group.appendChild(label);
+
+		var input;
+
+		switch (name) {
+
+			case 'Text':
+				input = document.createElement('textarea');
+				input.innerHTML = value;
+				input.className = 'form-control';
+				break;
+			case 'Body':
+				input = document.createElement('textarea');
+				input.innerHTML = value;
+				input.className = 'form-control';
+				break;
+			case 'question':
+				input = document.createElement('textarea');
+				input.innerHTML = value;
+				input.className = 'form-control';
+
+				break;
+			case 'other':
+				input = document.createElement('input');
+				input.setAttribute('value', value);
+				input.setAttribute('placeholder', 'Insert label for "Other" answer option');
+				input.className = 'form-control';
+				break;
+				// case 'addtext':
+				// 	var input = document.createElement('input');
+				// 	input.setAttribute('type', 'checkbox');
+				// 	console.log('value', value);
+				// 	if (value == 'true') {
+				// 		input.setAttribute('checked', true);
+				//
+				// 	} else {
+				// 		input.removeAttribute("checked");
+				//
+				// 	}
+				// 	// input.value = input.checked;
+				//
+				// 	input.onclick = function() {
+				// 		this.value = this.checked;
+				//
+				// 		console.log(input);
+				// 		if (this.checked) {
+				// 			console.log(this.checked);
+				// 			var rowdiv = editorUi.createDiv('form-group');
+				// 			rowdiv.setAttribute('id', 'rowdiv');
+				// 			var col1 = editorUi.createDiv('col-md-4');
+				// 			var col2 = editorUi.createDiv('col-md-8');
+				// 			var label = document.createElement('label');
+				// 			// input.setAttribute('placeholder', 'Insert label');
+				// 			label.className = 'pull-right control-label';
+				// 			mxUtils.write(label, 'Texarea Label');
+				// 			col1.appendChild(label);
+				// 			var input = document.createElement('input');
+				// 			// input.setAttribute('placeholder', 'Insert label');
+				// 			// input.value = inputValue;
+				// 			input.className = 'form-control';
+				// 			col2.appendChild(input);
+				// 			rowdiv.appendChild(col1);
+				// 			rowdiv.appendChild(col2);
+				// 			inputGroup.appendChild(rowdiv);
+				// 			body.appendChild(inputGroup);
+				// 		} else {
+				// 			console.log(this.checked);
+				// 			document.getElementById('inputGroup').removeChild(document.getElementById('rowdiv'));
+				// 		}
+				// 	};
+				//
+				//
+				// 	input.className = 'btn green';
+				// 	break;
+			case 'add':
+				var input = mxUtils.button('+', function() {
+					var rowdiv = createDynamicInput(null, null, editorUi);
+					inputGroup.appendChild(rowdiv);
+					body.appendChild(inputGroup);
+				})
+
+				input.className = 'btn green';
+				break;
+			default:
+				input = document.createElement('input');
+				input.setAttribute('value', value);
+				input.className = 'form-control';
+				break;
+		}
+
+		input.setAttribute('id', id)
+		var div = editorUi.createDiv('col-sm-8');
+		div.appendChild(input);
+		// Help
+		if (mxResources.get('help_' + name)) {
+			var help = editorUi.createDiv('p');
+			help.className = 'help-block';
+			mxUtils.write(help, mxResources.get('help_' + name));
+			div.appendChild(help);
+		}
+
+		group.appendChild(div);
+		form.appendChild(group);
+	}
+	body.appendChild(form);
+	// console.log(cell);
+	if (cell.value.nodeName == 'input') {
+
+		if (cell.children) {
+			for (var j = 0; j < cell.children.length; j++) {
+				var selectValue = '';
+				var inputValue = '';
+				_.forEach(cell.children[j].value.attributes, function(element) {
+					// console.log(element);
+					if (element.name == 'type') {
+						selectValue = element.value;
+
+					} else if (element.name == 'label') {
+						inputValue = element.value;
+
+					}
+				})
+				var rowdiv = createDynamicInput(selectValue, inputValue, editorUi);
+				inputGroup.appendChild(rowdiv);
+
+			}
+		}
+
+	}
+	body.appendChild(inputGroup);
+
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), mxUtils.bind(this,
+		function(data) {
+			for (var i = 0; i < cell.value.attributes.length; i++) {
+				var id = '_' + cell.value.attributes[i].name;
+				var name = cell.value.attributes[i].name;
+				console.log(name);
+
+				cell.setAttribute(name, document.getElementById(id).value);
+				console.log(document.getElementById(id).value);
+
+
+			};
+
+			if (cell.value.nodeName == 'input') {
+				if (graph.model.getChildCount(cell)) {
+					cell.children = [];
+				}
+				if (document.getElementById('inputGroup')) {
+					var inputGroup = document.getElementById('inputGroup').children;
+					for (var j = 0; j < inputGroup.length; j++) {
+
+						var divSelect = inputGroup[j].children[1];
+						var divInput = inputGroup[j].children[0];
+						var doc = mxUtils.createXmlDocument();
+						var node = doc.createElement('inputGroup');
+						node.setAttribute('type', inputGroup[j].getElementsByTagName('select')[0].value);
+						node.setAttribute('label', inputGroup[j].getElementsByTagName('input')[0].value);
+						var cells = new mxCell(node);
+						graph.model.add(cell, cells);
+					}
+				}
+			}
+
+			graph.refresh(cell);
+			editorUi.hideDialog();
+		}));
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
+
+
+
+function createDynamicInput(selectValue, inputValue, editorUi) {
+
+	var rowdiv = editorUi.createDiv('form-group');
+	var col1 = editorUi.createDiv('col-md-5');
+	var col2 = editorUi.createDiv('col-md-5');
+	var col3 = editorUi.createDiv('col-md-2');
+
+	var input = document.createElement('input');
+	input.setAttribute('placeholder', 'Insert label');
+	input.value = inputValue;
+	input.className = 'form-control';
+	col1.appendChild(input);
+
+	var select = document.createElement('select');
+	var option = document.createElement('option');
+	option.text = '-- None --';
+	option.value = '';
+	select.appendChild(option);
+
+	var selectValues = ['text', 'number', 'textarea', 'checkbox'];
+	selectValues.forEach(function(elem) {
+		option = document.createElement('option');
+		option.text = elem;
+		option.value = elem;
+		option.selected = (elem == selectValue);
+		select.appendChild(option);
+	})
+	select.className = 'form-control';
+	col2.appendChild(select);
+	var button = mxUtils.button('x', function() {
+		var buttonParent = button.parentElement;
+		var divParent = buttonParent.parentElement;
+		document.getElementById('inputGroup').removeChild(divParent);
+	})
+
+	button.className = 'btn red';
+	col3.appendChild(button);
+	rowdiv.appendChild(col1);
+	rowdiv.appendChild(col2);
+	rowdiv.appendChild(col3);
+	return rowdiv;
+
+
+}
diff --git a/public/assets/plugins/jscripty/js/Editor.js b/public/assets/plugins/jscripty/js/Editor.js
index d69ea21..9858989 100644
--- a/public/assets/plugins/jscripty/js/Editor.js
+++ b/public/assets/plugins/jscripty/js/Editor.js
@@ -1 +1,888 @@
\ No newline at end of file
+/**
+ * $Id: Editor.js,v 1.14 2013/03/06 17:57:10 boris Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+// Specifies if local storage should be used (eg. on the iPad which has no filesystem)
+var useLocalStorage = (mxClient.IS_TOUCH || urlParams['storage'] == 'local') && typeof(localStorage) != 'undefined';
+var fileSupport = window.File != null && window.FileReader != null && window.FileList != null;
+
+// Specifies if connector should be shown on selected cells
+var touchStyle = mxClient.IS_TOUCH || urlParams['touch'] == '1';
+
+// Counts open editor tabs (must be global for cross-window access)
+var counter = 0;
+
+// Cross-domain window access is not allowed in FF, so if we
+// were opened from another domain then this will fail.
+try {
+	var op = window;
+
+	while (op.opener != null && !isNaN(op.opener.counter)) {
+		op = op.opener;
+	}
+
+	// Increments the counter in the first opener in the chain
+	if (op != null) {
+		op.counter++;
+		counter = op.counter;
+	}
+} catch (e) {
+	// ignore
+}
+
+/**
+ * Editor constructor executed on page load.
+ */
+Editor = function() {
+	mxEventSource.call(this);
+	this.init();
+	this.initStencilRegistry();
+	this.graph = new Graph();
+	this.outline = new mxOutline(this.graph);
+	this.outline.updateOnPan = true;
+	this.undoManager = this.createUndoManager();
+	this.status = '';
+
+	// Contains the name which was used for the last save. Default value is null.
+	this.filename = null;
+
+	this.getOrCreateFilename = function() {
+		return this.filename || mxResources.get('drawing', [counter]) + '.xml';
+	};
+
+	this.getFilename = function() {
+		return this.filename;
+	};
+
+	// Sets the status and fires a statusChanged event
+	this.setStatus = function(value) {
+		this.status = value;
+		this.fireEvent(new mxEventObject('statusChanged'));
+	};
+
+	// Returns the current status
+	this.getStatus = function() {
+		return this.status;
+	};
+
+	// Contains the current modified state of the diagram. This is false for
+	// new diagrams and after the diagram was saved.
+	this.modified = false;
+
+	// Updates modified state if graph changes
+	this.graphChangeListener = function() {
+		this.modified = true;
+	};
+	this.graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function() {
+		this.graphChangeListener.apply(this, arguments);
+	}));
+
+	// Installs dialog if browser window is closed without saving
+	// This must be disabled during save and image export
+	window.onbeforeunload = mxUtils.bind(this, function() {
+		if (this.modified) {
+			return mxResources.get('allChangesLost');
+		}
+	});
+
+	// Sets persistent graph state defaults
+	this.graph.resetViewOnRootChange = false;
+	this.graph.scrollbars = true;
+	this.graph.background = null;
+};
+
+// Editor inherits from mxEventSource
+mxUtils.extend(Editor, mxEventSource);
+
+/**
+ * Specifies the image URL to be used for the grid.
+ */
+Editor.prototype.gridImage = IMAGE_PATH + '/grid.gif';
+
+/**
+ * Specifies the image URL to be used for the transparent background.
+ */
+Editor.prototype.transparentImage = IMAGE_PATH + '/transparent.gif';
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.setGraphXml = function(node) {
+	var dec = new mxCodec(node.ownerDocument);
+
+	if (node.nodeName == 'mxGraphModel') {
+		this.graph.view.scale = 1;
+		this.graph.gridEnabled = node.getAttribute('grid') != '0';
+		this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
+		this.graph.setTooltips(node.getAttribute('tooltips') != '0');
+		this.graph.setConnectable(node.getAttribute('connect') != '0');
+		this.graph.foldingEnabled = node.getAttribute('fold') != '0';
+		this.graph.scrollbars = node.getAttribute('scrollbars') != '0';
+
+		if (!this.graph.scrollbars) {
+			this.graph.container.scrollLeft = 0;
+			this.graph.container.scrollTop = 0;
+			this.graph.view.translate.x = Number(node.getAttribute('dx') || 0);
+			this.graph.view.translate.y = Number(node.getAttribute('dy') || 0);
+		}
+
+		this.graph.pageVisible = node.getAttribute('page') == '1';
+		this.graph.pageBreaksVisible = this.graph.pageVisible;
+		this.graph.preferPageSize = this.graph.pageBreaksVisible;
+
+		// Loads the persistent state settings
+		var ps = node.getAttribute('pageScale');
+
+		if (ps != null) {
+			this.graph.pageScale = ps;
+		} else {
+			this.graph.pageScale = 1.5;
+		}
+
+		var pw = node.getAttribute('pageWidth');
+		var ph = node.getAttribute('pageHeight');
+
+		if (pw != null && ph != null) {
+			this.graph.pageFormat = new mxRectangle(0, 0, parseFloat(pw), parseFloat(ph));
+			this.outline.outline.pageFormat = this.graph.pageFormat;
+		}
+
+		// Loads the persistent state settings
+		var bg = node.getAttribute('background');
+
+		if (bg != null && bg.length > 0) {
+			this.graph.background = bg;
+		}
+
+		dec.decode(node, this.graph.getModel());
+		this.updateGraphComponents();
+	}
+};
+
+/**
+ * Returns the XML node that represents the current diagram.
+ */
+Editor.prototype.getGraphXml = function() {
+	var enc = new mxCodec(mxUtils.createXmlDocument());
+	var node = enc.encode(this.graph.getModel());
+
+	if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0) {
+		node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
+		node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
+	}
+
+	node.setAttribute('grid', (this.graph.isGridEnabled()) ? '1' : '0');
+	node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
+	node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
+	node.setAttribute('tooltips', (this.graph.tooltipHandler.isEnabled()) ? '1' : '0');
+	node.setAttribute('connect', (this.graph.connectionHandler.isEnabled()) ? '1' : '0');
+	node.setAttribute('fold', (this.graph.foldingEnabled) ? '1' : '0');
+	node.setAttribute('page', (this.graph.pageVisible) ? '1' : '0');
+	node.setAttribute('pageScale', this.graph.pageScale);
+	node.setAttribute('pageWidth', this.graph.pageFormat.width);
+	node.setAttribute('pageHeight', this.graph.pageFormat.height);
+
+	if (!this.graph.scrollbars) {
+		node.setAttribute('scrollbars', '0');
+	}
+
+	if (this.graph.background != null) {
+		node.setAttribute('background', this.graph.background);
+	}
+
+	return node;
+};
+
+/**
+ * Keeps the graph container in sync with the persistent graph state
+ */
+Editor.prototype.updateGraphComponents = function() {
+	var graph = this.graph;
+	var outline = this.outline;
+
+	if (graph.container != null && outline.outline.container != null) {
+		if (graph.background != null) {
+			if (graph.background == 'none') {
+				graph.container.style.backgroundColor = 'transparent';
+			} else {
+				if (graph.view.backgroundPageShape != null) {
+					graph.view.backgroundPageShape.fill = graph.background;
+					graph.view.backgroundPageShape.reconfigure();
+				}
+
+				graph.container.style.backgroundColor = graph.background;
+			}
+		} else {
+			graph.container.style.backgroundColor = '';
+		}
+
+		if (graph.pageVisible) {
+			graph.container.style.backgroundColor = '#ebebeb';
+			graph.container.style.borderStyle = 'solid';
+			graph.container.style.borderColor = '#e5e5e5';
+			graph.container.style.borderTopWidth = '1px';
+			graph.container.style.borderLeftWidth = '1px';
+			graph.container.style.borderRightWidth = '0px';
+			graph.container.style.borderBottomWidth = '0px';
+		} else {
+			graph.container.style.border = '';
+		}
+
+		outline.outline.container.style.backgroundColor = graph.container.style.backgroundColor;
+
+		if (outline.outline.pageVisible != graph.pageVisible ||
+			outline.outline.pageScale != graph.pageScale) {
+			outline.outline.pageScale = graph.pageScale;
+			outline.outline.pageVisible = graph.pageVisible;
+			outline.outline.view.validate();
+		}
+
+		if (graph.scrollbars && graph.container.style.overflow == 'hidden' && !touchStyle) {
+			graph.container.style.overflow = 'auto';
+		} else if (!graph.scrollbars || touchStyle) {
+			graph.container.style.overflow = 'hidden';
+		}
+
+		// Transparent.gif is a workaround for focus repaint problems in IE
+		var noBackground = (mxClient.IS_IE && document.documentMode >= 9) ? 'url(' + this.transparentImage + ')' : 'none';
+		graph.container.style.backgroundImage = (!graph.pageVisible && graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : noBackground;
+
+		if (graph.view.backgroundPageShape != null) {
+			graph.view.backgroundPageShape.node.style.backgroundImage = (this.graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : 'none';
+		}
+	}
+};
+
+/**
+ * Initializes the environment.
+ */
+Editor.prototype.init = function() {
+	// Adds stylesheet for IE6
+	if (mxClient.IS_IE6) {
+		mxClient.link('stylesheet', CSS_PATH + '/grapheditor-ie6.css');
+	}
+
+	// Adds required resources (disables loading of fallback properties, this can only
+	// be used if we know that all keys are defined in the language specific file)
+	mxResources.loadDefaultBundle = false;
+	mxResources.add(RESOURCE_BASE);
+
+	// Makes the connection hotspot smaller
+	mxConstants.DEFAULT_HOTSPOT = 0.3;
+
+	var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
+	mxConnectionHandler.prototype.createMarker = function() {
+		var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
+
+		// Overrides to ignore hotspot only for target terminal
+		marker.intersects = mxUtils.bind(this, function(state, evt) {
+			if (this.isConnecting()) {
+				return true;
+			}
+
+			return mxCellMarker.prototype.intersects.apply(marker, arguments);
+		});
+
+		return marker;
+	};
+
+	// Makes the shadow brighter
+	mxConstants.SHADOWCOLOR = '#d0d0d0';
+
+	// Changes some default colors
+	mxConstants.HANDLE_FILLCOLOR = '#99ccff';
+	mxConstants.HANDLE_STROKECOLOR = '#0088cf';
+	mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
+	mxConstants.OUTLINE_COLOR = '#00a8ff';
+	mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff';
+	mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff';
+	mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff';
+	mxConstants.EDGE_SELECTION_COLOR = '#00a8ff';
+	mxConstants.DEFAULT_VALID_COLOR = '#00a8ff';
+	mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff';
+	mxConstants.GUIDE_COLOR = '#0088cf';
+
+	mxGraph.prototype.pageBreakColor = '#c0c0c0';
+	mxGraph.prototype.pageScale = 1;
+
+	// Increases default rubberband opacity (default is 20)
+	mxRubberband.prototype.defaultOpacity = 30;
+
+	// Changes border color of background page shape
+	mxGraphView.prototype.createBackgroundPageShape = function(bounds) {
+		return new mxRectangleShape(bounds, this.graph.background || 'white', '#cacaca');
+	};
+
+	// Fits the number of background pages to the graph
+	mxGraphView.prototype.getBackgroundPageBounds = function() {
+		var gb = this.getGraphBounds();
+
+		// Computes unscaled, untranslated graph bounds
+		var x = (gb.width > 0) ? gb.x / this.scale - this.translate.x : 0;
+		var y = (gb.height > 0) ? gb.y / this.scale - this.translate.y : 0;
+		var w = gb.width / this.scale;
+		var h = gb.height / this.scale;
+
+		var fmt = this.graph.pageFormat;
+		var ps = this.graph.pageScale;
+
+		var pw = fmt.width * ps;
+		var ph = fmt.height * ps;
+
+		var x0 = Math.floor(Math.min(0, x) / pw);
+		var y0 = Math.floor(Math.min(0, y) / ph);
+		var xe = Math.ceil(Math.max(1, x + w) / pw);
+		var ye = Math.ceil(Math.max(1, y + h) / ph);
+
+		var rows = xe - x0;
+		var cols = ye - y0;
+
+		var bounds = new mxRectangle(this.scale * (this.translate.x + x0 * pw), this.scale *
+			(this.translate.y + y0 * ph), this.scale * rows * pw, this.scale * cols * ph);
+
+		return bounds;
+	};
+
+	// Add panning for background page in VML
+	var graphPanGraph = mxGraph.prototype.panGraph;
+	mxGraph.prototype.panGraph = function(dx, dy) {
+		graphPanGraph.apply(this, arguments);
+
+		if ((this.dialect != mxConstants.DIALECT_SVG && this.view.backgroundPageShape != null) &&
+			(!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container))) {
+			this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
+			this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
+		}
+	};
+
+	var editor = this;
+
+	// Uses HTML for background pages (to support grid background image)
+	mxGraphView.prototype.validateBackground = function() {
+		var bg = this.graph.getBackgroundImage();
+
+		if (bg != null) {
+			if (this.backgroundImage == null || this.backgroundImage.image != bg.src) {
+				if (this.backgroundImage != null) {
+					this.backgroundImage.destroy();
+				}
+
+				var bounds = new mxRectangle(0, 0, 1, 1);
+
+				this.backgroundImage = new mxImageShape(bounds, bg.src);
+				this.backgroundImage.dialect = this.graph.dialect;
+				this.backgroundImage.init(this.backgroundPane);
+				this.backgroundImage.redraw();
+			}
+
+			this.redrawBackgroundImage(this.backgroundImage, bg);
+		} else if (this.backgroundImage != null) {
+			this.backgroundImage.destroy();
+			this.backgroundImage = null;
+		}
+
+		if (this.graph.pageVisible) {
+			var bounds = this.getBackgroundPageBounds();
+
+			if (this.backgroundPageShape == null) {
+				this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+				this.backgroundPageShape.scale = 1;
+				this.backgroundPageShape.isShadow = true;
+				this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
+				this.backgroundPageShape.init(this.graph.container);
+				// Required for the browser to render the background page in correct order
+				this.graph.container.firstChild.style.position = 'absolute';
+				this.graph.container.insertBefore(this.backgroundPageShape.node, this.graph.container.firstChild);
+				this.backgroundPageShape.redraw();
+
+				this.backgroundPageShape.node.className = 'geBackgroundPage';
+				this.backgroundPageShape.node.style.backgroundPosition = '-1px -1px';
+
+				// Adds listener for double click handling on background
+				mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
+					mxUtils.bind(this, function(evt) {
+						this.graph.dblClick(evt);
+					})
+				);
+
+				// Adds basic listeners for graph event dispatching outside of the
+				// container and finishing the handling of a single gesture
+				mxEvent.addGestureListeners(this.backgroundPageShape.node,
+					mxUtils.bind(this, function(evt) {
+						this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt) {
+						// Hides the tooltip if mouse is outside container
+						if (this.graph.tooltipHandler != null &&
+							this.graph.tooltipHandler.isHideOnHover()) {
+							this.graph.tooltipHandler.hide();
+						}
+
+						if (this.graph.isMouseDown &&
+							!mxEvent.isConsumed(evt)) {
+							this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+								new mxMouseEvent(evt));
+						}
+					}),
+					mxUtils.bind(this, function(evt) {
+						this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+							new mxMouseEvent(evt));
+					}));
+			} else {
+				this.backgroundPageShape.scale = 1;
+				this.backgroundPageShape.bounds = bounds;
+				this.backgroundPageShape.redraw();
+			}
+
+			this.backgroundPageShape.node.style.backgroundImage = (this.graph.isGridEnabled()) ?
+				'url(' + editor.gridImage + ')' : 'none';
+		} else if (this.backgroundPageShape != null) {
+			this.backgroundPageShape.destroy();
+			this.backgroundPageShape = null;
+		}
+	};
+
+	// Draws page breaks only within the page
+	mxGraph.prototype.updatePageBreaks = function(visible, width, height) {
+		var scale = this.view.scale;
+		var tr = this.view.translate;
+		var fmt = this.pageFormat;
+		var ps = scale * this.pageScale;
+
+		var bounds2 = this.view.getBackgroundPageBounds();
+
+		width = bounds2.width;
+		height = bounds2.height;
+		var bounds = new mxRectangle(scale * tr.x, scale * tr.y,
+			fmt.width * ps, fmt.height * ps);
+
+		// Does not show page breaks if the scale is too small
+		visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+		var horizontalCount = (visible) ? Math.ceil(width / bounds.width) - 1 : 0;
+		var verticalCount = (visible) ? Math.ceil(height / bounds.height) - 1 : 0;
+		var right = bounds2.x + width;
+		var bottom = bounds2.y + height;
+
+		if (this.horizontalPageBreaks == null && horizontalCount > 0) {
+			this.horizontalPageBreaks = [];
+		}
+
+		if (this.horizontalPageBreaks != null) {
+			for (var i = 0; i <= horizontalCount; i++) {
+				var pts = [new mxPoint(bounds2.x + (i + 1) * bounds.width, bounds2.y),
+					new mxPoint(bounds2.x + (i + 1) * bounds.width, bottom)
+				];
+
+				if (this.horizontalPageBreaks[i] != null) {
+					this.horizontalPageBreaks[i].scale = 1;
+					this.horizontalPageBreaks[i].points = pts;
+					this.horizontalPageBreaks[i].redraw();
+				} else {
+					var pageBreak = new mxPolyline(pts, this.pageBreakColor, this.scale);
+					pageBreak.dialect = this.dialect;
+					pageBreak.isDashed = this.pageBreakDashed;
+					pageBreak.addPipe = false;
+					pageBreak.scale = scale;
+					pageBreak.init(this.view.backgroundPane);
+					pageBreak.redraw();
+
+					this.horizontalPageBreaks[i] = pageBreak;
+				}
+			}
+
+			for (var i = horizontalCount; i < this.horizontalPageBreaks.length; i++) {
+				this.horizontalPageBreaks[i].destroy();
+			}
+
+			this.horizontalPageBreaks.splice(horizontalCount, this.horizontalPageBreaks.length - horizontalCount);
+		}
+
+		if (this.verticalPageBreaks == null && verticalCount > 0) {
+			this.verticalPageBreaks = [];
+		}
+
+		if (this.verticalPageBreaks != null) {
+			for (var i = 0; i <= verticalCount; i++) {
+				var pts = [new mxPoint(bounds2.x, bounds2.y + (i + 1) * bounds.height),
+					new mxPoint(right, bounds2.y + (i + 1) * bounds.height)
+				];
+
+				if (this.verticalPageBreaks[i] != null) {
+					this.verticalPageBreaks[i].scale = 1; //scale;
+					this.verticalPageBreaks[i].points = pts;
+					this.verticalPageBreaks[i].redraw();
+				} else {
+					var pageBreak = new mxPolyline(pts, this.pageBreakColor, scale);
+					pageBreak.dialect = this.dialect;
+					pageBreak.isDashed = this.pageBreakDashed;
+					pageBreak.addPipe = false;
+					pageBreak.scale = scale;
+					pageBreak.init(this.view.backgroundPane);
+					pageBreak.redraw();
+
+					this.verticalPageBreaks[i] = pageBreak;
+				}
+			}
+
+			for (var i = verticalCount; i < this.verticalPageBreaks.length; i++) {
+				this.verticalPageBreaks[i].destroy();
+			}
+
+			this.verticalPageBreaks.splice(verticalCount, this.verticalPageBreaks.length - verticalCount);
+		}
+	};
+
+	// Enables snapping to off-grid terminals for edge waypoints
+	mxEdgeHandler.prototype.snapToTerminals = true;
+
+	// Enables guides
+	mxGraphHandler.prototype.guidesEnabled = true;
+
+	// Disables removing relative children from parents
+	var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
+	mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt) {
+		for (var i = 0; i < cells.length; i++) {
+			if (this.graph.getModel().isVertex(cells[i])) {
+				var geo = this.graph.getCellGeometry(cells[i]);
+
+				if (geo != null && geo.relative) {
+					return false;
+				}
+			}
+		}
+
+		return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
+	};
+
+	// Alt-move disables guides
+	mxGuide.prototype.isEnabledForEvent = function(evt) {
+		return !mxEvent.isAltDown(evt);
+	};
+
+	// Consumes click events for disabled menu items
+	mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
+	mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled) {
+		var result = mxPopupMenuAddItem.apply(this, arguments);
+
+		if (enabled != null && !enabled) {
+			mxEvent.addListener(result, 'mousedown', function(evt) {
+				mxEvent.consume(evt);
+			});
+		}
+
+		return result;
+	};
+
+	// Selects descendants before children selection mode
+	var graphHandlerGetInitialCellForEvent = mxGraphHandler.prototype.getInitialCellForEvent;
+	mxGraphHandler.prototype.getInitialCellForEvent = function(me) {
+		var model = this.graph.getModel();
+		var psel = model.getParent(this.graph.getSelectionCell());
+		var cell = graphHandlerGetInitialCellForEvent.apply(this, arguments);
+		var parent = model.getParent(cell);
+
+		if (psel == null || (psel != cell && psel != parent)) {
+			while (!this.graph.isCellSelected(cell) && !this.graph.isCellSelected(parent) &&
+				model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+				cell = parent;
+				parent = this.graph.getModel().getParent(cell);
+			}
+		}
+
+		return cell;
+	};
+
+	// Selection is delayed to mouseup if child selected
+	var graphHandlerIsDelayedSelection = mxGraphHandler.prototype.isDelayedSelection;
+	mxGraphHandler.prototype.isDelayedSelection = function(cell) {
+		var result = graphHandlerIsDelayedSelection.apply(this, arguments);
+		var model = this.graph.getModel();
+		var psel = model.getParent(this.graph.getSelectionCell());
+		var parent = model.getParent(cell);
+
+		if (psel == null || (psel != cell && psel != parent)) {
+			if (!this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+				result = true;
+			}
+		}
+
+		return result;
+	};
+
+	// Delayed selection of parent group
+	mxGraphHandler.prototype.selectDelayed = function(me) {
+		var cell = me.getCell();
+
+		if (cell == null) {
+			cell = this.cell;
+		}
+
+		var model = this.graph.getModel();
+		var parent = model.getParent(cell);
+
+		while (this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+			cell = parent;
+			parent = model.getParent(cell);
+		}
+
+		this.graph.selectCellForEvent(cell, me.getEvent());
+	};
+
+	// Returns last selected ancestor
+	mxPanningHandler.prototype.getCellForPopupEvent = function(me) {
+		var cell = me.getCell();
+		var model = this.graph.getModel();
+		var parent = model.getParent(cell);
+
+		while (model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+			if (this.graph.isCellSelected(parent)) {
+				cell = parent;
+			}
+
+			parent = model.getParent(parent);
+		}
+
+		return cell;
+	};
+};
+
+/**
+ * Creates and returns a new undo manager.
+ */
+Editor.prototype.createUndoManager = function() {
+	var graph = this.graph;
+	var undoMgr = new mxUndoManager();
+
+	// Installs the command history
+	var listener = function(sender, evt) {
+		undoMgr.undoableEditHappened(evt.getProperty('edit'));
+	};
+
+	graph.getModel().addListener(mxEvent.UNDO, listener);
+	graph.getView().addListener(mxEvent.UNDO, listener);
+
+	// Keeps the selection in sync with the history
+	var undoHandler = function(sender, evt) {
+		var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes);
+		var cells = [];
+
+		for (var i = 1; i < cand.length; i++) {
+			if (graph.view.getState(cand[i]) != null) {
+				cells.push(cand[i]);
+			}
+		}
+
+		graph.setSelectionCells(cells);
+	};
+
+	undoMgr.addListener(mxEvent.UNDO, undoHandler);
+	undoMgr.addListener(mxEvent.REDO, undoHandler);
+
+	return undoMgr;
+};
+
+/**
+ * Adds basic stencil set (no namespace).
+ */
+Editor.prototype.initStencilRegistry = function() {
+	// Loads default stencils
+	mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/general.xml');
+};
+
+/**
+ * Overrides stencil registry for dynamic loading of stencils.
+ */
+(function() {
+	/**
+	 * Maps from library names to an array of Javascript filenames,
+	 * which are synchronously loaded. Currently only stencil files
+	 * (.xml) and JS files (.js) are supported.
+	 * IMPORTANT: For embedded diagrams to work entries must also
+	 * be added in EmbedServlet.java.
+	 */
+	mxStencilRegistry.libraries = {};
+
+	/**
+	 * Stores all package names that have been dynamically loaded.
+	 * Each package is only loaded once.
+	 */
+	mxStencilRegistry.packages = [];
+
+	// Extends the default stencil registry to add dynamic loading
+	mxStencilRegistry.getStencil = function(name) {
+		var result = mxStencilRegistry.stencils[name];
+
+		if (result == null) {
+			var basename = mxStencilRegistry.getBasenameForStencil(name);
+
+			// Loads stencil files and tries again
+			if (basename != null) {
+				var libs = mxStencilRegistry.libraries[basename];
+
+				if (libs != null) {
+					if (mxStencilRegistry.packages[basename] == null) {
+						mxStencilRegistry.packages[basename] = 1;
+
+						for (var i = 0; i < libs.length; i++) {
+							var fname = libs[i];
+
+							if (fname.toLowerCase().substring(fname.length - 4, fname.length) == '.xml') {
+								mxStencilRegistry.loadStencilSet(fname, null);
+							} else if (fname.toLowerCase().substring(fname.length - 3, fname.length) == '.js') {
+								var req = mxUtils.load(fname);
+
+								if (req != null) {
+									eval.call(window, req.getText());
+								}
+							} else {
+								// FIXME: This does not yet work as the loading is triggered after
+								// the shape was used in the graph, at which point the keys have
+								// typically been translated in the calling method.
+								//mxResources.add(fname);
+							}
+						}
+					}
+				} else {
+					mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null);
+				}
+
+				result = mxStencilRegistry.stencils[name];
+			}
+		}
+
+		return result;
+	};
+
+	// Returns the basename for the given stencil or null if no file must be
+	// loaded to render the given stencil.
+	mxStencilRegistry.getBasenameForStencil = function(name) {
+		var parts = name.split('.');
+		var tmp = null;
+
+		if (parts.length > 0 && parts[0] == 'mxgraph') {
+			tmp = parts[1];
+
+			for (var i = 2; i < parts.length - 1; i++) {
+				tmp += '/' + parts[i];
+			}
+		}
+
+		return tmp;
+	};
+
+	// Loads the given stencil set
+	mxStencilRegistry.loadStencilSet = function(stencilFile, postStencilLoad, force) {
+		force = (force != null) ? force : false;
+
+		// Uses additional cache for detecting previous load attempts
+		var xmlDoc = mxStencilRegistry.packages[stencilFile];
+
+		if (force || xmlDoc == null) {
+			var install = false;
+
+			if (xmlDoc == null) {
+				var req = mxUtils.load(stencilFile);
+				xmlDoc = req.getXml();
+				mxStencilRegistry.packages[stencilFile] = xmlDoc;
+				install = true;
+			}
+
+			mxStencilRegistry.parseStencilSet(xmlDoc, postStencilLoad, install);
+		}
+	};
+
+	// Parses the given stencil set
+	mxStencilRegistry.parseStencilSet = function(xmlDocument, postStencilLoad, install) {
+		install = (install != null) ? install : true;
+		var root = xmlDocument.documentElement;
+		var shape = root.firstChild;
+		var packageName = '';
+		var name = root.getAttribute('name');
+
+		if (name != null) {
+			packageName = name + '.';
+		}
+
+		while (shape != null) {
+			if (shape.nodeType == mxConstants.NODETYPE_ELEMENT) {
+				name = shape.getAttribute('name');
+
+				if (name != null) {
+					packageName = packageName.toLowerCase();
+					var stencilName = name.replace(/ /g, "_");
+
+					if (install) {
+						mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape));
+					}
+
+					if (postStencilLoad != null) {
+						var w = shape.getAttribute('w');
+						var h = shape.getAttribute('h');
+
+						w = (w == null) ? 80 : parseInt(w, 10);
+						h = (h == null) ? 80 : parseInt(h, 10);
+
+						postStencilLoad(packageName, stencilName, name, w, h);
+					}
+				}
+			}
+
+			shape = shape.nextSibling;
+		}
+	};
+})();
+
+/**
+ * Class for asynchronously opening a new window and loading a file at the same
+ * time. This acts as a bridge between the open dialog and the new editor.
+ */
+OpenFile = function(done) {
+	this.producer = null;
+	this.consumer = null;
+	this.done = done;
+};
+
+/**
+ * Registers the editor from the new window.
+ */
+OpenFile.prototype.setConsumer = function(value) {
+	this.consumer = value;
+	this.execute();
+};
+
+/**
+ * Sets the data from the loaded file.
+ */
+OpenFile.prototype.setData = function(value, filename) {
+	this.data = value;
+	this.filename = filename;
+	this.execute();
+};
+
+/**
+ * Displays an error message.
+ */
+OpenFile.prototype.error = function(msg) {
+	this.cancel();
+	mxUtils.alert(msg);
+};
+
+/**
+ * Consumes the data.
+ */
+OpenFile.prototype.execute = function() {
+	if (this.consumer != null && this.data != null) {
+		this.consumer(this.data, this.filename);
+		this.cancel();
+	}
+};
+
+/**
+ * Cancels the operation.
+ */
+OpenFile.prototype.cancel = function() {
+	if (this.done != null) {
+		this.done();
+	}
+};
diff --git a/public/assets/plugins/jscripty/js/EditorUi.js b/public/assets/plugins/jscripty/js/EditorUi.js
index 2162a2b..b81caf5 100644
--- a/public/assets/plugins/jscripty/js/EditorUi.js
+++ b/public/assets/plugins/jscripty/js/EditorUi.js
@@ -1 +1,1325 @@
\ No newline at end of file
+/**
+ * $Id: EditorUi.js,v 1.21 2013/03/14 20:46:36 david Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new graph editor
+ */
+EditorUi = function(editor, container) {
+	this.editor = editor || new Editor();
+	this.container = container || document.getElementById('geEditor');
+	var graph = editor.graph;
+	var self = this;
+
+	// Disables scrollbars
+	this.container.style.overflow = 'hidden';
+
+	// Pre-fetches submenu image
+	new Image().src = mxPopupMenu.prototype.submenuImage;
+
+	// Pre-fetches connect image
+	if (mxConnectionHandler.prototype.connectImage != null) {
+		new Image().src = mxConnectionHandler.prototype.connectImage.src;
+	}
+
+	// Creates the user interface
+	this.actions = new Actions(this);
+	this.menus = new Menus(this);
+	this.createDivs();
+	this.refresh();
+	this.createUi();
+
+	// Disables HTML and text selection
+	var textEditing = mxUtils.bind(this, function(evt) {
+		if (evt == null) {
+			evt = window.event;
+		}
+
+		if (this.isSelectionAllowed(evt)) {
+			return true;
+		}
+
+		return graph.isEditing();
+	});
+
+	// Disables text selection while not editing and no dialog visible
+	if (this.container == document.body) {
+		this.menubarContainer.onselectstart = textEditing;
+		this.menubarContainer.onmousedown = textEditing;
+		this.toolbarContainer.onselectstart = textEditing;
+		this.toolbarContainer.onmousedown = textEditing;
+		this.diagramContainer.onselectstart = textEditing;
+		this.diagramContainer.onmousedown = textEditing;
+		this.sidebarContainer.onselectstart = textEditing;
+		this.sidebarContainer.onmousedown = textEditing;
+		this.footerContainer.onselectstart = textEditing;
+		this.footerContainer.onmousedown = textEditing;
+	}
+
+	// And uses built-in context menu while editing
+	if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' ||
+			document.documentMode < 9)) {
+		mxEvent.addListener(this.diagramContainer, 'contextmenu', textEditing);
+		mxEvent.addListener(this.sidebarContainer, 'contextmenu', textEditing);
+	} else {
+		// Allows browser context menu outside of diagram and sidebar
+		this.diagramContainer.oncontextmenu = textEditing;
+		this.sidebarContainer.oncontextmenu = textEditing;
+	}
+
+	// Contains the main graph instance inside the given panel
+	graph.init(this.diagramContainer);
+	graph.refresh();
+
+	// Enables scrollbars and sets cursor style for the container
+	graph.container.setAttribute('tabindex', '0');
+	// graph.container.style.overflow = (touchStyle) ? 'hidden' : 'auto';
+	graph.container.style.cursor = 'default';
+	graph.container.style.backgroundImage = 'url(' + editor.gridImage + ')';
+	graph.container.style.backgroundPosition = '-1px -1px';
+	graph.container.focus();
+
+	// Keeps graph container focused on mouse down
+	var graphFireMouseEvent = graph.fireMouseEvent;
+	graph.fireMouseEvent = function(evtName, me, sender) {
+		if (evtName == mxEvent.MOUSE_DOWN) {
+			this.container.focus();
+		}
+
+		graphFireMouseEvent.apply(this, arguments);
+	};
+
+	// Giuseppe Careri
+	// Defines invalid connections along with the error messages that they produce.
+
+	this.createMultiplicities(graph.multiplicities, ['start', 'end', 'input',
+		'question', 'email'
+	], [
+		[1, 1],
+		[0, 0],
+		[1, 1],
+		[1, 'n'],
+		[1, 'n']
+	], [
+		[0, 0],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n']
+
+	]);
+
+
+	// Giuseppe Careri
+	// Processes a doubleclick on an optional cell and fires a <dblclick> event.
+	// The event is fired initially.
+	// If the graph is enabled and the event has not been consumed, then <edit> is called with the given cell.
+	// The event is ignored if no cell was specified.
+	graph.dblClick = function(evt, cell) {
+		var edges = ['question'];
+		var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell',
+			cell);
+		this.fireEvent(mxe);
+
+		if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) {
+			// Override dblClick Vertices and Edges
+			if (this.getModel().isEdge(cell)) {
+				if (edges.indexOf(cell.source.value.tagName) >= 0)
+					this.startEditingAtCell(cell);
+			} else if (this.getModel().isVertex(cell)) {
+				console.log(self);
+				console.log(cell);
+				self.showDialog(new GeneralDialog(self, cell).container, 320, 280, true,
+					true);
+			}
+			mxe.consume();
+		}
+	}
+
+	// Configures automatic expand on mouseover
+	graph.panningHandler.autoExpand = true;
+
+	// Installs context menu
+	graph.panningHandler.factoryMethod = mxUtils.bind(this, function(menu, cell,
+		evt) {
+		this.menus.createPopupMenu(menu, cell, evt);
+	});
+
+	// Initializes the outline
+	editor.outline.init(this.outlineContainer);
+
+	// Hides context menu
+	mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
+		graph.panningHandler.hideMenu();
+	}));
+
+	// Adds gesture handling (pinch to zoom)
+	if (mxClient.IS_TOUCH) {
+		mxEvent.addListener(graph.container, 'gesturechange',
+			mxUtils.bind(this, function(evt) {
+				graph.view.getDrawPane().setAttribute('transform', 'scale(' + evt.scale +
+					')');
+				graph.view.getOverlayPane().style.visibility = 'hidden';
+			})
+		);
+
+		mxEvent.addListener(graph.container, 'gestureend',
+			mxUtils.bind(this, function(evt) {
+				graph.view.getDrawPane().removeAttribute('transform');
+				graph.zoomToCenter = true;
+				graph.zoom(evt.scale);
+				graph.view.getOverlayPane().style.visibility = 'visible';
+			})
+		);
+	}
+
+	// Create handler for key events
+	var keyHandler = this.createKeyHandler(editor);
+
+	// Getter for key handler
+	this.getKeyHandler = function() {
+		return keyHandler;
+	};
+
+	// Updates the editor UI after the window has been resized
+	mxEvent.addListener(window, 'resize', mxUtils.bind(this, function() {
+		this.refresh();
+		graph.sizeDidChange();
+		this.editor.outline.update(false);
+		this.editor.outline.outline.sizeDidChange();
+	}));
+
+	// Updates action and menu states
+	this.init();
+	this.open();
+};
+
+/**
+ * Specifies the size of the split bar.
+ */
+EditorUi.prototype.splitSize = (mxClient.IS_TOUCH) ? 16 : 8;
+
+/**
+ * Specifies the height of the menubar. Default is 34.
+ */
+EditorUi.prototype.menubarHeight = 33;
+
+/**
+ * Specifies the height of the toolbar. Default is 36.
+ */
+EditorUi.prototype.toolbarHeight = 36;
+
+/**
+ * Specifies the height of the footer. Default is 28.
+ */
+EditorUi.prototype.footerHeight = 28;
+
+/**
+ * Specifies the position of the horizontal split bar. Default is 212.
+ */
+EditorUi.prototype.hsplitPosition = 204;
+
+/**
+ * Specifies the position of the vertical split bar. Default is 190.
+ */
+EditorUi.prototype.vsplitPosition = 190;
+
+/**
+ * Installs the listeners to update the action states.
+ */
+EditorUi.prototype.init = function() {
+	// Updates action states
+	this.addUndoListener();
+	this.addSelectionListener();
+
+	// Overrides clipboard to update paste action state
+	var paste = this.actions.get('paste');
+
+	var updatePaste = function() {
+		paste.setEnabled(!mxClipboard.isEmpty());
+	};
+
+	var mxClipboardCut = mxClipboard.cut;
+	mxClipboard.cut = function() {
+		mxClipboardCut.apply(this, arguments);
+		updatePaste();
+	};
+
+	var mxClipboardCopy = mxClipboard.copy;
+	mxClipboard.copy = function() {
+		mxClipboardCopy.apply(this, arguments);
+		updatePaste();
+	};
+};
+
+/**
+ * Hook for allowing selection and context menu for certain events.
+ */
+EditorUi.prototype.isSelectionAllowed = function(evt) {
+	return false;
+};
+
+/**
+ * Opens the current diagram via the window.opener if one exists.
+ */
+EditorUi.prototype.open = function() {
+	// Cross-domain window access is not allowed in FF, so if we
+	// were opened from another domain then this will fail.
+	try {
+		if (window.opener != null && window.opener.openFile != null) {
+			window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml,
+				filename) {
+				try {
+					var doc = mxUtils.parseXml(xml);
+					this.editor.setGraphXml(doc.documentElement);
+					this.editor.modified = false;
+					this.editor.undoManager.clear();
+
+					if (filename != null) {
+						this.editor.filename = filename;
+					}
+				} catch (e) {
+					mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
+				}
+			}));
+		}
+	} catch (e) {
+		// ignore
+	}
+};
+
+/**
+ * Giuseppe Careri
+ * Opens the current diagram via string.
+ */
+EditorUi.prototype.openString = function(xml, filename, data) {
+	try {
+		var doc = mxUtils.parseXml(xml);
+		this.editor.setGraphXml(doc.documentElement);
+		this.editor.modified = false;
+		this.editor.undoManager.clear();
+
+		if (filename != null) {
+			this.editor.filename = filename;
+		}
+
+		if (data != null) {
+			this.editor.data = data;
+		}
+	} catch (e) {
+		mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
+	}
+};
+
+/**
+ * Saves As the current graph under the given project name.
+ */
+EditorUi.prototype.new = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+		try {
+			if (useLocalStorage) {
+				if (localStorage.getItem(name) != null &&
+					!mxUtils.confirm(mxResources.get('replace', [name]))) {
+					return;
+				}
+
+				localStorage.setItem(name, xml);
+				this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+			} else {
+				console.log(xml.length);
+				console.log(MAX_REQUEST_SIZE);
+				if (xml.length < MAX_REQUEST_SIZE) {
+					xml = encodeURIComponent(xml);
+					name = encodeURIComponent(name);
+
+					var xhr = new XMLHttpRequest();
+					xhr.open("POST", SAVE_URL, true);
+					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+					xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+					xhr.onload = function(e) {
+						if (xhr.readyState === 4) {
+							if (xhr.status === 201) {
+								editor.setStatus('Project ' + JSON.parse(xhr.response).name +
+									' is opening in new window!');
+								setTimeout(function() {
+									window.open('jscripty/project/' + JSON.parse(xhr.response).id,
+										'_blank');
+								}, 1500);
+							} else {
+								editor.setStatus('Error creating project: ' +
+									JSON.parse(xhr.response).errors[0].message);
+							}
+						}
+					};
+					xhr.onerror = function(e) {
+						mxUtils.alert(xhr.statusText);
+					};
+					xhr.send('description=project_new&name=' + name);
+				} else {
+					mxUtils.alert(mxResources.get('drawingTooLarge'));
+					mxUtils.popup(xml);
+
+					return;
+				}
+			}
+
+			this.editor.filename = name;
+			this.editor.modified = false;
+		} catch (e) {
+			this.editor.setStatus('Error creating project');
+		}
+	} else {
+		this.editor.setStatus('Error creating project');
+	}
+};
+
+
+/**
+ * Saves the current graph under the given project name.
+ */
+EditorUi.prototype.save = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+		try {
+			if (useLocalStorage) {
+				if (localStorage.getItem(name) != null &&
+					!mxUtils.confirm(mxResources.get('replace', [name]))) {
+					return;
+				}
+
+				localStorage.setItem(name, xml);
+				this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+			} else {
+				console.log(xml.length);
+				console.log(MAX_REQUEST_SIZE);
+				if (xml.length < MAX_REQUEST_SIZE) {
+					xml = encodeURIComponent(xml);
+
+					var xhr = new XMLHttpRequest();
+					xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
+					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+					xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+					xhr.onload = function(e) {
+						if (xhr.readyState === 4) {
+							if (xhr.status === 200) {
+								editor.setStatus('Updated at ' + JSON.parse(xhr.response).updatedAt +
+									' (' + xhr.status + ' ' + xhr.statusText + ')');
+							} else {
+								editor.setStatus('Error saving project: ' +
+									JSON.parse(xhr.response).errors[0].message);
+							}
+						}
+					};
+					xhr.onerror = function(e) {
+						mxUtils.alert(xhr.statusText);
+					};
+					xhr.send('draft=' + xml);
+				} else {
+					mxUtils.alert(mxResources.get('drawingTooLarge'));
+					mxUtils.popup(xml);
+
+					return;
+				}
+			}
+
+			this.editor.filename = name;
+			this.editor.modified = false;
+		} catch (e) {
+			this.editor.setStatus('Error saving file');
+		}
+	} else {
+		this.editor.setStatus('Error saving file');
+	}
+};
+
+/**
+ * Saves As the current graph under the given project name.
+ */
+EditorUi.prototype.saveAs = function(name) {
+	var editor = this.editor;
+	console.log(name);
+	if (name != null) {
+		var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+		try {
+			if (useLocalStorage) {
+				if (localStorage.getItem(name) != null &&
+					!mxUtils.confirm(mxResources.get('replace', [name]))) {
+					return;
+				}
+
+				localStorage.setItem(name, xml);
+				this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+			} else {
+				console.log(xml.length);
+				console.log(MAX_REQUEST_SIZE);
+				if (xml.length < MAX_REQUEST_SIZE) {
+					xml = encodeURIComponent(xml);
+					name = encodeURIComponent(name);
+
+					var xhr = new XMLHttpRequest();
+					xhr.open("POST", SAVE_URL, true);
+					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+					xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+					xhr.onload = function(e) {
+						if (xhr.readyState === 4) {
+							if (xhr.status === 201) {
+								editor.setStatus('Project ' + JSON.parse(xhr.response).name +
+									' is opening in new window!');
+								setTimeout(function() {
+									window.open('jscripty/project/' + JSON.parse(xhr.response).id,
+										'_blank');
+								}, 1500);
+							} else {
+								editor.setStatus('Error cloning project: ' +
+									JSON.parse(xhr.response).errors[0].message);
+							}
+						}
+					};
+					xhr.onerror = function(e) {
+						mxUtils.alert(xhr.statusText);
+					};
+					xhr.send('description=project_cloned&name=' + name +
+						'&draft=' + xml + '&production=' + xml);
+				} else {
+					mxUtils.alert(mxResources.get('drawingTooLarge'));
+					mxUtils.popup(xml);
+
+					return;
+				}
+			}
+
+			this.editor.filename = name;
+			this.editor.modified = false;
+		} catch (e) {
+			this.editor.setStatus('Error saving file');
+		}
+	} else {
+		this.editor.setStatus('Error saving file');
+	}
+};
+
+/**
+ * Plush the current graph under the given project name.
+ */
+EditorUi.prototype.validate = function(a, b) {
+	var graph = this.editor.graph;
+	a = null != a ? a : graph.model.getRoot();
+	b = null != b ? b : {};
+	for (var c = !0, d = graph.model.getChildCount(a), e = 0; e < d; e++) {
+		var f = graph.model.getChildAt(a, e),
+			g = b;
+		graph.isValidRoot(f) && (g = {});
+		g = this.validate(f, g);
+		null != g ? graph.setCellWarning(f, g.replace(/\n/g, "\x3cbr\x3e")) : graph.setCellWarning(f, null);
+		c = c && null == g
+	}
+	d = "";
+	if (graph.model.isVertex(a)) {
+		switch (a.value.nodeName) {
+			case 'question':
+				d = (a.value.getAttribute('question') !== "") ? "" :
+					'Question can not be empty' + "\n";
+				d += (a.value.getAttribute('label') !== "") ? "" : 'Label can not be empty' + "\n";
+
+				break;
+			case 'input':
+				d = (a.value.getAttribute('question') !== "") ? "" : 'Question can not be empty' + "\n";
+				d += (a.value.getAttribute('label') !== "") ? "" : 'Label can not be empty' + "\n";
+
+				break;
+			case 'start':
+				d = this.validateBlock('start') ? '' : "Only one Start block is allowed" + "\n";
+				d += (a.value.getAttribute('label') !== "") ? "" : 'Label can not be empty' + "\n";
+
+				break;
+		}
+	}
+
+	if (graph.model.isEdge(a)) {
+		var z = graph.model.getCell(a.source.getId());
+		switch (z.value.nodeName) {
+			case 'question':
+				// var patt = /^[a-zA-Z0-9][a-zA-Z0-9\s]*$/;
+				var str = String(a.getValue());
+				console.log(str);
+				if (str === 'null' || str === 'undefined') {
+					d = "Value can not be empty" + "\n";
+				} else {
+					var result = str.split(",");
+					result.forEach(function(entry, key) {
+						d = d + ((entry.trim() !== "") ? "" : 'Value' + (key + 1) + ' can not be empty' + "\n");
+					});
+				}
+				// console.log(a.getValue());
+				// if ((str == 'undefined') || (str == 'null')) {
+				// 	str = ' ';
+				// }
+				// d = ((a.getValue() !== "") && (a.getValue() !== 'undefined') && (a.getValue() !== null)) ? '' : "Value can not be empty" + "\n";
+				// d = d + ((patt.test(str.trim())) ? "" : 'Value can not be empty' + "\n");
+				//
+				// res.forEach(function(entry) {
+				// d = d + ((patt.test(entry.trim())) ? "" : 'Can not be empty' + "\n");
+				// });
+				break;
+
+		}
+	}
+
+	graph.isCellCollapsed(a) && !c && (d += (mxResources.get(graph.containsValidationErrorsResource) || graph.containsValidationErrorsResource) + "\n");
+	d = graph.model.isEdge(a) ? d + (graph.getEdgeValidationError(a, graph.model.getTerminal(a, !0), graph.model.getTerminal(a, !1)) || "") : d + (graph.getCellValidationError(a) || "");
+	e = graph.validateCell(a, b);
+	null != e && (d += e);
+	null == graph.model.getParent(a) && graph.view.validate();
+	return 0 < d.length || !c ? d : null
+}
+
+EditorUi.prototype.validateBlock = function(blockName) {
+	var graph = this.editor.graph;
+	var parent = graph.getDefaultParent();
+	var count = 0;
+	// var model = graph.getModel();
+	//We can also use model.getChildren(parent)
+	parent.children.forEach(function(n) {
+		if (graph.model.isVertex(n) && n.value.nodeName == blockName) {
+			count++;
+		}
+	});
+	return count > 1 ? false : true;
+}
+
+EditorUi.prototype.publish = function(name) {
+
+	var editor = this.editor;
+
+	if (name != null) {
+
+		var response = this.validate();
+		if (response === null) {
+			var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+			try {
+				if (useLocalStorage) {
+					if (localStorage.getItem(name) != null &&
+						!mxUtils.confirm(mxResources.get('replace', [name]))) {
+						return;
+					}
+
+					localStorage.setItem(name, xml);
+					this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+				} else {
+					console.log(xml.length);
+					console.log(MAX_REQUEST_SIZE);
+					if (xml.length < MAX_REQUEST_SIZE) {
+						xml = encodeURIComponent(xml);
+
+						var xhr = new XMLHttpRequest();
+						xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
+						xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+						xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+						xhr.onload = function(e) {
+							if (xhr.readyState === 4) {
+								if (xhr.status === 200) {
+									editor.setStatus('Published at ' + JSON.parse(xhr.response).updatedAt +
+										' (' + xhr.status + ' ' + xhr.statusText + ')');
+								} else {
+									editor.setStatus('Error saving project: ' +
+										JSON.parse(xhr.response).errors[0].message);
+								}
+							}
+						};
+						xhr.onerror = function(e) {
+							mxUtils.alert(xhr.statusText);
+						};
+						xhr.send('draft=' + xml + '&production=' + xml);
+					} else {
+						mxUtils.alert(mxResources.get('drawingTooLarge'));
+						mxUtils.popup(xml);
+
+						return;
+					}
+				}
+
+				this.editor.filename = name;
+				this.editor.modified = false;
+			} catch (e) {
+				this.editor.setStatus('Error publishing file');
+			}
+		} else {
+			editor.setStatus('Error publishing project: ');
+		}
+	} else {
+		this.editor.setStatus('Error publishing file');
+	}
+	// }
+
+
+
+};
+
+/**
+ * Create variable
+ */
+EditorUi.prototype.variable = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		try {
+
+			var xhr = new XMLHttpRequest();
+			xhr.open("POST", VARIABLE_URL, true);
+			xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+			xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+			xhr.onload = function(e) {
+				if (xhr.readyState === 4) {
+					if (xhr.status === 201) {
+						editor.setStatus('Variable created at ' + JSON.parse(xhr.response).createdAt +
+							' (' + xhr.status + ' ' + xhr.statusText + ')');
+					} else {
+						editor.setStatus('Error cloning project: ' +
+							JSON.parse(xhr.response).errors[0].message);
+					}
+				}
+			};
+			xhr.onerror = function(e) {
+				mxUtils.alert(xhr.statusText);
+			};
+			xhr.send('name=' + name);
+		} catch (e) {
+			this.editor.setStatus('Error creating variable');
+		}
+	} else {
+		this.editor.setStatus('Error creating variable');
+	}
+};
+
+/**
+ * Returns the URL for a copy of this editor with no state.
+ */
+EditorUi.prototype.getUrl = function(pathname) {
+	var href = (pathname != null) ? pathname : window.location.pathname;
+	var parms = (href.indexOf('?') > 0) ? 1 : 0;
+
+	// Removes template URL parameter for new blank diagram
+	for (var key in urlParams) {
+		if (parms == 0) {
+			href += '?';
+		} else {
+			href += '&';
+		}
+
+		href += key + '=' + urlParams[key];
+		parms++;
+	}
+
+	return href;
+};
+
+/**
+ * Updates the states of the given undo/redo items.
+ */
+EditorUi.prototype.addUndoListener = function() {
+	var undo = this.actions.get('undo');
+	var redo = this.actions.get('redo');
+
+	var undoMgr = this.editor.undoManager;
+
+	var undoListener = function() {
+		undo.setEnabled(undoMgr.canUndo());
+		redo.setEnabled(undoMgr.canRedo());
+	};
+
+	undoMgr.addListener(mxEvent.ADD, undoListener);
+	undoMgr.addListener(mxEvent.UNDO, undoListener);
+	undoMgr.addListener(mxEvent.REDO, undoListener);
+	undoMgr.addListener(mxEvent.CLEAR, undoListener);
+
+	// Updates the button states once
+	undoListener();
+};
+
+/**
+ * Updates the states of the given toolbar items based on the selection.
+ */
+EditorUi.prototype.addSelectionListener = function() {
+	var selectionListener = mxUtils.bind(this, function() {
+		var graph = this.editor.graph;
+		var selected = !graph.isSelectionEmpty();
+		var vertexSelected = false;
+		var edgeSelected = false;
+
+		var cells = graph.getSelectionCells();
+
+		if (cells != null) {
+			for (var i = 0; i < cells.length; i++) {
+				var cell = cells[i];
+
+				if (graph.getModel().isEdge(cell)) {
+					edgeSelected = true;
+				}
+
+				if (graph.getModel().isVertex(cell)) {
+					vertexSelected = true;
+				}
+
+				if (edgeSelected && vertexSelected) {
+					break;
+				}
+			}
+		}
+
+		// Updates action states
+		var actions = ['cut', 'copy', 'delete', 'duplicate', 'bold', 'italic',
+			'style', 'underline', 'toFront', 'toBack', 'dashed', 'rounded',
+			'shadow',
+			'tilt', 'autosize'
+		];
+
+		for (var i = 0; i < actions.length; i++) {
+			this.actions.get(actions[i]).setEnabled(selected);
+		}
+
+		this.actions.get('curved').setEnabled(edgeSelected);
+		this.actions.get('rotation').setEnabled(vertexSelected);
+		this.actions.get('wordWrap').setEnabled(vertexSelected);
+		this.actions.get('group').setEnabled(graph.getSelectionCount() > 1);
+		this.actions.get('ungroup').setEnabled(graph.getSelectionCount() == 1 &&
+			graph.getModel().getChildCount(graph.getSelectionCell()) > 0);
+		var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
+		this.actions.get('removeFromGroup').setEnabled(oneVertexSelected &&
+			graph.getModel().isVertex(graph.getModel().getParent(graph.getSelectionCell()))
+		);
+
+		// Updates menu states
+		var menus = ['fontFamily', 'fontSize', 'alignment', 'position', 'text',
+			'format',
+			'arrange', 'linewidth', 'spacing'
+		];
+
+		for (var i = 0; i < menus.length; i++) {
+			this.menus.get(menus[i]).setEnabled(selected);
+		}
+
+		menus = ['line', 'lineend', 'linestart'];
+
+		for (var i = 0; i < menus.length; i++) {
+			this.menus.get(menus[i]).setEnabled(edgeSelected);
+		}
+
+		this.actions.get('setAsDefaultEdge').setEnabled(edgeSelected);
+
+		this.menus.get('align').setEnabled(graph.getSelectionCount() > 1);
+		this.menus.get('direction').setEnabled(vertexSelected || (edgeSelected &&
+			graph.isLoop(graph.view.getState(graph.getSelectionCell()))));
+		this.menus.get('navigation').setEnabled(graph.foldingEnabled && ((graph.view
+				.currentRoot != null) ||
+			(graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell()))
+		));
+		this.actions.get('home').setEnabled(graph.view.currentRoot != null);
+		this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
+		var groupEnabled = graph.getSelectionCount() == 1 && graph.isValidRoot(
+			graph.getSelectionCell());
+		this.actions.get('enterGroup').setEnabled(groupEnabled);
+		this.actions.get('expand').setEnabled(groupEnabled);
+		this.actions.get('collapse').setEnabled(groupEnabled);
+		this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
+		this.actions.get('openLink').setEnabled(graph.getSelectionCount() == 1 &&
+			graph.getLinkForCell(graph.getSelectionCell()) != null);
+	});
+
+	this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE,
+		selectionListener);
+	selectionListener();
+};
+
+/**
+ * Refreshes the viewport.
+ */
+EditorUi.prototype.refresh = function() {
+	var quirks = mxClient.IS_IE && (document.documentMode == null || document.documentMode ==
+		5);
+	var w = this.container.clientWidth;
+	var h = this.container.clientHeight;
+
+	if (this.container == document.body) {
+		w = document.body.clientWidth || document.documentElement.clientWidth;
+		h = (quirks) ? document.body.clientHeight || document.documentElement.clientHeight :
+			document.documentElement.clientHeight;
+	}
+
+	var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize -
+		20));
+	var effVsplitPosition = Math.max(0, Math.min(this.vsplitPosition, h - this.menubarHeight -
+		this.toolbarHeight - this.footerHeight - this.splitSize - 1));
+
+	this.menubarContainer.style.height = this.menubarHeight + 'px';
+	this.toolbarContainer.style.top = this.menubarHeight + 'px';
+	this.toolbarContainer.style.height = this.toolbarHeight + 'px';
+
+	var tmp = this.menubarHeight + this.toolbarHeight;
+
+	if (!mxClient.IS_QUIRKS) {
+		tmp += 1;
+	}
+
+	this.sidebarContainer.style.top = tmp + 'px';
+	this.sidebarContainer.style.width = effHsplitPosition + 'px';
+	this.outlineContainer.style.width = effHsplitPosition + 'px';
+	this.outlineContainer.style.height = effVsplitPosition + 'px';
+	this.outlineContainer.style.bottom = this.footerHeight + 'px';
+	this.diagramContainer.style.left = (effHsplitPosition + this.splitSize) +
+		'px';
+	this.diagramContainer.style.top = this.sidebarContainer.style.top;
+	this.footerContainer.style.height = this.footerHeight + 'px';
+	this.footerContainer.style.display = 'none';
+	this.hsplit.style.top = this.sidebarContainer.style.top;
+	this.hsplit.style.bottom = this.outlineContainer.style.bottom;
+	this.hsplit.style.left = effHsplitPosition + 'px';
+	this.vsplit.style.width = this.sidebarContainer.style.width;
+	this.vsplit.style.bottom = (effVsplitPosition + this.footerHeight) + 'px';
+
+	if (quirks) {
+		this.menubarContainer.style.width = w + 'px';
+		this.toolbarContainer.style.width = this.menubarContainer.style.width;
+		var sidebarHeight = (h - effVsplitPosition - this.splitSize - this.footerHeight -
+			this.menubarHeight - this.toolbarHeight);
+		this.sidebarContainer.style.height = sidebarHeight + 'px';
+		this.diagramContainer.style.width = (w - effHsplitPosition - this.splitSize) +
+			'px';
+		var diagramHeight = (h - this.footerHeight - this.menubarHeight - this.toolbarHeight);
+		this.diagramContainer.style.height = diagramHeight + 'px';
+		this.footerContainer.style.width = this.menubarContainer.style.width;
+		this.hsplit.style.height = diagramHeight + 'px';
+	} else {
+		this.sidebarContainer.style.bottom = (effVsplitPosition + this.splitSize +
+			this.footerHeight) + 'px';
+		this.diagramContainer.style.bottom = this.outlineContainer.style.bottom;
+	}
+};
+
+/**
+ * Creates the required containers.
+ */
+EditorUi.prototype.createDivs = function() {
+	this.menubarContainer = this.createDiv('geMenubarContainer');
+	this.toolbarContainer = this.createDiv('geToolbarContainer');
+	this.sidebarContainer = this.createDiv('geSidebarContainer');
+	this.outlineContainer = this.createDiv('geOutlineContainer');
+	this.diagramContainer = this.createDiv('geDiagramContainer');
+	this.footerContainer = this.createDiv('geFooterContainer');
+	this.hsplit = this.createDiv('geHsplit');
+	this.vsplit = this.createDiv('geVsplit');
+
+	// Sets static style for containers
+	this.menubarContainer.style.top = '0px';
+	this.menubarContainer.style.left = '0px';
+	this.menubarContainer.style.right = '0px';
+	this.toolbarContainer.style.left = '0px';
+	this.toolbarContainer.style.right = '0px';
+	this.sidebarContainer.style.left = '0px';
+	this.outlineContainer.style.left = '0px';
+	this.diagramContainer.style.right = '0px';
+	this.footerContainer.style.left = '0px';
+	this.footerContainer.style.right = '0px';
+	this.footerContainer.style.bottom = '0px';
+	this.vsplit.style.left = '0px';
+	this.vsplit.style.height = this.splitSize + 'px';
+	this.hsplit.style.width = this.splitSize + 'px';
+};
+
+/**
+ * Creates the required containers.
+ */
+EditorUi.prototype.createUi = function() {
+	// Creates menubar
+	this.menubar = this.menus.createMenubar(this.createDiv('geMenubar'));
+	this.menubarContainer.appendChild(this.menubar.container);
+
+	// Creates toolbar
+	this.toolbar = this.createToolbar(this.createDiv('geToolbar'));
+	this.toolbarContainer.appendChild(this.toolbar.container);
+
+	// Creates the sidebar
+	this.sidebar = this.createSidebar(this.sidebarContainer);
+
+	// Creates the footer
+	this.footerContainer.appendChild(this.createFooter());
+
+	// Adds status bar in menubar
+	this.statusContainer = this.createStatusContainer();
+
+	// Connects the status bar to the editor status
+	this.editor.addListener('statusChanged', mxUtils.bind(this, function() {
+		this.setStatusText(this.editor.getStatus());
+	}));
+
+	this.setStatusText(this.editor.getStatus());
+	this.menubar.container.appendChild(this.statusContainer);
+
+	// Inserts into DOM
+	this.container.appendChild(this.menubarContainer);
+	this.container.appendChild(this.toolbarContainer);
+	this.container.appendChild(this.sidebarContainer);
+	this.container.appendChild(this.outlineContainer);
+	this.container.appendChild(this.diagramContainer);
+	this.container.appendChild(this.footerContainer);
+	this.container.appendChild(this.hsplit);
+	this.container.appendChild(this.vsplit);
+
+	// HSplit
+	this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function(
+		value) {
+		this.hsplitPosition = value;
+		this.refresh();
+		this.editor.graph.sizeDidChange();
+		this.editor.outline.update(false);
+		this.editor.outline.outline.sizeDidChange();
+	}));
+
+	// VSplit
+	this.addSplitHandler(this.vsplit, false, this.footerHeight, mxUtils.bind(
+		this,
+		function(value) {
+			this.vsplitPosition = value;
+			this.refresh();
+			this.editor.outline.update(false);
+			this.editor.outline.outline.sizeDidChange();
+		}));
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createStatusContainer = function() {
+	var container = document.createElement('a');
+	container.className = 'geItem geStatus';
+
+	return container;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createStatusContainer = function() {
+	var container = document.createElement('a');
+	container.className = 'geItem geStatus';
+
+	return container;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.setStatusText = function(value) {
+	this.statusContainer.innerHTML = value;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createToolbar = function(container) {
+	return new Toolbar(this, container);
+};
+
+/**
+ * Creates a new sidebar for the given container.
+ */
+EditorUi.prototype.createSidebar = function(container) {
+	return new Sidebar(this, container);
+};
+
+/**
+ * Creates and returns a new footer.
+ */
+EditorUi.prototype.createFooter = function() {
+	return this.createDiv('geFooter');
+};
+
+/**
+ * Creates the actual toolbar for the toolbar container.
+ */
+EditorUi.prototype.createDiv = function(classname) {
+	var elt = document.createElement('div');
+	elt.className = classname;
+
+	return elt;
+};
+
+/**
+ * Creates the custom header.
+ */
+EditorUi.prototype.createHeader = function(classname) {
+	var elt = document.createElement(classname);
+	return elt;
+};
+
+/**
+ * Creates the custom header.
+ */
+EditorUi.prototype.createForm = function(classname) {
+	var elt = document.createElement('form');
+	elt.className = classname;
+
+	return elt;
+};
+
+/**
+ * Updates the states of the given undo/redo items.
+ */
+EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange) {
+	var start = null;
+	var initial = null;
+
+	function getValue() {
+		return parseInt(((horizontal) ? elt.style.left : elt.style.bottom));
+	};
+
+	function moveHandler(evt) {
+		if (start != null) {
+			var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+			onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y -
+				pt.y)) - dx));
+			mxEvent.consume(evt);
+		}
+	};
+
+	function dropHandler(evt) {
+		moveHandler(evt);
+		start = null;
+		initial = null;
+	};
+
+	mxEvent.addGestureListeners(elt, function(evt) {
+		start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+		initial = getValue();
+		mxEvent.consume(evt);
+	});
+
+	mxEvent.addListener(document, 'mousemove', moveHandler);
+	mxEvent.addListener(document, 'touchmove', moveHandler);
+	mxEvent.addListener(document, 'mouseup', dropHandler);
+	mxEvent.addListener(document, 'touchend', dropHandler);
+};
+
+/**
+ * Displays a print dialog.
+ */
+EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose) {
+	this.hideDialog();
+	this.dialog = new Dialog(this, elt, w, (mxClient.IS_VML) ? h - 12 : h,
+		modal,
+		closable, onClose);
+};
+
+/**
+ * Displays a print dialog.
+ */
+EditorUi.prototype.hideDialog = function() {
+	if (this.dialog != null) {
+		this.dialog.close();
+		this.dialog = null;
+		this.editor.graph.container.focus();
+	}
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+EditorUi.prototype.saveFile = function(forceDialog) {
+	if (!forceDialog && this.editor.filename != null) {
+		this.save(this.editor.getOrCreateFilename());
+	} else {
+		this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
+	}
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+EditorUi.prototype.publishFile = function(forceDialog) {
+	if (!forceDialog && this.editor.filename != null) {
+		this.publish(this.editor.getOrCreateFilename());
+	} else {
+		//this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
+	}
+};
+
+/**
+ * Executes the given layout.
+ */
+EditorUi.prototype.executeLayout = function(layout, animate, ignoreChildCount) {
+	var graph = this.editor.graph;
+	var cell = graph.getSelectionCell();
+
+	// Allow global overridding of animation
+	animate = this.animate != null ? this.animate : animate;
+
+	graph.getModel().beginUpdate();
+	try {
+		layout.execute(graph.getDefaultParent(), cell);
+	} catch (e) {
+		throw e;
+	} finally {
+		// Animates the changes in the graph model except
+		// for Camino, where animation is too slow
+		if (animate && navigator.userAgent.indexOf('Camino') < 0) {
+			// New API for animating graph layout results asynchronously
+			var morph = new mxMorphing(graph);
+			morph.addListener(mxEvent.DONE, mxUtils.bind(this, function() {
+				graph.getModel().endUpdate();
+			}));
+
+			morph.startAnimation();
+		} else {
+			graph.getModel().endUpdate();
+		}
+	}
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+EditorUi.prototype.createKeyHandler = function(editor) {
+	var graph = this.editor.graph;
+	var keyHandler = new mxKeyHandler(graph);
+
+	// Routes command-key to control-key on Mac
+	keyHandler.isControlDown = function(evt) {
+		return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
+	};
+
+	// Helper function to move cells with the cursor keys
+	function nudge(keyCode) {
+		if (!graph.isSelectionEmpty()) {
+			var dx = 0;
+			var dy = 0;
+
+			if (keyCode == 37) {
+				dx = -1;
+			} else if (keyCode == 38) {
+				dy = -1;
+			} else if (keyCode == 39) {
+				dx = 1;
+			} else if (keyCode == 40) {
+				dy = 1;
+			}
+
+			graph.moveCells(graph.getSelectionCells(), dx, dy);
+			graph.scrollCellToVisible(graph.getSelectionCell());
+		}
+	};
+
+	// Binds keystrokes to actions
+	var bindAction = mxUtils.bind(this, function(code, control, key, shift) {
+		var action = this.actions.get(key);
+
+		if (action != null) {
+			var f = function() {
+				if (action.enabled) {
+					action.funct();
+				}
+			};
+
+			if (control) {
+				if (shift) {
+					keyHandler.bindControlShiftKey(code, f);
+				} else {
+					keyHandler.bindControlKey(code, f);
+				}
+			} else {
+				if (shift) {
+					keyHandler.bindShiftKey(code, f);
+				} else {
+					keyHandler.bindKey(code, f);
+				}
+			}
+		}
+	});
+
+	var ui = this;
+	var keyHandleEscape = keyHandler.escape;
+	keyHandler.escape = function(evt) {
+		ui.hideDialog();
+		keyHandleEscape.apply(this, arguments);
+	};
+
+	// Ignores enter keystroke. Remove this line if you want the
+	// enter keystroke to stop editing.
+	keyHandler.enter = function() {};
+	keyHandler.bindKey(8, function() {
+		graph.foldCells(true);
+	}); // Backspace
+	keyHandler.bindKey(13, function() {
+		graph.foldCells(false);
+	}); // Enter
+	keyHandler.bindKey(33, function() {
+		graph.exitGroup();
+	}); // Page Up
+	keyHandler.bindKey(34, function() {
+		graph.enterGroup();
+	}); // Page Down
+	keyHandler.bindKey(36, function() {
+		graph.home();
+	}); // Home
+	keyHandler.bindKey(35, function() {
+		graph.refresh();
+	}); // End
+	keyHandler.bindKey(37, function() {
+		nudge(37);
+	}); // Left arrow
+	keyHandler.bindKey(38, function() {
+		nudge(38);
+	}); // Up arrow
+	keyHandler.bindKey(39, function() {
+		nudge(39);
+	}); // Right arrow
+	keyHandler.bindKey(40, function() {
+		nudge(40);
+	}); // Down arrow
+	keyHandler.bindKey(113, function() {
+		graph.startEditingAtCell();
+	});
+	bindAction(46, false, 'delete'); // Delete
+	bindAction(82, true, 'tilt'); // Ctrl+R
+	bindAction(83, true, 'save'); // Ctrl+S
+	bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
+	bindAction(107, false, 'zoomIn'); // Add
+	bindAction(109, false, 'zoomOut'); // Subtract
+	bindAction(65, true, 'selectAll'); // Ctrl+A
+	bindAction(86, true, 'selectVertices', true); // Ctrl+Shift+V
+	bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
+	bindAction(69, true, 'export'); // Ctrl+Shift+E
+	bindAction(66, true, 'toBack'); // Ctrl+B
+	bindAction(70, true, 'toFront'); // Ctrl+F
+	bindAction(68, true, 'duplicate'); // Ctrl+D
+	bindAction(90, true, 'undo'); // Ctrl+Z
+	bindAction(89, true, 'redo'); // Ctrl+Y
+	bindAction(88, true, 'cut'); // Ctrl+X
+	bindAction(67, true, 'copy'); // Ctrl+C
+	bindAction(81, true, 'connect'); // Ctrl+Q
+	bindAction(86, true, 'paste'); // Ctrl+V
+	bindAction(71, true, 'group'); // Ctrl+G
+	bindAction(71, true, 'grid', true); // Ctrl+Shift+G
+	bindAction(85, true, 'ungroup'); // Ctrl+U
+	bindAction(112, false, 'about'); // F1
+	bindAction(80, true, 'publish', true); // Ctrl+Shift+P
+
+	return keyHandler;
+};
+
+/**
+ * Giuseppe Careri
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createMultiplicities = function(graph, cells, source,
+	target) {
+	for (var i = 0; i < cells.length; i++) {
+		graph.push(new mxMultiplicity(true, cells[i], null, null, source[i][0],
+			source[i][1], null, mxResources.get(cells[i]) + ' must have ' + source[i][0] + ' outgoing edge.', null));
+		graph.push(new mxMultiplicity(false, cells[i], null, null, target[i][0],
+			target[i][1], null, mxResources.get(cells[i]) + ' must have ' + target[i][0] + ' incoming edge.', null));
+	};
+};
diff --git a/public/assets/plugins/jscripty/js/Graph.js b/public/assets/plugins/jscripty/js/Graph.js
index 957c3a1..46f34e4 100644
--- a/public/assets/plugins/jscripty/js/Graph.js
+++ b/public/assets/plugins/jscripty/js/Graph.js
@@ -1 +1,748 @@
\ No newline at end of file
+/**
+ * $Id: Graph.js,v 1.14 2013-02-16 10:19:54 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new graph instance. Note that the constructor does not take a
+ * container because the graph instance is needed for creating the UI, which
+ * in turn will create the container for the graph. Hence, the container is
+ * assigned later in EditorUi.
+ */
+Graph = function(container, model, renderHint, stylesheet)
+{
+	mxGraph.call(this, container, model, renderHint, stylesheet);
+	
+	this.setConnectable(true);
+	this.setDropEnabled(false);
+	this.setPanning(true);
+	this.setTooltips(!mxClient.IS_TOUCH);
+	this.setAllowLoops(false);
+	this.setMultigraph(false);
+	this.allowAutoPanning = true;
+	this.resetEdgesOnConnect = false;
+	this.constrainChildren = false;
+	
+	// Centers the port icon on the target port
+    this.connectionHandler.targetConnectImage = true;
+
+    // Does not allow dangling edges
+    this.setAllowDanglingEdges(false);
+
+	// Enables cloning of connection sources
+	this.connectionHandler.setCreateTarget(false);
+	
+	// Disables built-in connection starts
+	this.connectionHandler.isValidSource = function()
+	{
+		return mxConnectionHandler.prototype.isValidSource.apply(this, arguments) && urlParams['connect'] != '2';
+	};
+
+	// Sets the style to be used when an elbow edge is double clicked
+	this.alternateEdgeStyle = 'vertical';
+
+	if (stylesheet == null)
+	{
+		this.loadStylesheet();
+	}
+	
+	// Creates rubberband selection
+    var rubberband = new mxRubberband(this);
+    
+    this.getRubberband = function()
+    {
+    	return rubberband;
+    };
+    
+    // Shows hand cursor while panning
+	this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function()
+	{
+		this.container.style.cursor = 'pointer';
+	}));
+			
+	this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function()
+	{
+		this.container.style.cursor = 'default';
+	}));
+
+    // Adds support for HTML labels via style. Note: Currently, only the Java
+    // backend supports HTML labels but CSS support is limited to the following:
+    // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
+	this.isHtmlLabel = function(cell)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		return style['html'] == '1' || style['whiteSpace'] == 'wrap';
+	};
+	
+	// HTML entities are displayed as plain text in wrapped plain text labels
+	this.cellRenderer.getLabelValue = function(state)
+	{
+		var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);
+		
+		if (state.style['whiteSpace'] == 'wrap' && state.style['html'] != 1)
+		{
+			result = mxUtils.htmlEntities(result, false);
+		}
+		
+		return result;
+	};
+	
+	// Unlocks all cells
+	this.isCellLocked = function(cell)
+	{
+		return false;
+	};
+
+	// Tap and hold brings up context menu.
+	// Tolerance slightly below graph tolerance is better.
+	this.connectionHandler.tapAndHoldTolerance = 16;
+	
+	//  Tap and hold on background starts rubberband on cell starts connecting
+	var connectionHandlerTapAndHold = this.connectionHandler.tapAndHold;
+	this.connectionHandler.tapAndHold = function(me, state)
+	{
+		if (state == null)
+		{
+			if (!this.graph.panningHandler.active)
+			{
+				rubberband.start(me.getGraphX(), me.getGraphY());
+				this.graph.panningHandler.panningTrigger = false;
+			}
+		}
+		else if (tapAndHoldStartsConnection)
+		{
+			connectionHandlerTapAndHold.apply(this, arguments);	
+		}
+		else if (this.graph.isCellSelected(state.cell) && this.graph.getSelectionCount() > 1)
+		{
+			this.graph.removeSelectionCell(state.cell);
+		}
+	};
+
+	if (touchStyle)
+	{
+		this.initTouch();
+	}
+};
+
+// Graph inherits from mxGraph
+mxUtils.extend(Graph, mxGraph);
+
+/**
+ * Allows to all values in fit.
+ */
+Graph.prototype.minFitScale = null;
+
+/**
+ * Allows to all values in fit.
+ */
+Graph.prototype.maxFitScale = null;
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+Graph.prototype.loadStylesheet = function()
+{
+    var node = mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement();
+	var dec = new mxCodec(node.ownerDocument);
+	dec.decode(node, this.getStylesheet());
+};
+
+/**
+ * Inverts the elbow edge style without removing existing styles.
+ */
+Graph.prototype.flipEdge = function(edge)
+{
+	if (edge != null)
+	{
+		var state = this.view.getState(edge);
+		var style = (state != null) ? state.style : this.getCellStyle(edge);
+		
+		if (style != null)
+		{
+			var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+				mxConstants.ELBOW_HORIZONTAL);
+			var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+				mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+			this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+		}
+	}
+};
+
+/**
+ * Sets the default edge for future connections.
+ */
+Graph.prototype.setDefaultEdge = function(cell)
+{
+	if (cell != null && this.getModel().isEdge(cell))
+	{
+		// Take a snapshot of the cell at the moment of calling
+		var proto = this.getModel().cloneCells([cell])[0];
+		
+		// Delete existing points
+		if (proto.geometry != null)
+		{
+			proto.geometry.points = null;
+		}
+		
+		// Delete entry-/exitXY styles
+		var style = proto.getStyle();
+		style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_X, null);
+		style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_Y, null);
+		style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_X, null);
+		style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_Y, null);
+		proto.setStyle(style);
+		
+		// Uses edge template for connect preview
+		this.connectionHandler.createEdgeState = function(me)
+		{
+    		return this.graph.view.createState(proto);
+	    };
+
+	    // Creates new connections from edge template
+	    this.connectionHandler.factoryMethod = function()
+	    {
+    		return this.graph.cloneCells([proto])[0];
+	    };
+	}
+};
+
+/**
+ * Disables folding for non-swimlanes.
+ */
+Graph.prototype.isCellFoldable = function(cell)
+{
+	return this.foldingEnabled && this.isSwimlane(cell);
+};
+
+/**
+ * Disables drill-down for non-swimlanes.
+ */
+Graph.prototype.isValidRoot = function(cell)
+{
+	return this.isSwimlane(cell);
+};
+
+/**
+ * Overrides createGroupCell to set the group style for new groups to 'group'.
+ */
+Graph.prototype.createGroupCell = function()
+{
+	var group = mxGraph.prototype.createGroupCell.apply(this, arguments);
+	group.setStyle('group');
+	
+	return group;
+};
+
+/**
+ * Giuseppe Careri
+ * Overrides tooltips to show position and size
+ */
+Graph.prototype.getTooltipForCell = function(cell)
+{
+	var tip = '';
+	
+	if (this.getModel().isVertex(cell))
+	{
+		// var geo = this.getCellGeometry(cell);
+		
+		// var f2 = function(x)
+		// {
+		// 	return Math.round(parseFloat(x) * 100) / 100;
+		// };
+		
+		// if (geo != null)
+		// {
+		// 	if (tip == null)
+		// 	{
+		// 		tip = '';
+		// 	}
+		// 	else if (tip.length > 0)
+		// 	{
+		// 		tip += '\n';
+		// 	}
+			
+		// 	tip += 'X: ' + f2(geo.x) + '\nY: ' + f2(geo.y) + '\nW: ' + f2(geo.width) + '\nH: ' + f2(geo.height);
+		// }
+		
+		tip += mxResources.get('description_' + cell.value.nodeName);
+	}
+	else if (this.getModel().isEdge(cell))
+	{
+		tip = mxGraph.prototype.getTooltipForCell.apply(this, arguments);
+	}
+	
+	return tip;
+};
+
+/**
+ * Giuseppe Careri
+ * Returns the label for the given cell.
+ */
+Graph.prototype.convertValueToString = function(cell)
+{
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		return cell.value.getAttribute('label');
+	}
+	
+	return mxGraph.prototype.convertValueToString.apply(this, arguments);
+};
+
+/**
+ * Giuseppe Careri
+ * Handles label changes for XML user objects.
+ */
+Graph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		var tmp = cell.value.cloneNode(true);
+		tmp.setAttribute('label', value);
+		value = tmp;
+	}
+	
+	mxGraph.prototype.cellLabelChanged.apply(this, arguments);
+};
+
+/**
+ * Sets the link for the given cell.
+ */
+Graph.prototype.setLinkForCell = function(cell, link)
+{
+	var value = null;
+	
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		value = cell.value.cloneNode(true);
+	}
+	else
+	{
+		var doc = mxUtils.createXmlDocument();
+		
+		value = doc.createElement('UserObject');
+		value.setAttribute('label', cell.value);
+	}
+	
+	if (link != null && link.length > 0)
+	{
+		value.setAttribute('link', link);
+	}
+	else
+	{
+		value.removeAttribute('link');
+	}
+	
+	this.model.setValue(cell, value);
+};
+
+/**
+ * Returns the link for the given cell.
+ */
+Graph.prototype.getLinkForCell = function(cell)
+{
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		return cell.value.getAttribute('link');
+	}
+	
+	return null;
+};
+
+/**
+ * Customized graph for touch devices.
+ */
+Graph.prototype.initTouch = function()
+{
+	// Disables new connections via "hotspot"
+	this.connectionHandler.marker.isEnabled = function()
+	{
+		return this.graph.connectionHandler.first != null;
+	};
+
+	// Hides menu when editing starts
+	this.addListener(mxEvent.START_EDITING, function(sender, evt)
+	{
+		this.panningHandler.hideMenu();
+	});
+
+	// Context menu for touchstyle
+	var showMenu = false;
+	var menuCell = null;
+
+	// Checks if native hit detection did not return anything and does custom
+	// hit detection for edges to take into account the tolerance
+	this.updateMouseEvent = function(me)
+	{
+		mxGraph.prototype.updateMouseEvent.apply(this, arguments);
+
+		if (me.getState() == null)
+		{
+			var cell = this.getCellAt(me.graphX, me.graphY);
+			
+			if (this.getModel().isEdge(cell))
+			{
+				me.state = this.view.getState(cell);
+				
+				if (me.state != null && me.state.shape != null)
+				{
+					this.container.style.cursor = me.state.shape.node.style.cursor;
+				}
+			}
+		}
+		
+		if (me.getState() == null)
+		{
+			this.container.style.cursor = 'default';
+		}
+	};
+	
+	// Handles popup menu on touch devices (tap selected cell)
+	this.fireMouseEvent = function(evtName, me, sender)
+	{
+		if (evtName == mxEvent.MOUSE_DOWN)
+		{
+			if (!this.panningHandler.isMenuShowing())
+			{
+				menuCell = me.getCell();
+				showMenu = (menuCell != null) ? this.isCellSelected(menuCell) : this.isSelectionEmpty();
+			}
+			else
+			{
+				showMenu = false;
+				menuCell = null;
+			}
+		}
+		else if (evtName == mxEvent.MOUSE_UP)
+		{
+			if (showMenu && !this.isEditing())
+			{
+				if (!this.panningHandler.isMenuShowing())
+				{
+					var x = mxEvent.getClientX(me.getEvent());
+					var y = mxEvent.getClientY(me.getEvent());
+					
+					this.panningHandler.popup(x + 16, y, menuCell, me.getEvent());
+				}
+				
+				showMenu = false;
+				menuCell = null;
+				me.consume();
+				
+				return;
+			}
+			
+			showMenu = false;
+			menuCell = null;
+		}
+
+		mxGraph.prototype.fireMouseEvent.apply(this, arguments);
+
+		if (evtName == mxEvent.MOUSE_MOVE && me.isConsumed())
+		{
+			showMenu = false;
+			menuCell = null;
+		}
+	};
+};
+
+/**
+ * Implements touch devices.
+ */
+(function()
+{
+	// Enables rotation handle
+	mxVertexHandler.prototype.rotationEnabled = true;
+	
+	// Matches label positions of mxGraph 1.x
+	mxText.prototype.baseSpacingTop = 5;
+	mxText.prototype.baseSpacingBottom = 1;
+
+	// Touch-specific static overrides
+	if (touchStyle)
+	{
+		// Sets constants for touch style
+		mxConstants.HANDLE_SIZE = 16;
+		mxConstants.LABEL_HANDLE_SIZE = 7;
+		
+		// Larger tolerance and grid for real touch devices
+		if (mxClient.IS_TOUCH)
+		{
+			mxVertexHandler.prototype.tolerance = 4;
+			mxEdgeHandler.prototype.tolerance = 6;
+			Graph.prototype.tolerance = 14;
+			Graph.prototype.gridSize = 20;
+			
+			// One finger pans (no rubberband selection) must start regardless of mouse button
+			mxPanningHandler.prototype.selectOnPopup = false;
+			mxPanningHandler.prototype.useLeftButtonForPanning = true;
+			mxPanningHandler.prototype.isPanningTrigger = function(me)
+			{
+				var evt = me.getEvent();
+			 	
+			 	return (this.useLeftButtonForPanning && (this.ignoreCell || me.getState() == null)/* &&
+			 			mxEvent.isLeftMouseButton(evt)*/) || (mxEvent.isControlDown(evt) &&
+			 			mxEvent.isShiftDown(evt)) || (this.usePopupTrigger &&
+			 		   	mxEvent.isPopupTrigger(evt));
+			};
+		}
+		
+		// Don't clear selection if multiple cells selected
+		var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
+		mxGraphHandler.prototype.mouseDown = function(sender, me)
+		{
+			graphHandlerMouseDown.apply(this, arguments);
+
+			if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1)
+			{
+				this.delayedSelection = false;
+			}
+		};
+
+		// Changes order of panninghandler
+		Graph.prototype.createHandlers = function(container)
+		{
+			this.tooltipHandler = new mxTooltipHandler(this);
+			this.tooltipHandler.setEnabled(false);
+			// Selection cells first
+			this.selectionCellsHandler = new mxSelectionCellsHandler(this);
+			this.panningHandler = new mxPanningHandler(this);
+			this.panningHandler.panningEnabled = false;
+			this.connectionHandler = new mxConnectionHandler(this);
+			this.connectionHandler.setEnabled(false);
+			this.graphHandler = new mxGraphHandler(this);
+		};
+
+		// On connect the target is selected and we clone the cell of the preview edge for insert
+		mxConnectionHandler.prototype.selectCells = function(edge, target)
+		{
+			if (touchStyle && target != null)
+			{
+				this.graph.setSelectionCell(target);
+			}
+			else
+			{
+				this.graph.setSelectionCell(edge);
+			}
+		};
+
+		// Overrides double click handling to use the tolerance
+		// FIXME: Double click on edges in iPad needs focus on textarea
+		var graphDblClick = mxGraph.prototype.dblClick;
+		Graph.prototype.dblClick = function(evt, cell)
+		{
+			if (cell == null)
+			{
+				var pt = mxUtils.convertPoint(this.container,
+					mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+				cell = this.getCellAt(pt.x, pt.y);
+			}
+
+			graphDblClick.call(this, evt, cell);
+		};
+
+		// Rounded edge and vertex handles
+		var touchHandle = new mxImage(IMAGE_PATH + '/touch-handle.png', 16, 16);
+		mxVertexHandler.prototype.handleImage = touchHandle;
+		mxEdgeHandler.prototype.handleImage = touchHandle;
+		mxOutline.prototype.sizerImage = touchHandle;
+		
+		// Pre-fetches touch handle
+		new Image().src = touchHandle.src;
+
+		// Adds connect icon to selected vertices
+		var connectorSrc = IMAGE_PATH + '/touch-connector.png';
+		
+		var vertexHandlerInit = mxVertexHandler.prototype.init;
+		mxVertexHandler.prototype.init = function()
+		{
+			vertexHandlerInit.apply(this, arguments);
+
+			// Only show connector image on one cell and do not show on containers
+			if (showConnectorImg && this.graph.connectionHandler.isEnabled() &&
+				this.graph.isCellConnectable(this.state.cell) &&
+				!this.graph.isValidRoot(this.state.cell) &&
+				this.graph.getSelectionCount() == 1)
+			{
+				this.connectorImg = mxUtils.createImage(connectorSrc);
+				this.connectorImg.style.cursor = 'pointer';
+				this.connectorImg.style.width = '29px';
+				this.connectorImg.style.height = '29px';
+				this.connectorImg.style.position = 'absolute';
+				
+				if (!mxClient.IS_TOUCH)
+				{
+					this.connectorImg.setAttribute('title', mxResources.get('connect'));
+					mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
+				}
+
+				// Adds 2px tolerance
+				this.connectorImg.style.padding = '2px';
+				
+				// Starts connecting on touch/mouse down
+				mxEvent.addGestureListeners(this.connectorImg,
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.panningHandler.hideMenu();
+						var pt = mxUtils.convertPoint(this.graph.container,
+								mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+						this.graph.connectionHandler.start(this.state, pt.x, pt.y);
+						this.graph.isMouseDown = true;
+						mxEvent.consume(evt);
+					})
+				);
+
+				this.graph.container.appendChild(this.connectorImg);
+			}
+
+			this.redrawTools();
+		};
+		
+		var vertexHandlerRedraw = mxVertexHandler.prototype.redraw;
+		mxVertexHandler.prototype.redraw = function()
+		{
+			vertexHandlerRedraw.apply(this);
+			this.redrawTools();
+		};
+		
+		mxVertexHandler.prototype.redrawTools = function()
+		{
+			if (this.state != null && this.connectorImg != null)
+			{
+				// Top right for single-sizer
+				if (mxVertexHandler.prototype.singleSizer)
+				{
+					this.connectorImg.style.left = (this.state.x + this.state.width - this.connectorImg.offsetWidth / 2) + 'px';
+					this.connectorImg.style.top = (this.state.y - this.connectorImg.offsetHeight / 2) + 'px';
+				}
+				else
+				{
+					this.connectorImg.style.left = (this.state.x + this.state.width + mxConstants.HANDLE_SIZE / 2 + 4/* - 2 padding*/) + 'px';
+					this.connectorImg.style.top = (this.state.y + (this.state.height - this.connectorImg.offsetHeight) / 2) + 'px';
+				}
+			}
+		};
+		
+		var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
+		mxVertexHandler.prototype.destroy = function(sender, me)
+		{
+			vertexHandlerDestroy.apply(this, arguments);
+
+			if (this.connectorImg != null)
+			{
+				this.connectorImg.parentNode.removeChild(this.connectorImg);
+				this.connectorImg = null;
+			}
+		};
+		
+		// Pre-fetches touch connector
+		new Image().src = connectorSrc;
+	}
+	else
+	{
+		var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15);
+		mxConnectionHandler.prototype.connectImage = img;
+
+		// Pre-fetches img
+		new Image().src = img.src;
+		
+		if (urlParams['connect'] == '2') // not touchStyle
+		{
+			var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15);
+					
+			var vertexHandlerInit = mxVertexHandler.prototype.init;
+			mxVertexHandler.prototype.init = function()
+			{
+				vertexHandlerInit.apply(this, arguments);
+	
+				// Only show connector image on one cell and do not show on containers
+				if (showConnectorImg && this.graph.connectionHandler.isEnabled() &&
+					this.graph.isCellConnectable(this.state.cell) &&
+					!this.graph.isValidRoot(this.state.cell) &&
+					this.graph.getSelectionCount() == 1)
+				{
+					// Workaround for event redirection via image tag in quirks and IE8
+					if (mxClient.IS_IE && !mxClient.IS_SVG)
+					{
+						this.connectorImg = document.createElement('div');
+						this.connectorImg.style.backgroundImage = 'url(' + img.src + ')';
+						this.connectorImg.style.backgroundPosition = 'center';
+						this.connectorImg.style.backgroundRepeat = 'no-repeat';
+						this.connectorImg.style.width = (img.width + 4) + 'px';
+						this.connectorImg.style.height = (img.height + 4) + 'px';
+						this.connectorImg.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+					}
+					else
+					{
+						this.connectorImg = mxUtils.createImage(img.src);
+						this.connectorImg.style.width = img.width + 'px';
+						this.connectorImg.style.height = img.height + 'px';
+					}
+					
+					this.connectorImg.style.cursor = 'pointer';
+					this.connectorImg.style.position = 'absolute';
+					this.connectorImg.setAttribute('title', mxResources.get('connect'));
+					mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
+					
+					// Adds 2px tolerance
+					this.connectorImg.style.padding = '2px';
+					
+					// Starts connecting on touch/mouse down
+					mxEvent.addListener(this.connectorImg, 'mousedown',
+						mxUtils.bind(this, function(evt)
+						{
+							this.graph.panningHandler.hideMenu();
+							var pt = mxUtils.convertPoint(this.graph.container,
+									mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+							this.graph.connectionHandler.start(this.state, pt.x, pt.y);
+							this.graph.isMouseDown = true;
+							mxEvent.consume(evt);
+						})
+					);
+	
+					this.graph.container.appendChild(this.connectorImg);
+				}
+	
+				this.redrawTools();
+			};
+			
+			var vertexHandlerRedraw = mxVertexHandler.prototype.redraw;
+			mxVertexHandler.prototype.redraw = function()
+			{
+				vertexHandlerRedraw.apply(this);
+				this.redrawTools();
+			};
+			
+			mxVertexHandler.prototype.redrawTools = function()
+			{
+				if (this.state != null && this.connectorImg != null)
+				{
+					// Top right for single-sizer
+					if (mxVertexHandler.prototype.singleSizer)
+					{
+						this.connectorImg.style.left = (this.state.x + this.state.width - this.connectorImg.offsetWidth / 2) + 'px';
+						this.connectorImg.style.top = (this.state.y - this.connectorImg.offsetHeight / 2) + 'px';
+					}
+					else
+					{
+						this.connectorImg.style.left = (this.state.x + this.state.width + mxConstants.HANDLE_SIZE / 2 + 2/* - 2 padding*/) + 'px';
+						this.connectorImg.style.top = (this.state.y + (this.state.height - this.connectorImg.offsetHeight) / 2) + 'px';
+					}
+				}
+			};
+			
+			var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
+			mxVertexHandler.prototype.destroy = function(sender, me)
+			{
+				vertexHandlerDestroy.apply(this, arguments);
+	
+				if (this.connectorImg != null)
+				{
+					this.connectorImg.parentNode.removeChild(this.connectorImg);
+					this.connectorImg = null;
+				}
+			};
+		}
+	}
+})();
diff --git a/public/assets/plugins/jscripty/js/Menus.js b/public/assets/plugins/jscripty/js/Menus.js
index 0188340..7d33b9e 100644
--- a/public/assets/plugins/jscripty/js/Menus.js
+++ b/public/assets/plugins/jscripty/js/Menus.js
@@ -1 +1,716 @@
\ No newline at end of file
+/**
+ * $Id: Menus.js,v 1.13 2013-02-20 16:21:29 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new graph editor
+ */
+Menus = function(editorUi) {
+	this.editorUi = editorUi;
+	this.menus = new Object();
+	this.init();
+
+	// Pre-fetches checkmark image
+	new Image().src = IMAGE_PATH + '/checkmark.gif';
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.init = function() {
+	var graph = this.editorUi.editor.graph;
+
+	this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var fonts = ['Helvetica', 'Verdana', 'Times New Roman', 'Garamond',
+			'Comic Sans MS',
+			'Courier New', 'Georgia', 'Lucida Console', 'Tahoma'
+		];
+
+		for (var i = 0; i < fonts.length; i++) {
+			var tr = this.styleChange(menu, fonts[i], [mxConstants.STYLE_FONTFAMILY], [
+				fonts[i]
+			], null, parent);
+			tr.firstChild.nextSibling.style.fontFamily = fonts[i];
+		}
+
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('custom'), '', mxConstants.DEFAULT_FONTFAMILY,
+			mxConstants.STYLE_FONTFAMILY, parent);
+	})));
+	this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];
+
+		for (var i = 0; i < sizes.length; i++) {
+			this.styleChange(menu, sizes[i], [mxConstants.STYLE_FONTSIZE], [sizes[i]],
+				null, parent);
+		}
+
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('custom'), '(pt)', '12',
+			mxConstants.STYLE_FONTSIZE, parent);
+	})));
+	this.put('linewidth', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var sizes = [1, 2, 3, 4, 8, 12, 16, 24];
+
+		for (var i = 0; i < sizes.length; i++) {
+			this.styleChange(menu, sizes[i] + 'px', [mxConstants.STYLE_STROKEWIDTH], [
+				sizes[i]
+			], null, parent);
+		}
+
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('custom'), '(px)', '1',
+			mxConstants.STYLE_STROKEWIDTH, parent);
+	})));
+	this.put('line', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('straight'), [mxConstants.STYLE_EDGE], [
+			null
+		], null, parent);
+		this.styleChange(menu, mxResources.get('entityRelation'), [mxConstants.STYLE_EDGE], [
+			'entityRelationEdgeStyle'
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('horizontal'), [mxConstants.STYLE_EDGE,
+			mxConstants.STYLE_ELBOW
+		], ['elbowEdgeStyle', 'horizontal'], null, parent);
+		this.styleChange(menu, mxResources.get('vertical'), [mxConstants.STYLE_EDGE,
+			mxConstants.STYLE_ELBOW
+		], ['elbowEdgeStyle', 'vertical'], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('manual'), [mxConstants.STYLE_EDGE], [
+			'segmentEdgeStyle'
+		], null, parent);
+		this.styleChange(menu, mxResources.get('automatic'), [mxConstants.STYLE_EDGE], [
+			'orthogonalEdgeStyle'
+		], null, parent);
+	})));
+	this.put('lineend', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('classic'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_CLASSIC
+		], null, parent);
+		this.styleChange(menu, mxResources.get('openArrow'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_OPEN
+		], null, parent);
+		this.styleChange(menu, mxResources.get('block'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_BLOCK
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('oval'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_OVAL
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamond'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_DIAMOND
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamondThin'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_DIAMOND_THIN
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('none'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.NONE
+		], null, parent);
+		menu.addSeparator(parent);
+		menu.addItem(mxResources.get('transparent'), null, function() {
+			graph.toggleCellStyles('endFill', true);
+		}, parent, null, true);
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('size'), '(px)', mxConstants.DEFAULT_MARKERSIZE,
+			mxConstants.STYLE_ENDSIZE, parent);
+	})));
+	this.put('linestart', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('classic'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_CLASSIC
+		], null, parent);
+		this.styleChange(menu, mxResources.get('openArrow'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_OPEN
+		], null, parent);
+		this.styleChange(menu, mxResources.get('block'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_BLOCK
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('oval'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_OVAL
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamond'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_DIAMOND
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamondThin'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_DIAMOND_THIN
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('none'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.NONE
+		], null, parent);
+		menu.addSeparator(parent);
+		menu.addItem(mxResources.get('transparent'), null, function() {
+			graph.toggleCellStyles('startFill', true);
+		}, parent, null, true);
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('size'), '(px)', mxConstants.DEFAULT_MARKERSIZE,
+			mxConstants.STYLE_STARTSIZE, parent);
+	})));
+	this.put('spacing', new Menu(mxUtils.bind(this, function(menu, parent) {
+		// Uses shadow action and line menu to analyze selection
+		var vertexSelected = this.editorUi.actions.get('shadow').enabled;
+		var edgeSelected = this.get('line').enabled;
+
+		if (vertexSelected || menu.showDisabled) {
+			this.promptChange(menu, mxResources.get('top'), '(px)', '0',
+				mxConstants.STYLE_SPACING_TOP, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('right'), '(px)', '0',
+				mxConstants.STYLE_SPACING_RIGHT, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('bottom'), '(px)', '0',
+				mxConstants.STYLE_SPACING_BOTTOM, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('left'), '(px)', '0',
+				mxConstants.STYLE_SPACING_LEFT, parent, vertexSelected);
+			menu.addSeparator(parent);
+			this.promptChange(menu, mxResources.get('global'), '(px)', '0',
+				mxConstants.STYLE_SPACING, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('perimeter'), '(px)', '0',
+				mxConstants.STYLE_PERIMETER_SPACING, parent, vertexSelected);
+		}
+
+		if (edgeSelected || menu.showDisabled) {
+			menu.addSeparator(parent);
+			this.promptChange(menu, mxResources.get('sourceSpacing'), '(px)', '0',
+				mxConstants.STYLE_SOURCE_PERIMETER_SPACING, parent, edgeSelected);
+			this.promptChange(menu, mxResources.get('targetSpacing'), '(px)', '0',
+				mxConstants.STYLE_TARGET_PERIMETER_SPACING, parent, edgeSelected);
+		}
+	})));
+	this.put('format', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItem(menu, 'style', parent);
+	})));
+	this.put('text', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var enabled = this.get('text').enabled;
+		menu.addSeparator(parent);
+		this.addSubmenu('fontFamily', menu, parent);
+		this.addSubmenu('fontSize', menu, parent);
+		this.addMenuItems(menu, ['-', 'bold', 'italic', 'underline', '-'],
+			parent);
+		this.addSubmenu('alignment', menu, parent);
+		this.addMenuItem(menu, 'wordWrap', parent);
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('textOpacity'), '(%)', '100',
+			mxConstants.STYLE_TEXT_OPACITY, parent, enabled);
+		menu.addItem(mxResources.get('hide'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_NOLABEL, false);
+		}, parent, null, enabled);
+	})));
+	this.put('alignment', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('leftAlign'), [mxConstants.STYLE_ALIGN], [
+			mxConstants.ALIGN_LEFT
+		], null, parent);
+		this.styleChange(menu, mxResources.get('center'), [mxConstants.STYLE_ALIGN], [
+			mxConstants.ALIGN_CENTER
+		], null, parent);
+		this.styleChange(menu, mxResources.get('rightAlign'), [mxConstants.STYLE_ALIGN], [
+			mxConstants.ALIGN_RIGHT
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('topAlign'), [mxConstants.STYLE_VERTICAL_ALIGN], [
+			mxConstants.ALIGN_TOP
+		], null, parent);
+		this.styleChange(menu, mxResources.get('middle'), [mxConstants.STYLE_VERTICAL_ALIGN], [
+			mxConstants.ALIGN_MIDDLE
+		], null, parent);
+		this.styleChange(menu, mxResources.get('bottomAlign'), [mxConstants.STYLE_VERTICAL_ALIGN], [
+			mxConstants.ALIGN_BOTTOM
+		], null, parent);
+		menu.addSeparator(parent);
+		this.addSubmenu('position', menu, parent);
+		this.addSubmenu('spacing', menu, parent);
+		menu.addSeparator(parent);
+		var enabled = this.get('text').enabled;
+		menu.addItem(mxResources.get('vertical'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_HORIZONTAL, true);
+		}, parent, null, enabled);
+	})));
+	this.put('position', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('left'), [mxConstants.STYLE_LABEL_POSITION,
+			mxConstants.STYLE_ALIGN
+		], [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_RIGHT], null, parent);
+		this.styleChange(menu, mxResources.get('center'), [mxConstants.STYLE_LABEL_POSITION,
+			mxConstants.STYLE_ALIGN
+		], [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_CENTER], null, parent);
+		this.styleChange(menu, mxResources.get('right'), [mxConstants.STYLE_LABEL_POSITION,
+			mxConstants.STYLE_ALIGN
+		], [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_LEFT], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('top'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+			mxConstants.STYLE_VERTICAL_ALIGN
+		], [mxConstants.ALIGN_TOP, mxConstants.ALIGN_BOTTOM], null, parent);
+		this.styleChange(menu, mxResources.get('middle'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+			mxConstants.STYLE_VERTICAL_ALIGN
+		], [mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_MIDDLE], null, parent);
+		this.styleChange(menu, mxResources.get('bottom'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+			mxConstants.STYLE_VERTICAL_ALIGN
+		], [mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_TOP], null, parent);
+	})));
+	this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent) {
+		menu.addItem(mxResources.get('flipH'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false);
+		}, parent);
+		menu.addItem(mxResources.get('flipV'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false);
+		}, parent);
+		this.addMenuItems(menu, ['-', 'tilt', 'rotation'], parent);
+	})));
+	this.put('align', new Menu(mxUtils.bind(this, function(menu, parent) {
+		menu.addItem(mxResources.get('leftAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_LEFT);
+		}, parent);
+		menu.addItem(mxResources.get('center'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_CENTER);
+		}, parent);
+		menu.addItem(mxResources.get('rightAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_RIGHT);
+		}, parent);
+		menu.addSeparator(parent);
+		menu.addItem(mxResources.get('topAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_TOP);
+		}, parent);
+		menu.addItem(mxResources.get('middle'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_MIDDLE);
+		}, parent);
+		menu.addItem(mxResources.get('bottomAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_BOTTOM);
+		}, parent);
+	})));
+	this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent) {
+		// menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
+		// {
+		// 	if (!graph.isSelectionEmpty())
+		// 	{
+		// 		var layout = new mxCompactTreeLayout(graph, true);
+		// 		layout.edgeRouting = false;
+		// 		layout.levelDistance = 30;
+		//    		this.editorUi.executeLayout(layout, true, true);
+		// 	}
+		// }), parent);
+		// menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
+		// {
+		// 	if (!graph.isSelectionEmpty())
+		// 	{
+		// 		var layout = new mxCompactTreeLayout(graph, false);
+		// 		layout.edgeRouting = false;
+		// 		layout.levelDistance = 30;
+		//    		this.editorUi.executeLayout(layout, true, true);
+		// 	}
+		// }), parent);
+		// menu.addSeparator(parent);
+		menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this,
+			function() {
+				var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
+				this.editorUi.executeLayout(layout, true, true);
+			}), parent);
+		menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this,
+			function() {
+				var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
+				this.editorUi.executeLayout(layout, true, true);
+			}), parent);
+		//menu.addSeparator(parent);
+		// menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
+		// {
+		// 	var layout = new mxFastOrganicLayout(graph);
+		//   		this.editorUi.executeLayout(layout, true, true);
+		// }), parent);
+		// menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
+		// {
+		// 	var layout = new mxCircleLayout(graph);
+		//   		this.editorUi.executeLayout(layout, true, true, graph.getSelectionCells());
+		// }), parent);
+	})));
+	this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-',
+			'expand', 'collapse'
+		], parent);
+	})));
+	this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['toFront', 'toBack', '-'], parent);
+		//this.addSubmenu('direction', menu, parent);
+		this.addSubmenu('layout', menu, parent);
+		//this.addSubmenu('align', menu, parent);
+		menu.addSeparator(parent);
+		//this.addSubmenu('navigation', menu, parent);
+		this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup'],
+			parent);
+		//this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'autosize'], parent);
+	})));
+	this.put('view', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['actualSize'], parent);
+		menu.addSeparator();
+		var scales = [0.25, 0.5, 0.75, 1, 2, 4];
+
+		for (var i = 0; i < scales.length; i++) {
+			(function(scale) {
+				menu.addItem((scale * 100) + '%', null, function() {
+					graph.zoomTo(scale);
+				}, parent);
+			})(scales[i]);
+		}
+
+		this.addMenuItems(menu, ['-', 'zoomIn', 'zoomOut', '-', 'fitWindow',
+			'customZoom', '-', 'fitPage', 'fitPageWidth'
+		], parent);
+	})));
+	this.put('file', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', 'publish',
+			'-', 'variable', '-', 'import', 'export', '-', 'editFile', 'rename', '-',
+			'print'
+		], parent);
+	})));
+	this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste',
+			'delete', '-', 'duplicate', '-',
+			'selectVertices', 'selectEdges', 'selectAll'
+		]);
+	})));
+	this.put('help', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['help', '-', 'about']);
+	})));
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.put = function(name, menu) {
+	this.menus[name] = menu;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.get = function(name) {
+	return this.menus[name];
+};
+
+/**
+ * Adds the given submenu.
+ */
+Menus.prototype.addSubmenu = function(name, menu, parent) {
+	var enabled = this.get(name).enabled;
+
+	if (menu.showDisabled || enabled) {
+		var submenu = menu.addItem(mxResources.get(name), null, null, parent, null,
+			enabled);
+		this.addMenu(name, menu, submenu);
+	}
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.addMenu = function(name, popupMenu, parent) {
+	var menu = this.get(name);
+
+	if (menu != null && (popupMenu.showDisabled || menu.enabled)) {
+		this.get(name).execute(popupMenu, parent);
+	}
+};
+
+/**
+ * Adds a style change item to the given menu.
+ */
+Menus.prototype.styleChange = function(menu, label, keys, values, sprite,
+	parent) {
+	return menu.addItem(label, null, mxUtils.bind(this, function() {
+		var graph = this.editorUi.editor.graph;
+
+		graph.getModel().beginUpdate();
+		try {
+			for (var i = 0; i < keys.length; i++) {
+				graph.setCellStyles(keys[i], values[i]);
+			}
+		} finally {
+			graph.getModel().endUpdate();
+		}
+	}), parent, sprite);
+};
+
+/**
+ * Adds a style change item with a prompt to the given menu.
+ */
+Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key,
+	parent, enabled) {
+	return menu.addItem(label, null, mxUtils.bind(this, function() {
+		var graph = this.editorUi.editor.graph;
+		var value = defaultValue;
+		var state = graph.getView().getState(graph.getSelectionCell());
+
+		if (state != null) {
+			value = state.style[key] || value;
+		}
+
+		value = mxUtils.prompt(mxResources.get('enterValue') + ((hint.length > 0) ?
+			(' ' + hint) : ''), value);
+
+		if (value != null && value.length > 0) {
+			graph.setCellStyles(key, value);
+		}
+	}), parent, null, enabled);
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.addMenuItem = function(menu, key, parent) {
+	var action = this.editorUi.actions.get(key);
+
+	if (action != null && (menu.showDisabled || action.enabled)) {
+		var item = menu.addItem(action.label, null, action.funct, parent, null,
+			action.enabled);
+
+		// Adds checkmark image
+		if (action.toggleAction && action.isSelected()) {
+			this.addCheckmark(item);
+		}
+
+		this.addShortcut(item, action);
+
+		return item;
+	}
+
+	return null;
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+Menus.prototype.addShortcut = function(item, action) {
+	if (action.shortcut != null) {
+		var td = item.firstChild.nextSibling.nextSibling;
+		var span = document.createElement('span');
+		span.style.color = 'gray';
+		mxUtils.write(span, action.shortcut);
+		td.appendChild(span);
+	}
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+Menus.prototype.addCheckmark = function(item) {
+	var td = item.firstChild.nextSibling;
+	td.style.backgroundImage = 'url(' + IMAGE_PATH + '/checkmark.gif)';
+	td.style.backgroundRepeat = 'no-repeat';
+	td.style.backgroundPosition = '2px 50%';
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.addMenuItems = function(menu, keys, parent) {
+	for (var i = 0; i < keys.length; i++) {
+		if (keys[i] == '-') {
+			menu.addSeparator(parent);
+		} else {
+			this.addMenuItem(menu, keys[i], parent);
+		}
+	}
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.createPopupMenu = function(menu, cell, evt) {
+	var graph = this.editorUi.editor.graph;
+	menu.smartSeparators = true;
+
+	if (graph.isSelectionEmpty()) {
+		this.addMenuItems(menu, ['undo', 'redo', '-', 'paste', '-']);
+	} else {
+		this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', '-', 'duplicate']);
+
+		if (graph.getSelectionCount() == 1 && graph.getModel().isEdge(graph.getSelectionCell())) {
+			this.addMenuItems(menu, ['setAsDefaultEdge']);
+		}
+
+		menu.addSeparator();
+	}
+
+	if (graph.getSelectionCount() > 0) {
+		this.addMenuItems(menu, ['toFront', 'toBack', '-']);
+
+		if (graph.getModel().isEdge(graph.getSelectionCell())) {
+			var isWaypoint = false;
+			var cell = graph.getSelectionCell();
+
+			if (cell != null && graph.getModel().isEdge(cell)) {
+				var handler = graph.selectionCellsHandler.getHandler(cell);
+
+				if (handler instanceof mxEdgeHandler && handler.bends != null && handler.bends
+					.length > 2) {
+					var index = handler.getHandleForEvent(new mxMouseEvent(evt));
+
+					// Configures removeWaypoint action before execution
+					var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
+					rmWaypointAction.handler = handler;
+					rmWaypointAction.index = index;
+
+					isWaypoint = index > 0 && index < handler.bends.length;
+				}
+			}
+
+			this.addMenuItems(menu, ['-', (isWaypoint) ? 'removeWaypoint' :
+				'addWaypoint'
+			]);
+		} else if (graph.getSelectionCount() > 1) {
+			menu.addSeparator();
+			this.addMenuItems(menu, ['group']);
+		}
+
+		menu.addSeparator();
+
+		if (graph.getSelectionCount() == 1) {
+			var link = graph.getLinkForCell(graph.getSelectionCell());
+
+			if (link != null) {
+				this.addMenuItems(menu, ['openLink']);
+			}
+		}
+	} else {
+		this.addMenuItems(menu, ['-', 'selectVertices', 'selectEdges', '-',
+			'selectAll'
+		]);
+	}
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.createMenubar = function(container) {
+	var menubar = new Menubar(this.editorUi, container);
+	// var menus = ['file', 'edit', 'view', 'format', 'text', 'arrange',
+	// 	'help'
+	// ];
+	var menus = ['file', 'edit', 'view', 'format', 'text', 'arrange'];
+	for (var i = 0; i < menus.length; i++) {
+		menubar.addMenu(mxResources.get(menus[i]), this.get(menus[i]).funct);
+	}
+
+	return menubar;
+};
+
+/**
+ * Construcs a new menubar for the given editor.
+ */
+function Menubar(editorUi, container) {
+	this.editorUi = editorUi;
+	this.container = container;
+
+	// Global handler to hide the current menu
+	mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
+		this.hideMenu();
+	}));
+};
+
+/**
+ * Adds the menubar elements.
+ */
+Menubar.prototype.hideMenu = function() {
+	if (this.currentMenu != null) {
+		this.currentMenu.hideMenu();
+	}
+};
+
+/**
+ * Adds a submenu to this menubar.
+ */
+Menubar.prototype.addMenu = function(label, funct) {
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geItem';
+	mxUtils.write(elt, label);
+
+	this.addMenuHandler(elt, funct);
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Adds a handler for showing a menu in the given element.
+ */
+Menubar.prototype.addMenuHandler = function(elt, funct) {
+	if (funct != null) {
+		var show = true;
+
+		var clickHandler = mxUtils.bind(this, function(evt) {
+			if (show && elt.enabled == null || elt.enabled) {
+				this.editorUi.editor.graph.panningHandler.hideMenu();
+				var menu = new mxPopupMenu(funct);
+				menu.div.className += ' geMenubarMenu';
+				menu.smartSeparators = true;
+				menu.showDisabled = true;
+				menu.autoExpand = true;
+
+				// Disables autoexpand and destroys menu when hidden
+				menu.hideMenu = mxUtils.bind(this, function() {
+					mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
+					menu.destroy();
+					this.currentMenu = null;
+					this.currentElt = null;
+				});
+
+				// Added width of the page-sidebar
+				var x = elt.offsetLeft + document.getElementById('page-sidebar').offsetWidth +
+					22;
+				// Added static height of the page-navbar
+				var y = elt.offsetTop + elt.offsetHeight + 140;
+
+				menu.popup(x, y, null, evt);
+
+				//menu.popup(elt.offsetLeft + 4, elt.offsetTop + elt.offsetHeight + 4, null, evt);
+				this.currentMenu = menu;
+				this.currentElt = elt;
+			}
+
+			show = true;
+			mxEvent.consume(evt);
+		});
+
+		// Shows menu automatically while in expanded state
+		mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt) {
+			if (this.currentMenu != null && this.currentElt != elt) {
+				this.hideMenu();
+				clickHandler(evt);
+			}
+		}));
+
+		// Hides menu if already showing
+		mxEvent.addListener(elt, 'mousedown', mxUtils.bind(this, function() {
+			show = this.currentElt != elt;
+		}));
+
+		mxEvent.addListener(elt, 'click', clickHandler);
+	}
+};
+
+/**
+ * Constructs a new action for the given parameters.
+ */
+function Menu(funct, enabled) {
+	mxEventSource.call(this);
+	this.funct = funct;
+	this.enabled = (enabled != null) ? enabled : true;
+};
+
+// Menu inherits from mxEventSource
+mxUtils.extend(Menu, mxEventSource);
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Menu.prototype.setEnabled = function(value) {
+	if (this.enabled != value) {
+		this.enabled = value;
+		this.fireEvent(new mxEventObject('stateChanged'));
+	}
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Menu.prototype.execute = function(menu, parent) {
+	this.funct(menu, parent);
+};
diff --git a/public/assets/plugins/jscripty/js/Shapes.js b/public/assets/plugins/jscripty/js/Shapes.js
index 4afc1cb..42e13ca 100644
--- a/public/assets/plugins/jscripty/js/Shapes.js
+++ b/public/assets/plugins/jscripty/js/Shapes.js
@@ -1 +1,1234 @@
\ No newline at end of file
+/**
+ * $Id: Shapes.js,v 1.13 2013-02-02 06:44:30 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+
+/**
+ * Registers shapes.
+ */
+(function()
+{
+	// Cube Shape, supports size style
+	function CubeShape() { };
+	CubeShape.prototype = new mxCylinder();
+	CubeShape.prototype.constructor = CubeShape;
+	CubeShape.prototype.size = 20;
+	CubeShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = Math.min(w, Math.min(h, mxUtils.getValue(this.style, 'size', this.size)));
+
+		if (isForeground)
+		{
+			path.moveTo(s, h);
+			path.lineTo(s, s);
+			path.lineTo(0, 0);
+			path.moveTo(s, s);
+			path.lineTo(w, s);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w - s, 0);
+			path.lineTo(w, s);
+			path.lineTo(w, h);
+			path.lineTo(s, h);
+			path.lineTo(0, h - s);
+			path.lineTo(0, 0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['cube'] = CubeShape;
+
+	// Note Shape, supports size style
+	function NoteShape() { };
+	NoteShape.prototype = new mxCylinder();
+	NoteShape.prototype.constructor = NoteShape;
+	NoteShape.prototype.size = 30;
+	NoteShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = Math.min(w, Math.min(h, mxUtils.getValue(this.style, 'size', this.size)));
+
+		if (isForeground)
+		{
+			path.moveTo(w - s, 0);
+			path.lineTo(w - s, s);
+			path.lineTo(w, s);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w - s, 0);
+			path.lineTo(w, s);
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.lineTo(0, 0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['note'] = NoteShape;
+
+	// Folder Shape, supports tabWidth, tabHeight styles
+	function FolderShape() { };
+	FolderShape.prototype = new mxCylinder();
+	FolderShape.prototype.constructor = FolderShape;
+	FolderShape.prototype.tabWidth = 60;
+	FolderShape.prototype.tabHeight = 20;
+	FolderShape.prototype.tabPosition = 'right';
+	FolderShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var tw = mxUtils.getValue(this.style, 'tabWidth', this.tabWidth);
+		var th = mxUtils.getValue(this.style, 'tabHeight', this.tabHeight);
+		var tp = mxUtils.getValue(this.style, 'tabPosition', this.tabPosition);
+		var dx = Math.min(w, tw);
+		var dy = Math.min(h, th);
+
+		if (isForeground)
+		{
+			if (tp == 'left')
+			{
+				path.moveTo(0, dy);
+				path.lineTo(dx, dy);
+			}
+			// Right is default
+			else
+			{
+				path.moveTo(w - dx, dy);
+				path.lineTo(w, dy);
+			}
+			
+			path.end();
+		}
+		else
+		{
+			if (tp == 'left')
+			{
+				path.moveTo(0, 0);
+				path.lineTo(dx, 0);
+				path.lineTo(dx, dy);
+				path.lineTo(w, dy);
+			}
+			// Right is default
+			else
+			{
+				path.moveTo(0, dy);
+				path.lineTo(w - dx, dy);
+				path.lineTo(w - dx, 0);
+				path.lineTo(w, 0);
+			}
+			
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.lineTo(0, dy);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['folder'] = FolderShape;
+
+	// Card Shape, supports size style
+	function CardShape() { };
+	CardShape.prototype = new mxCylinder();
+	CardShape.prototype.constructor = CardShape;
+	CardShape.prototype.size = 30;
+	CardShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = Math.min(w, Math.min(h, mxUtils.getValue(this.style, 'size', this.size)));
+
+		if (!isForeground)
+		{
+			path.moveTo(s, 0);
+			path.lineTo(w, 0);
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.lineTo(0, s);
+			path.lineTo(s, 0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['card'] = CardShape;
+
+	// Tape Shape, supports size style
+	function TapeShape() { };
+	TapeShape.prototype = new mxCylinder();
+	TapeShape.prototype.constructor = TapeShape;
+	TapeShape.prototype.size = 0.4;
+	TapeShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = mxUtils.getValue(this.style, 'size', this.size);
+		var dy = h * s;
+		var fy = 1.4;
+
+		if (!isForeground)
+		{
+			path.moveTo(0, dy / 2);
+			path.quadTo(w / 4, dy * fy, w / 2, dy / 2);
+			path.quadTo(w * 3 / 4, dy * (1 - fy), w, dy / 2);
+			path.lineTo(w, h - dy / 2);
+			path.quadTo(w * 3 / 4, h - dy * fy, w / 2, h - dy / 2);
+			path.quadTo(w / 4, h - dy * (1 - fy), 0, h - dy / 2);
+			path.lineTo(0, dy / 2);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['tape'] = TapeShape;
+
+	// Tape Shape, supports size style
+	function StepShape() { };
+	StepShape.prototype = new mxCylinder();
+	StepShape.prototype.constructor = StepShape;
+	StepShape.prototype.size = 0.2;
+	StepShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s =  w * mxUtils.getValue(this.style, 'size', this.size);
+
+		if (!isForeground)
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w - s, 0);
+			path.lineTo(w, h / 2);
+			path.lineTo(w - s, h);
+			path.lineTo(0, h);
+			path.lineTo(s, h / 2);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['step'] = StepShape;
+
+	// Plus Shape
+	function PlusShape()
+	{
+		mxRectangleShape.call(this);
+	};
+	mxUtils.extend(PlusShape, mxRectangleShape);
+	PlusShape.prototype.isHtmlAllowed = function()
+	{
+		return false;
+	};
+	PlusShape.prototype.paintForeground = function(c, x, y, w, h)
+	{
+		var border = Math.min(w / 5, h / 5) + 1;
+		
+		c.begin();
+		c.moveTo(x + w / 2, y + border);
+		c.lineTo(x + w / 2, y + h - border);
+		c.moveTo(x + border, y + h / 2);
+		c.lineTo(x + w - border, y + h / 2);
+		c.end();
+		c.stroke();
+		mxRectangleShape.prototype.paintForeground.apply(this, arguments);
+	};
+
+	mxCellRenderer.prototype.defaultShapes['plus'] = PlusShape;
+
+	// CompositeShape
+	function ExtendedShape()
+	{
+		mxRectangleShape.call(this);
+	};
+	mxUtils.extend(ExtendedShape, mxRectangleShape);
+	ExtendedShape.prototype.isHtmlAllowed = function()
+	{
+		return false;
+	};
+	ExtendedShape.prototype.paintForeground = function(c, x, y, w, h)
+	{
+		if (this.style != null)
+		{
+			if (this.style['double'] == 1)
+			{
+				var inset = Math.max(2, this.strokewidth + 1);
+	
+				mxRectangleShape.prototype.paintBackground.call(this, c, x + inset, y + inset, w - 2 * inset, h - 2 * inset);
+				mxRectangleShape.prototype.paintForeground.apply(this, arguments);
+				
+				x += inset;
+				y += inset;
+				w -= 2 * inset;
+				h -= 2 * inset;
+			}
+			
+			c.setDashed(false);
+			
+			// Draws the symbols defined in the style. The symbols are
+			// numbered from 1...n. Possible postfixes are align,
+			// verticalAlign, spacing, arcSpacing, width, height
+			var counter = 0;
+			var shape = null;
+			
+			do
+			{
+				shape = mxCellRenderer.prototype.defaultShapes[this.style['symbol' + counter]];
+				
+				if (shape != null)
+				{
+					var align = this.style['symbol' + counter + 'Align'];
+					var valign = this.style['symbol' + counter + 'VerticalAlign'];
+					var width = this.style['symbol' + counter + 'Width'];
+					var height = this.style['symbol' + counter + 'Height'];
+					var spacing = this.style['symbol' + counter + 'Spacing'] || 0;
+					var arcspacing = this.style['symbol' + counter + 'ArcSpacing'];
+					
+					if (arcspacing != null)
+					{
+						spacing += this.getArcSize(w + this.strokewidth, h + this.strokewidth) * arcspacing;
+					}
+					
+					var x2 = x;
+					var y2 = y;
+					
+					if (align == mxConstants.ALIGN_CENTER)
+					{
+						x2 += (w - width) / 2;
+					}
+					else if (align == mxConstants.ALIGN_RIGHT)
+					{
+						x2 += w - width - spacing;
+					}
+					else
+					{
+						x2 += spacing;
+					}
+					
+					if (valign == mxConstants.ALIGN_MIDDLE)
+					{
+						y2 += (h - height) / 2;
+					}
+					else if (valign == mxConstants.ALIGN_BOTTOM)
+					{
+						y2 += h - height - spacing;
+					}
+					else
+					{
+						y2 += spacing;
+					}
+					
+					c.save();
+					
+					// Small hack to pass style along into subshape
+					var tmp = new shape();
+					// TODO: Clone style and override settings (eg. strokewidth)
+					tmp.style = this.style;
+					shape.prototype.paintVertexShape.call(tmp, c, x2, y2, width, height);
+					c.restore();
+				}
+				
+				counter++;
+			}
+			while (shape != null);
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['ext'] = ExtendedShape;
+	
+	// Tape Shape, supports size style
+	function MessageShape() { };
+	MessageShape.prototype = new mxCylinder();
+	MessageShape.prototype.constructor = MessageShape;
+	MessageShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		if (isForeground)
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w / 2, h / 2);
+			path.lineTo(w, 0);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w, 0);
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.close();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['message'] = MessageShape;
+	
+	// New Actor Shape
+	function UmlActorShape() { };
+	UmlActorShape.prototype = new mxCylinder();
+	UmlActorShape.prototype.constructor = UmlActorShape;
+	UmlActorShape.prototype.addPipe = true;
+	UmlActorShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var width = w / 3;
+		var height = h / 4;
+		
+		if (!isForeground)
+		{
+			path.moveTo(w / 2, height);
+			path.curveTo(w / 2 - width, height, w / 2 - width, 0, w / 2, 0);
+			path.curveTo(w / 2 + width, 0, w / 2 + width, height, w / 2, height);
+			path.close();
+
+			path.moveTo(w / 2, height);
+			path.lineTo(w / 2, 2 * h / 3);
+			
+			// Arms
+			path.moveTo(w / 2, h / 3);
+			path.lineTo(0, h / 3);
+			path.moveTo(w / 2, h / 3);
+			path.lineTo(w, h / 3);
+			
+			// Legs
+			path.moveTo(w / 2, 2 * h / 3);
+			path.lineTo(0, h);
+			path.moveTo(w / 2, 2 * h / 3);
+			path.lineTo(w, h);
+			path.end();
+		}
+	};
+
+	// Replaces existing actor shape
+	mxCellRenderer.prototype.defaultShapes['umlActor'] = UmlActorShape;
+
+	// New Actor Shape
+	function LollipopShape() { };
+	LollipopShape.prototype = new mxCylinder();
+	LollipopShape.prototype.constructor = LollipopShape;
+	LollipopShape.prototype.size = 10;
+	LollipopShape.prototype.addPipe = true;
+	LollipopShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var ss = mxUtils.getValue(this.style, 'size', this.size);
+		var width = ss * 2 / 3;
+		var height = ss;
+		
+		if (!isForeground)
+		{
+			path.moveTo(w / 2, height);
+			path.curveTo(w / 2 - width, height, w / 2 - width, 0, w / 2, 0);
+			path.curveTo(w / 2 + width, 0, w / 2 + width, height, w / 2, height);
+			path.close();
+
+			path.moveTo(w / 2, height);
+			path.lineTo(w / 2, h);
+			path.end();
+		}
+	};
+
+	// Replaces existing actor shape
+	mxCellRenderer.prototype.defaultShapes['lollipop'] = LollipopShape;
+	
+	// Folder Shape, supports tabWidth, tabHeight styles
+	function ComponentShape() { };
+	ComponentShape.prototype = new mxCylinder();
+	ComponentShape.prototype.constructor = ComponentShape;
+	ComponentShape.prototype.jettyWidth = 32;
+	ComponentShape.prototype.jettyHeight = 12;
+	ComponentShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var dx = mxUtils.getValue(this.style, 'jettyWidth', this.jettyWidth);
+		var dy = mxUtils.getValue(this.style, 'jettyHeight', this.jettyHeight);
+		var x0 = dx / 2;
+		var x1 = x0 + dx / 2;
+		var y0 = 0.3 * h - dy / 2;
+		var y1 = 0.7 * h - dy / 2;
+
+		if (isForeground)
+		{
+			path.moveTo(x0, y0);
+			path.lineTo(x1, y0);
+			path.lineTo(x1, y0 + dy);
+			path.lineTo(x0, y0 + dy);
+			path.moveTo(x0, y1);
+			path.lineTo(x1, y1);
+			path.lineTo(x1, y1 + dy);
+			path.lineTo(x0, y1 + dy);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(x0, 0);
+			path.lineTo(w, 0);
+			path.lineTo(w, h);
+			path.lineTo(x0, h);
+			path.lineTo(x0, y1 + dy);
+			path.lineTo(0, y1 + dy);
+			path.lineTo(0, y1);
+			path.lineTo(x0, y1);
+			path.lineTo(x0, y0 + dy);
+			path.lineTo(0, y0 + dy);
+			path.lineTo(0, y0);
+			path.lineTo(x0, y0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['component'] = ComponentShape;
+	
+	// State Shapes derives from double ellipse
+	function StateShape() { };
+	StateShape.prototype = new mxDoubleEllipse();
+	StateShape.prototype.constructor = StateShape;
+	StateShape.prototype.outerStroke = true;
+	StateShape.prototype.paintVertexShape = function(c, x, y, w, h)
+	{
+		var inset = Math.min(4, Math.min(w / 5, h / 5));
+		
+		if (w > 0 && h > 0)
+		{
+			c.ellipse(x + inset, y + inset, w - 2 * inset, h - 2 * inset);
+			c.fillAndStroke();
+		}
+		
+		c.setShadow(false);
+
+		if (this.outerStroke)
+		{
+			c.ellipse(x, y, w, h);
+			c.stroke();			
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['endState'] = StateShape;
+
+	function StartStateShape() { };
+	StartStateShape.prototype = new StateShape();
+	StartStateShape.prototype.constructor = StartStateShape;
+	StartStateShape.prototype.outerStroke = false;
+	
+	mxCellRenderer.prototype.defaultShapes['startState'] = StartStateShape;
+
+	// Image export for state shapes
+	var imageExportInitShapes = mxImageExport.prototype.initShapes;
+	mxImageExport.prototype.initShapes = function()
+	{
+		imageExportInitShapes.apply(this, arguments);
+
+		function createStateShape(outerStroke)
+		{
+			return {
+				drawShape: function(canvas, state, bounds, background)
+				{
+					var x = bounds.x;
+					var y = bounds.y;
+					var w = bounds.width;
+					var h = bounds.height;
+					
+					if (background)
+					{
+						var inset = Math.min(4, Math.min(w / 5, h / 5));
+						x += inset;
+						y += inset;
+						w -= 2 * inset;
+						h -= 2 * inset;
+						
+						if (w > 0 && h > 0)
+						{
+							canvas.ellipse(x, y, w, h);
+						}
+						
+						return true;
+					}
+					else
+					{
+						canvas.fillAndStroke();
+		
+						if (outerStroke)
+						{
+							canvas.ellipse(x, y, w, h);
+							canvas.stroke();
+						}
+					}
+				}
+			};
+		};
+		
+		this.shapes['endState'] = createStateShape(true);
+		this.shapes['startState'] = createStateShape(false);
+	};
+
+	// Defines custom edge shape
+	function LinkShape()
+	{
+		mxArrow.call(this);
+	};
+	mxUtils.extend(LinkShape, mxArrow);
+	LinkShape.prototype.paintEdgeShape = function(c, pts)
+	{
+		var width = 10;
+
+		// Base vector (between end points)
+		var p0 = pts[0];
+		var pe = pts[pts.length - 1];
+		
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+		var dist = Math.sqrt(dx * dx + dy * dy);
+		var length = dist;
+		
+		// Computes the norm and the inverse norm
+		var nx = dx / dist;
+		var ny = dy / dist;
+		var basex = length * nx;
+		var basey = length * ny;
+		var floorx = width * ny/3;
+		var floory = -width * nx/3;
+		
+		// Computes points
+		var p0x = p0.x - floorx / 2;
+		var p0y = p0.y - floory / 2;
+		var p1x = p0x + floorx;
+		var p1y = p0y + floory;
+		var p2x = p1x + basex;
+		var p2y = p1y + basey;
+		var p3x = p2x + floorx;
+		var p3y = p2y + floory;
+		// p4 not necessary
+		var p5x = p3x - 3 * floorx;
+		var p5y = p3y - 3 * floory;
+		
+		c.begin();
+		c.moveTo(p1x, p1y);
+		c.lineTo(p2x, p2y);
+		c.moveTo(p5x + floorx, p5y + floory);
+		c.lineTo(p0x, p0y);
+		c.stroke();
+	};
+
+	// Registers the link shape
+	mxCellRenderer.prototype.defaultShapes['link'] = LinkShape;
+
+	// Registers and defines the custom marker
+	mxMarker.addMarker('dash', function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var nx = unitX * (size + sw + 1);
+		var ny = unitY * (size + sw + 1);
+
+		return function()
+		{
+			canvas.begin();
+			canvas.moveTo(pe.x - nx / 2 - ny / 2, pe.y - ny / 2 + nx / 2);
+			canvas.lineTo(pe.x + ny / 2 - 3 * nx / 2, pe.y - 3 * ny / 2 - nx / 2);
+			canvas.stroke();
+		};
+	});
+
+	// Implements custom handlers
+	var SPECIAL_HANDLE_INDEX = -99;
+
+	// Handlers are only added if mxVertexHandler is defined (ie. not in embedded graph)
+	if (typeof(mxVertexHandler) != 'undefined')
+	{
+		function mxExtVertexHandler(state)
+		{
+			mxVertexHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxExtVertexHandler, mxVertexHandler);
+	
+		mxExtVertexHandler.prototype.useGridForSpecialHandle = false;
+		
+		mxExtVertexHandler.prototype.init = function()
+		{
+			this.horizontal = mxUtils.getValue(this.state.style, mxConstants.STYLE_HORIZONTAL, true);
+			var graph = this.state.view.graph;
+	
+			if (this.handleImage != null)
+			{
+				var bounds = new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height);
+				this.specialHandle = new mxImageShape(bounds, this.handleImage.src);
+			}
+			else
+			{
+				var size = 10;
+				var bounds = new mxRectangle(0, 0, size, size);
+				this.specialHandle = new mxRhombus(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+			}
+			
+			this.specialHandle.dialect = (graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+			this.specialHandle.init(graph.getView().getOverlayPane());
+			this.specialHandle.node.style.cursor = this.getSpecialHandleCursor();
+	
+			mxEvent.redirectMouseEvents(this.specialHandle.node, graph, this.state);
+			mxVertexHandler.prototype.init.apply(this, arguments);
+		};
+		
+		mxExtVertexHandler.prototype.getSpecialHandleCursor = function()
+		{
+			return 'default';
+		};
+		
+		mxExtVertexHandler.prototype.redraw = function()
+		{
+			mxVertexHandler.prototype.redraw.apply(this, arguments);
+	
+			var size = this.specialHandle.bounds.width;
+			this.specialHandle.bounds = this.getSpecialHandleBounds(size);
+			this.specialHandle.redraw();
+		};
+
+		mxExtVertexHandler.prototype.destroy = function()
+		{
+			mxVertexHandler.prototype.destroy.apply(this, arguments);
+			
+			if (this.specialHandle != null)
+			{
+				this.specialHandle.destroy();
+				this.specialHandle = null;
+			}
+		};
+		
+		mxExtVertexHandler.prototype.getHandleForEvent = function(me)
+		{
+			if (me.isSource(this.specialHandle))
+			{
+				return SPECIAL_HANDLE_INDEX;
+			}
+			
+			return mxVertexHandler.prototype.getHandleForEvent.apply(this, arguments);
+		};
+
+		mxExtVertexHandler.prototype.mouseMove = function(sender, me)
+		{
+			if (!me.isConsumed() && this.index == SPECIAL_HANDLE_INDEX)
+			{
+				var point = new mxPoint(me.getGraphX(), me.getGraphY());
+				this.constrainPoint(point);
+				var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+				var scale = this.graph.getView().scale;
+				
+				if (gridEnabled && this.useGridForSpecialHandle)
+				{
+					point.x = this.graph.snap(point.x / scale) * scale;
+					point.y = this.graph.snap(point.y / scale) * scale;
+				}
+				
+				this.updateStyle(point);			
+				this.moveSizerTo(this.specialHandle, point.x, point.y);
+				this.state.view.graph.cellRenderer.redraw(this.state, true);
+				me.consume();
+			}
+			else
+			{
+				mxVertexHandler.prototype.mouseMove.apply(this, arguments);
+			}
+		};
+
+		mxExtVertexHandler.prototype.mouseUp = function(sender, me)
+		{
+			if (!me.isConsumed() && this.index == SPECIAL_HANDLE_INDEX)
+			{
+				this.applyStyle();
+				this.reset();
+				me.consume();
+			}
+			else
+			{
+				mxVertexHandler.prototype.mouseUp.apply(this, arguments);
+			}
+		};
+
+		mxExtVertexHandler.prototype.getSpecialHandleBounds = function(size)
+		{
+			var rotation = this.state.shape.getShapeRotation();
+			var alpha = mxUtils.toRadians(rotation);
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+			
+			var bounds = new mxRectangle(this.state.x, this.state.y, this.state.width, this.state.height);
+			
+			if (this.state.shape.isPaintBoundsInverted())
+			{
+				var t = (bounds.width - bounds.height) / 2;
+				bounds.x += t;
+				bounds.y -= t;
+				var tmp = bounds.width;
+				bounds.width = bounds.height;
+				bounds.height = tmp;
+			}
+	
+			var pt = this.getSpecialHandlePoint(bounds);
+
+			if (this.state.shape.flipH)
+			{
+				pt.x = 2 * bounds.x + bounds.width - pt.x;
+			}
+			
+			if (this.state.shape.flipV)
+			{
+				pt.y = 2 * bounds.y + bounds.height - pt.y;
+			}
+			
+			pt = mxUtils.getRotatedPoint(pt, cos, sin,
+				new mxPoint(this.state.getCenterX(), this.state.getCenterY()));
+
+			return new mxRectangle(pt.x - size / 2, pt.y - size / 2, size, size);
+		};
+		
+		mxExtVertexHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			// Hook for subclassers
+			return null;
+		};
+	
+		mxExtVertexHandler.prototype.updateStyle = function(point)
+		{
+			// Hook for subclassers
+		};
+		
+		mxExtVertexHandler.prototype.constrainPoint = function(point)
+		{
+			point.x = Math.max(this.state.x, Math.min(this.state.x + this.state.width, point.x));
+			point.y = Math.max(this.state.y, Math.min(this.state.y + this.state.height, point.y));
+		};
+		
+		mxExtVertexHandler.prototype.applyStyle = function()
+		{
+			// Hook for subclassers
+		};
+
+		// Folder Handler
+		function mxFolderHandler(state)
+		{
+			mxExtVertexHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxFolderHandler, mxExtVertexHandler);
+
+		mxFolderHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var tw = Math.min(bounds.width, mxUtils.getValue(this.state.style, 'tabWidth', 60) * scale);
+			var th = Math.min(bounds.height, mxUtils.getValue(this.state.style, 'tabHeight', 20) * scale);
+			
+			var tp = mxUtils.getValue(this.state.style, 'tabPosition', 'right');
+			var x = (tp == 'left') ? bounds.x + tw : bounds.x + bounds.width - tw;
+	
+			return new mxPoint(x, bounds.y + th);
+		};
+		
+		mxFolderHandler.prototype.updateStyle = function(point)
+		{
+			var rotation = this.state.shape.getShapeRotation();
+			var alpha = mxUtils.toRadians(rotation);
+			var cos = Math.cos(-alpha);
+			var sin = Math.sin(-alpha);
+
+			var bounds = new mxRectangle(this.state.x, this.state.y, this.state.width, this.state.height);
+			
+			if (this.state.shape.isPaintBoundsInverted())
+			{
+				var t = (bounds.width - bounds.height) / 2;
+				bounds.x += t;
+				bounds.y -= t;
+				var tmp = bounds.width;
+				bounds.width = bounds.height;
+				bounds.height = tmp;
+			}
+	
+			var pt = new mxPoint(point.x, point.y);
+			pt = mxUtils.getRotatedPoint(pt, cos, sin,
+				new mxPoint(this.state.getCenterX(), this.state.getCenterY()));
+
+			if (this.state.shape.flipH)
+			{
+				pt.x = 2 * bounds.x + bounds.width - pt.x;
+			}
+			
+			if (this.state.shape.flipV)
+			{
+				pt.y = 2 * bounds.y + bounds.height - pt.y;
+			}
+			
+			var result = this.updateStyleUnrotated(pt, bounds);
+		
+			// Modifies point to use rotated coordinates of return value
+			if (result != null)
+			{
+				if (this.state.shape.flipH)
+				{
+					result.x = 2 * bounds.x + bounds.width - result.x;
+				}
+				
+				if (this.state.shape.flipV)
+				{
+					result.y = 2 * bounds.y + bounds.height - result.y;
+				}
+				
+				cos = Math.cos(alpha);
+				sin = Math.sin(alpha);
+				result = mxUtils.getRotatedPoint(result, cos, sin,
+						new mxPoint(this.state.getCenterX(), this.state.getCenterY()));
+				point.x = result.x;
+				point.y = result.y;
+			}
+		};
+		
+		mxFolderHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var tp = mxUtils.getValue(this.state.style, 'tabPosition', 'right');
+			var tw = (tp == 'left') ? pt.x - bounds.x : bounds.x + bounds.width - pt.x;
+			var th = pt.y - bounds.y;
+			
+			var scale = this.graph.getView().scale;
+			this.state.style['tabWidth'] = Math.round(Math.max(1, tw) / scale);
+			this.state.style['tabHeight'] =  Math.round(Math.max(1, th) / scale);
+		};
+		
+		mxFolderHandler.prototype.applyStyle = function()
+		{
+			var model = this.graph.getModel();
+			model.beginUpdate();
+			try
+			{
+				this.state.view.graph.setCellStyles('tabWidth', this.state.style['tabWidth'], [this.state.cell]);
+				this.state.view.graph.setCellStyles('tabHeight', this.state.style['tabHeight'], [this.state.cell]);
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		};
+		
+		// Swimlane Handler
+		function mxSwimlaneHandler(state)
+		{
+			mxFolderHandler.call(this, state);
+		};
+		
+		mxUtils.extend(mxSwimlaneHandler, mxFolderHandler);
+		
+		mxSwimlaneHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var startSize = mxUtils.getValue(this.state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
+
+			return new mxPoint(bounds.x + bounds.width / 2, bounds.y + Math.min(bounds.height, startSize * scale));
+		};
+		
+		mxSwimlaneHandler.prototype.updateStyleUnrotated = function(point, bounds)
+		{
+			point.x = bounds.x + bounds.width / 2;
+			startSize = point.y - bounds.y;
+			var scale = this.graph.getView().scale;
+			this.state.style['startSize'] = Math.round(Math.max(1, startSize) / scale);
+			
+			return point;
+		};
+		
+		mxSwimlaneHandler.prototype.applyStyle = function()
+		{
+			this.state.view.graph.setCellStyles('startSize', this.state.style['startSize'], [this.state.cell]);
+		};
+
+		// Cube Handler
+		function mxCubeHandler(state)
+		{
+			mxFolderHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxCubeHandler, mxFolderHandler);
+		
+		mxCubeHandler.prototype.defaultValue = 20;
+	
+		mxCubeHandler.prototype.scaleFactor = 1;
+		
+		mxCubeHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var sz = Math.min(bounds.width, Math.min(bounds.height,
+				mxUtils.getValue(this.state.style, 'size', this.defaultValue) * scale / this.scaleFactor));
+			
+			return new mxPoint(bounds.x + sz, bounds.y + sz);
+		};
+	
+		mxCubeHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.max(0, Math.min(Math.min(bounds.width / this.scaleFactor, pt.x - bounds.x),
+					Math.min(bounds.height / this.scaleFactor, pt.y - bounds.y)));
+			var scale = this.graph.getView().scale;
+			this.state.style['size'] = Math.round(Math.max(1, size) / scale) * this.scaleFactor;
+			
+			// Stays on the diagonal
+			return new mxPoint(bounds.x + size, bounds.y + size);
+		};
+		
+		mxCubeHandler.prototype.applyStyle = function()
+		{
+			this.state.view.graph.setCellStyles('size', this.state.style['size'], [this.state.cell]);
+		};
+		
+		// Card Handler
+		function mxCardHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxCardHandler, mxCubeHandler);
+		
+		mxCardHandler.prototype.defaultValue = 30;
+	
+		mxCardHandler.prototype.scaleFactor = 2;
+		
+		// Note Handler
+		function mxNoteHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxNoteHandler, mxCubeHandler);
+		
+		mxNoteHandler.prototype.defaultValue = 30;
+	
+		mxNoteHandler.prototype.scaleFactor = 1;
+		
+		mxNoteHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var sz = Math.min(bounds.width, Math.min(bounds.height,
+					mxUtils.getValue(this.state.style, 'size', this.defaultValue) * scale / this.scaleFactor));
+			
+			return new mxPoint(bounds.x + bounds.width - sz, bounds.y + sz);
+		};
+		
+		mxNoteHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.max(0, Math.min(Math.min(bounds.width / this.scaleFactor, pt.x - bounds.x + bounds.width),
+					Math.min(bounds.height / this.scaleFactor, pt.y - bounds.y)));
+			var scale = this.graph.getView().scale;
+			this.state.style['size'] = Math.round(Math.max(1, size) / scale) * this.scaleFactor;
+			
+			// Stays on the diagonal
+			return new mxPoint(bounds.x + bounds.width - size, bounds.y + size);
+		};
+		
+		// Step Handler
+		function mxStepHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxStepHandler, mxCubeHandler);
+		
+		mxStepHandler.prototype.defaultValue = 0.2;
+	
+		mxStepHandler.prototype.scaleFactor = 1;
+		
+		mxStepHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var sz = mxUtils.getValue(this.state.style, 'size', this.defaultValue);
+			
+			return new mxPoint(bounds.x + bounds.width * sz, bounds.y + bounds.height / 2);
+		};
+	
+		mxStepHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.min(1, (pt.x - bounds.x) / bounds.width);
+			this.state.style['size'] = size;
+			
+			return new mxPoint(bounds.x + size * bounds.width, bounds.y + bounds.height / 2);
+		};
+		
+		// Tape Handler
+		function mxTapeHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxTapeHandler, mxCubeHandler);
+		
+		mxTapeHandler.prototype.defaultValue = 0.4;
+	
+		mxTapeHandler.prototype.scaleFactor = 1;
+		
+		mxTapeHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var sz = mxUtils.getValue(this.state.style, 'size', this.defaultValue);
+	
+			return new mxPoint(bounds.x + bounds.width / 2, bounds.y + sz * bounds.height / 2);
+		};
+	
+		mxTapeHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.max(0, Math.min(1, ((pt.y - bounds.y) / bounds.height) * 2));
+			this.state.style['size'] = size;
+			
+			return new mxPoint(bounds.x + bounds.width / 2, bounds.y + size * bounds.height / 2);
+		};
+		
+		var handlers = {'swimlane': mxSwimlaneHandler, 'folder': mxFolderHandler, 'cube': mxCubeHandler,
+				'card': mxCardHandler, 'note': mxNoteHandler, 'step': mxStepHandler, 'tape': mxTapeHandler};
+
+		var mxGraphCreateHandler = mxGraph.prototype.createHandler;
+		mxGraph.prototype.createHandler = function(state)
+		{
+			if (state != null)
+			{
+				var ctor = handlers[state.style['shape']];
+
+				if (ctor != null)
+				{
+					return new ctor(state);
+				}
+			}
+			
+			return mxGraphCreateHandler.apply(this, arguments);
+		};
+	}
+	
+	// Constraints
+	mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+	{
+		if (terminal != null && terminal.shape != null)
+		{
+			if (terminal.shape.stencil != null)
+			{
+				if (terminal.shape.stencil != null)
+				{
+					return terminal.shape.stencil.constraints;
+				}
+			}
+			else if (terminal.shape.constraints != null)
+			{
+				return terminal.shape.constraints;
+			}
+		}
+
+		return null;
+	};
+
+	mxRectangleShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.75, 0), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.25), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.75), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.25), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.75), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.75, 1), true)];
+	mxLabel.prototype.constraints = mxRectangleShape.prototype.constraints;
+	mxImageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	mxSwimlane.prototype.constraints = mxRectangleShape.prototype.constraints;
+	PlusShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	NoteShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	CardShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	CubeShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	FolderShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	mxCylinder.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.15, 0.05), false),
+                                        new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+                                        new mxConnectionConstraint(new mxPoint(0.85, 0.05), false),
+      	              		 new mxConnectionConstraint(new mxPoint(0, 0.3), true),
+      	              		 new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+      	              		 new mxConnectionConstraint(new mxPoint(0, 0.7), true),
+      	            		 new mxConnectionConstraint(new mxPoint(1, 0.3), true),
+      	            		 new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+      	            		 new mxConnectionConstraint(new mxPoint(1, 0.7), true),
+      	            		 new mxConnectionConstraint(new mxPoint(0.15, 0.95), false),
+      	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+      	            		 new mxConnectionConstraint(new mxPoint(0.85, 0.95), false)];
+	UmlActorShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0.1), false),
+	                                          new mxConnectionConstraint(new mxPoint(0.5, 0), false),
+	                                          new mxConnectionConstraint(new mxPoint(0.75, 0.1), false),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 1/3), false),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 1), false),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 1/3), false),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 1), false),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.5, 0.5), false)];
+	ComponentShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.75, 0), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.3), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.7), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.25), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.75), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.75, 1), true)];
+	mxActor.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+   	              		 new mxConnectionConstraint(new mxPoint(0.25, 0.2), false),
+   	              		 new mxConnectionConstraint(new mxPoint(0.1, 0.5), false),
+   	              		 new mxConnectionConstraint(new mxPoint(0, 0.75), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.75, 0.25), false),
+   	            		 new mxConnectionConstraint(new mxPoint(0.9, 0.5), false),
+   	            		 new mxConnectionConstraint(new mxPoint(1, 0.75), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.75, 1), true)];
+	TapeShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.35), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.5), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.65), false),
+	                                   new mxConnectionConstraint(new mxPoint(1, 0.35), false),
+		                                new mxConnectionConstraint(new mxPoint(1, 0.5), false),
+		                                new mxConnectionConstraint(new mxPoint(1, 0.65), false),
+										new mxConnectionConstraint(new mxPoint(0.25, 1), false),
+										new mxConnectionConstraint(new mxPoint(0.75, 0), false)];
+	// TODO: Relative ports
+	StepShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
+                                       new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+                                       new mxConnectionConstraint(new mxPoint(0.75, 0), true),
+                                       new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+  	        	            		 	new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+  	        	            		 	new mxConnectionConstraint(new mxPoint(0.75, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.1, 0.25), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.2, 0.5), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.1, 0.75), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.9, 0.25), false),
+		                                new mxConnectionConstraint(new mxPoint(1, 0.5), false),
+		                                new mxConnectionConstraint(new mxPoint(0.9, 0.75), false)];
+	mxLine.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
+	                                new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
+	                                new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
+									new mxConnectionConstraint(new mxPoint(1, 0.5), false)];
+	LollipopShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.5, 0), false),
+										new mxConnectionConstraint(new mxPoint(0.5, 1), false)];
+	mxEllipse.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0), true), new mxConnectionConstraint(new mxPoint(1, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0, 1), true), new mxConnectionConstraint(new mxPoint(1, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.5, 0), true), new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	          	              		   new mxConnectionConstraint(new mxPoint(0, 0.5), true), new mxConnectionConstraint(new mxPoint(1, 0.5))];
+	mxDoubleEllipse.prototype.constraints = mxEllipse.prototype.constraints;
+	mxRhombus.prototype.constraints = mxEllipse.prototype.constraints;
+	mxTriangle.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.25), true),
+	                                    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.75), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(1, 0.5), true)];
+	mxHexagon.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.375, 0), true),
+	                                    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.625, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.125, 0.25), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.125, 0.75), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.875, 0.25), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.875, 0.75), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.375, 1), true),
+	                                    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.625, 1), true)];
+	mxCloud.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0.25), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.4, 0.1), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.16, 0.55), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.07, 0.4), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.31, 0.8), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.13, 0.77), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.8, 0.8), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.55, 0.95), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.875, 0.5), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.96, 0.7), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.625, 0.2), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.88, 0.25), false)];
+	mxArrow.prototype.constraints = null;
+})();
diff --git a/public/assets/plugins/jscripty/js/Sidebar.js b/public/assets/plugins/jscripty/js/Sidebar.js
index 2d05e1a..970d060 100644
--- a/public/assets/plugins/jscripty/js/Sidebar.js
+++ b/public/assets/plugins/jscripty/js/Sidebar.js
@@ -1 +1,782 @@
\ No newline at end of file
+'use strict';
+
+/**
+ * Construcs a new sidebar for the given editor.
+ */
+function Sidebar(editorUi, container) {
+	this.editorUi = editorUi;
+	this.container = container;
+	this.palettes = new Object();
+	this.showTooltips = true;
+	this.graph = new Graph(document.createElement('div'), null, null, this.editorUi
+		.editor.graph.getStylesheet());
+	this.graph.resetViewOnRootChange = false;
+	this.graph.foldingEnabled = false;
+	this.graph.setConnectable(false);
+	this.graph.autoScroll = false;
+	this.graph.setTooltips(false);
+	this.graph.setEnabled(false);
+
+	// Container must be in the DOM for correct HTML rendering
+	this.graph.container.style.visibility = 'hidden';
+	this.graph.container.style.position = 'absolute';
+	document.body.appendChild(this.graph.container);
+
+	if (!mxClient.IS_TOUCH) {
+		mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function() {
+			this.showTooltips = true;
+		}));
+
+		// Enables tooltips after scroll
+		mxEvent.addListener(container, 'scroll', mxUtils.bind(this, function() {
+			this.showTooltips = true;
+		}));
+
+		mxEvent.addListener(document, 'mousedown', mxUtils.bind(this, function() {
+			this.showTooltips = false;
+			this.hideTooltip();
+		}));
+
+		mxEvent.addListener(document, 'mousemove', mxUtils.bind(this, function(evt) {
+			var src = mxEvent.getSource(evt);
+
+			while (src != null) {
+				if (src == this.currentElt) {
+					return;
+				}
+
+				src = src.parentNode;
+			}
+
+			this.hideTooltip();
+		}));
+
+		// Handles mouse leaving the window
+		mxEvent.addListener(document, 'mouseout', mxUtils.bind(this, function(evt) {
+			if (evt.toElement == null && evt.relatedTarget == null) {
+				this.hideTooltip();
+			}
+		}));
+	}
+
+	this.init();
+
+	// Pre-fetches tooltip image
+	new Image().src = IMAGE_PATH + '/tooltip.png';
+};
+
+/**
+ * Adds all palettes to the sidebar.
+ */
+Sidebar.prototype.init = function() {
+	var dir = STENCIL_PATH;
+	console.log(dir);
+
+	// this.addImagePalette('entry', mxResources.get('application'), dir + '/',
+	// 	'_128x128.png', ['start', 'question', 'input', 'end', 'email'], [mxResources.get('start'),
+	// 		mxResources.get('question'), mxResources.get('input'), mxResources.get('end'), mxResources.get('email')
+	// 	]);
+	this.addImagePalette('entry', mxResources.get('application'), dir + '/',
+		'_128x128.png', ['start', 'question', 'input', 'end'], [mxResources.get('start'),
+			mxResources.get('question'), mxResources.get('input'), mxResources.get('end')
+		]);
+	// this.addBpmnPalette(dir, false);
+
+};
+
+/**
+ * Specifies if tooltips should be visible. Default is true.
+ */
+Sidebar.prototype.enableTooltips = !mxClient.IS_TOUCH;
+
+/**
+ * Specifies the delay for the tooltip. Default is 16 px.
+ */
+Sidebar.prototype.tooltipBorder = 16;
+
+/**
+ * Specifies the delay for the tooltip. Default is 3 px.
+ */
+Sidebar.prototype.thumbBorder = 3;
+
+/**
+ * Specifies the delay for the tooltip. Default is 300 ms.
+ */
+Sidebar.prototype.tooltipDelay = 300;
+
+/**
+ * Specifies if edges should be used as templates if clicked. Default is true.
+ */
+Sidebar.prototype.installEdges = true;
+
+/**
+ * Specifies the URL of the gear image.
+ */
+Sidebar.prototype.gearImage = STENCIL_PATH + '/clipart/Gear_128x128.png';
+
+/**
+ * Specifies the width of the thumbnails.
+ */
+Sidebar.prototype.thumbWidth = 34;
+
+/**
+ * Specifies the height of the thumbnails.
+ */
+Sidebar.prototype.thumbHeight = 34;
+
+/**
+ * Specifies the padding for the thumbnails. Default is 3.
+ */
+Sidebar.prototype.thumbPadding = 2;
+
+/**
+ * Specifies the size of the sidebar titles.
+ */
+Sidebar.prototype.sidebarTitleSize = 9;
+
+/**
+ * Specifies if titles in the sidebar should be enabled.
+ */
+Sidebar.prototype.sidebarTitles = false;
+
+/**
+ * Specifies if titles in the tooltips should be enabled.
+ */
+Sidebar.prototype.tooltipTitles = false;
+
+/**
+ * Adds all palettes to the sidebar.
+ */
+Sidebar.prototype.showTooltip = function(elt, cells, title, showLabel) {
+	if (this.enableTooltips && this.showTooltips) {
+		if (this.currentElt != elt) {
+			if (this.thread != null) {
+				window.clearTimeout(this.thread);
+				this.thread = null;
+			}
+
+			var show = mxUtils.bind(this, function() {
+				// Lazy creation of the DOM nodes and graph instance
+				if (this.tooltip == null) {
+					this.tooltip = document.createElement('div');
+					this.tooltip.className = 'geSidebarTooltip';
+					document.body.appendChild(this.tooltip);
+
+					this.graph2 = new Graph(this.tooltip, null, null, this.editorUi.editor.graph
+						.getStylesheet());
+					this.graph2.view.setTranslate(this.tooltipBorder, this.tooltipBorder);
+					this.graph2.resetViewOnRootChange = false;
+					this.graph2.foldingEnabled = false;
+					this.graph2.autoScroll = false;
+					this.graph2.setTooltips(false);
+					this.graph2.setConnectable(false);
+					this.graph2.setEnabled(false);
+
+					if (!mxClient.IS_SVG) {
+						this.graph2.view.canvas.style.position = 'relative';
+					}
+
+					this.tooltipImage = mxUtils.createImage(IMAGE_PATH + '/tooltip.png');
+					this.tooltipImage.style.position = 'absolute';
+					this.tooltipImage.style.width = '14px';
+					this.tooltipImage.style.height = '27px';
+
+					document.body.appendChild(this.tooltipImage);
+				}
+
+				this.tooltip.style.display = 'block';
+				this.graph2.labelsVisible = (showLabel == null || showLabel);
+				this.graph2.model.clear();
+				this.graph2.addCells(cells);
+
+				var bounds = this.graph2.getGraphBounds();
+				var width = bounds.width + 2 * this.tooltipBorder;
+				var height = bounds.height + 2 * this.tooltipBorder;
+
+				if (mxClient.IS_QUIRKS) {
+					width += 4;
+					height += 4;
+					this.tooltip.style.overflow = 'hidden';
+				} else {
+					this.tooltip.style.overflow = 'visible';
+				}
+
+				this.tooltipImage.style.visibility = 'visible';
+				this.tooltip.style.width = width + 'px';
+
+				// Adds title for entry
+				if (this.tooltipTitles && title != null && title.length > 0) {
+					if (this.tooltipTitle == null) {
+						this.tooltipTitle = document.createElement('div');
+						this.tooltipTitle.style.borderTop = '1px solid gray';
+						this.tooltipTitle.style.textAlign = 'center';
+						this.tooltipTitle.style.width = '100%';
+
+						// Oversize titles are cut-off currently. Should make tooltip wider later.
+						this.tooltipTitle.style.overflow = 'hidden';
+
+						if (mxClient.IS_SVG) {
+							this.tooltipTitle.style.paddingTop = '2px';
+						} else {
+							this.tooltipTitle.style.position = 'absolute';
+							this.tooltipTitle.style.paddingTop = '6px';
+						}
+
+						this.tooltip.appendChild(this.tooltipTitle);
+					} else {
+						this.tooltipTitle.innerHTML = '';
+					}
+
+					this.tooltipTitle.style.display = '';
+					mxUtils.write(this.tooltipTitle, title);
+
+					var dy = this.tooltipTitle.offsetHeight + 10;
+					height += dy;
+
+					if (mxClient.IS_SVG) {
+						this.tooltipTitle.style.marginTop = (-dy) + 'px';
+					} else {
+						height -= 6;
+						this.tooltipTitle.style.top = (height - dy) + 'px';
+					}
+				} else if (this.tooltipTitle != null && this.tooltipTitle.parentNode !=
+					null) {
+					this.tooltipTitle.style.display = 'none';
+				}
+
+				this.tooltip.style.height = height + 'px';
+				var x0 = -Math.min(0, bounds.x - this.tooltipBorder);
+				var y0 = -Math.min(0, bounds.y - this.tooltipBorder);
+
+				var left = this.container.clientWidth + this.editorUi.splitSize + 3;
+				var top = Math.max(0, (this.container.offsetTop + elt.offsetTop - this.container
+					.scrollTop - height / 2 + 16));
+
+				if (mxClient.IS_SVG) {
+					this.graph2.view.canvas.setAttribute('transform', 'translate(' + x0 +
+						',' + y0 + ')');
+				} else {
+					this.graph2.view.drawPane.style.left = x0 + 'px';
+					this.graph2.view.drawPane.style.top = y0 + 'px';
+				}
+
+				// Workaround for ignored position CSS style in IE9
+				// (changes to relative without the following line)
+				// Added width of the page-sidebar
+				//		left += document.getElementById('page-sidebar').offsetWidth;
+				// Added static height of the page-navbar
+				top += 90;
+
+				this.tooltip.style.position = 'absolute';
+				this.tooltip.style.left = left + 'px';
+				this.tooltip.style.top = top + 'px';
+				this.tooltipImage.style.left = (left - 13) + 'px';
+				this.tooltipImage.style.top = (top + height / 2 - 13) + 'px';
+			});
+
+			if (this.tooltip != null && this.tooltip.style.display != 'none') {
+				show();
+			} else {
+				this.thread = window.setTimeout(show, this.tooltipDelay);
+			}
+
+			this.currentElt = elt;
+		}
+	}
+};
+
+/**
+ * Hides the current tooltip.
+ */
+Sidebar.prototype.hideTooltip = function() {
+	if (this.thread != null) {
+		window.clearTimeout(this.thread);
+		this.thread = null;
+	}
+
+	if (this.tooltip != null) {
+		this.tooltip.style.display = 'none';
+		this.tooltipImage.style.visibility = 'hidden';
+		this.currentElt = null;
+	}
+};
+
+/**
+ * Creates and returns the given title element.
+ */
+Sidebar.prototype.createTitle = function(label) {
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geTitle';
+	mxUtils.write(elt, label);
+
+	return elt;
+};
+
+/**
+ * Creates a thumbnail for the given cells.
+ */
+Sidebar.prototype.createThumb = function(cells, width, height, parent, title,
+	showLabel) {
+	this.graph.labelsVisible = (showLabel == null || showLabel);
+	this.graph.view.scaleAndTranslate(1, 0, 0);
+	this.graph.addCells(cells);
+	var bounds = this.graph.getGraphBounds();
+	var corr = this.thumbBorder;
+	var s = Math.min((width - 2) / (bounds.width - bounds.x + corr), (height - 2) /
+		(bounds.height - bounds.y + corr));
+	var x0 = -Math.min(bounds.x, 0);
+	var y0 = -Math.min(bounds.y, 0);
+	this.graph.view.scaleAndTranslate(s, x0, y0);
+
+	bounds = this.graph.getGraphBounds();
+	var dx = Math.max(0, Math.floor((width - bounds.width - bounds.x) / 2));
+	var dy = Math.max(0, Math.floor((height - bounds.height - bounds.y) / 2));
+
+	var node = null;
+
+	// For supporting HTML labels in IE9 standards mode the container is cloned instead
+	if (this.graph.dialect == mxConstants.DIALECT_SVG && !mxClient.NO_FO) {
+		node = this.graph.view.getCanvas().ownerSVGElement.cloneNode(true);
+	}
+	// LATER: Check if deep clone can be used for quirks if container in DOM
+	else {
+		node = this.graph.container.cloneNode(false);
+		node.innerHTML = this.graph.container.innerHTML;
+	}
+
+	this.graph.getModel().clear();
+
+	// Catch-all event handling
+	if (mxClient.IS_IE6) {
+		parent.style.backgroundImage = 'url(' + this.editorUi.editor.transparentImage +
+			')';
+	}
+
+	var dd = 3;
+	node.style.position = 'relative';
+	node.style.overflow = 'hidden';
+	node.style.cursor = 'pointer';
+	node.style.left = (dx + dd) + 'px';
+	node.style.top = (dy + dd) + 'px';
+	node.style.width = width + 'px';
+	node.style.height = height + 'px';
+	node.style.visibility = '';
+	node.style.minWidth = '';
+	node.style.minHeight = '';
+
+	parent.appendChild(node);
+
+	// Adds title for sidebar entries
+	if (this.sidebarTitles && title != null) {
+		var border = (mxClient.IS_QUIRKS) ? 2 * this.thumbPadding + 2 : 0;
+		parent.style.height = (this.thumbHeight + border + this.sidebarTitleSize + 8) +
+			'px';
+
+		var div = document.createElement('div');
+		div.style.fontSize = this.sidebarTitleSize + 'px';
+		div.style.textAlign = 'center';
+		div.style.whiteSpace = 'nowrap';
+
+		if (mxClient.IS_IE) {
+			div.style.height = (this.sidebarTitleSize + 12) + 'px';
+		}
+
+		div.style.paddingTop = '4px';
+		mxUtils.write(div, title);
+		parent.appendChild(div);
+	}
+};
+
+/**
+ * Creates and returns a new palette item for the given image.
+ */
+Sidebar.prototype.createItem = function(cells, title, showLabel) {
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geItem';
+	elt.style.overflow = 'hidden';
+	var border = (mxClient.IS_QUIRKS) ? 8 + 2 * this.thumbPadding : 6;
+	elt.style.width = (this.thumbWidth + border) + 'px';
+	elt.style.height = (this.thumbHeight + border) + 'px';
+	elt.style.padding = this.thumbPadding + 'px';
+
+	// Blocks default click action
+	mxEvent.addListener(elt, 'click', function(evt) {
+		mxEvent.consume(evt);
+	});
+
+	this.createThumb(cells, this.thumbWidth, this.thumbHeight, elt, title,
+		showLabel);
+
+	return elt;
+};
+
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createDropHandler = function(cells, allowSplit) {
+	return function(graph, evt, target, x, y) {
+		if (graph.isEnabled()) {
+			cells = graph.getImportableCells(cells);
+
+			if (cells.length > 0) {
+				var validDropTarget = (target != null) ?
+					graph.isValidDropTarget(target, cells, evt) : false;
+				var select = null;
+
+				if (target != null && !validDropTarget) {
+					target = null;
+				}
+
+				// Splits the target edge or inserts into target group
+				if (allowSplit && graph.isSplitEnabled() && graph.isSplitTarget(target,
+						cells, evt)) {
+					graph.splitEdge(target, cells, null, x, y);
+					select = cells;
+				} else if (cells.length > 0) {
+					select = graph.importCells(cells, x, y, target);
+				}
+
+				if (select != null && select.length > 0) {
+					graph.scrollCellToVisible(select[0]);
+					graph.setSelectionCells(select);
+				}
+			}
+		}
+	};
+};
+
+/**
+ * Creates and returns a preview element for the given width and height.
+ */
+Sidebar.prototype.createDragPreview = function(width, height) {
+	var elt = document.createElement('div');
+	elt.style.border = '1px dashed black';
+	elt.style.width = width + 'px';
+	elt.style.height = height + 'px';
+
+	return elt;
+};
+
+/**
+ * Creates a drag source for the given element.
+ */
+Sidebar.prototype.createDragSource = function(elt, dropHandler, preview) {
+	var dragSource = mxUtils.makeDraggable(elt, this.editorUi.editor.graph,
+		dropHandler,
+		preview, 0, 0, this.editorUi.editor.graph.autoscroll, true, true);
+
+
+
+	// Allows drop into cell only if target is a valid root
+	dragSource.getDropTarget = function(graph, x, y) {
+		var target = mxDragSource.prototype.getDropTarget.apply(this, arguments);
+		if (!graph.isValidRoot(target)) {
+			target = null;
+		}
+
+		return target;
+	};
+
+	return dragSource;
+};
+
+
+
+/**
+ * Adds a handler for inserting the cell with a single click.
+ */
+Sidebar.prototype.addClickHandler = function(elt, ds) {
+	var graph = this.editorUi.editor.graph;
+	var first = null;
+
+	mxEvent.addGestureListeners(elt, function(evt) {
+		first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	});
+
+	var oldMouseUp = ds.mouseUp;
+	ds.mouseUp = function(evt) {
+		if (!mxEvent.isPopupTrigger(evt) && this.currentGraph == null && first !=
+			null) {
+			var tol = graph.tolerance;
+
+			if (Math.abs(first.x - mxEvent.getClientX(evt)) <= tol &&
+				Math.abs(first.y - mxEvent.getClientY(evt)) <= tol) {
+				var gs = graph.getGridSize();
+				ds.drop(graph, evt, null, gs, gs);
+			}
+		}
+
+		oldMouseUp.apply(this, arguments);
+		first = null;
+	};
+};
+
+/**
+ * Giuseppe Careri
+ * Create xml document vertex
+ */
+Sidebar.prototype.createXmlDocument = function(element, label, variables,
+	values) {
+	var doc = mxUtils.createXmlDocument();
+	var node = doc.createElement(element)
+	node.setAttribute('label', label);
+
+	variables.forEach(function(variable, index) {
+		node.setAttribute(variable, values[index] ? values[index] : '');
+	});
+
+	return node;
+}
+
+/**
+ * Giuseppe Careri
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createVertexTemplate = function(style, width, height, value,
+	title, showLabel) {
+	var variables = [];
+	var values = [];
+
+	switch (value) {
+
+		case 'question':
+			variables = ['question', 'other'];
+			values = [''];
+			break;
+		case 'input':
+			variables = ['question', 'add'];
+			values = [''];
+			break;
+		case 'email':
+			variables = ["Host", "Username", "Password", "To", "Subject", "Body"];
+			values = [''];
+			break;
+		default:
+			variables = [];
+			values = [];
+			break;
+	}
+
+	var node = this.createXmlDocument(value, title, variables, values);
+
+	//var cells = [new mxCell((value != null) ? value : '', new mxGeometry(0, 0, width, height), style)];
+	var cells = [new mxCell(node, new mxGeometry(0, 0, width, height), style)];
+	cells[0].vertex = true;
+
+	return this.createVertexTemplateFromCells(cells, width, height, title,
+		showLabel);
+};
+
+
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createVertexTemplateFromCells = function(cells, width, height,
+	title, showLabel) {
+	var elt = this.createItem(cells, title, showLabel);
+	var ds = this.createDragSource(elt, this.createDropHandler(cells, true), this
+		.createDragPreview(width, height));
+	this.addClickHandler(elt, ds);
+
+	// Uses guides for vertices only if enabled in graph
+	ds.isGuidesEnabled = mxUtils.bind(this, function() {
+		return this.editorUi.editor.graph.graphHandler.guidesEnabled;
+	});
+
+	// Shows a tooltip with the rendered cell
+	if (!touchStyle) {
+		mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt) {
+			this.showTooltip(elt, cells, title, showLabel);
+		}));
+	}
+
+	return elt;
+};
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createEdgeTemplate = function(style, width, height, value,
+	title, showLabel) {
+	var cells = [new mxCell((value != null) ? value : '', new mxGeometry(0, 0,
+		width, height), style)];
+	cells[0].geometry.setTerminalPoint(new mxPoint(0, height), true);
+	cells[0].geometry.setTerminalPoint(new mxPoint(width, 0), false);
+	cells[0].geometry.relative = true;
+	cells[0].edge = true;
+
+	return this.createEdgeTemplateFromCells(cells, width, height, title,
+		showLabel);
+};
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createEdgeTemplateFromCells = function(cells, width, height,
+	title, showLabel) {
+	var elt = this.createItem(cells, title, showLabel);
+	this.createDragSource(elt, this.createDropHandler(cells, false), this.createDragPreview(
+		width, height));
+
+	// Installs the default edge
+	var graph = this.editorUi.editor.graph;
+	mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt) {
+		if (this.installEdges) {
+			graph.setDefaultEdge(cells[0]);
+		}
+
+		// Highlights the entry for 200ms
+		elt.style.backgroundColor = '#ffffff';
+
+		window.setTimeout(function() {
+			elt.style.backgroundColor = '';
+		}, 300);
+
+		mxEvent.consume(evt);
+	}));
+
+	// Shows a tooltip with the rendered cell
+	touchStyle = false;
+	if (!touchStyle) {
+		mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt) {
+			this.showTooltip(elt, cells, title, showLabel);
+		}));
+	}
+
+	return elt;
+};
+
+/**
+ * Adds the given palette.
+ */
+Sidebar.prototype.addPalette = function(id, title, expanded, onInit) {
+	var elt = this.createTitle(title);
+	this.container.appendChild(elt);
+
+	var div = document.createElement('div');
+	div.className = 'geSidebar';
+
+	if (expanded) {
+		onInit(div);
+		onInit = null;
+	} else {
+		div.style.display = 'none';
+	}
+
+	this.addFoldingHandler(elt, div, onInit);
+
+	var outer = document.createElement('div');
+	outer.appendChild(div);
+	this.container.appendChild(outer);
+
+	// Keeps references to the DOM nodes
+	if (id != null) {
+		this.palettes[id] = [elt, outer];
+	}
+};
+
+/**
+ * Create the given title element.
+ */
+Sidebar.prototype.addFoldingHandler = function(title, content, funct) {
+	var initialized = false;
+
+	title.style.backgroundImage = (content.style.display == 'none') ?
+		'url(' + IMAGE_PATH + '/collapsed.gif)' : 'url(' + IMAGE_PATH +
+		'/expanded.gif)';
+	title.style.backgroundRepeat = 'no-repeat';
+	title.style.backgroundPosition = '100% 50%';
+
+	mxEvent.addListener(title, 'click', function(evt) {
+		if (content.style.display == 'none') {
+			if (!initialized) {
+				initialized = true;
+
+				if (funct != null) {
+					title.style.cursor = 'wait';
+					window.setTimeout(function() {
+						funct(content);
+						title.style.cursor = '';
+					}, 0);
+				}
+			}
+
+			title.style.backgroundImage = 'url(' + IMAGE_PATH + '/expanded.gif)';
+			content.style.display = 'block';
+		} else {
+			title.style.backgroundImage = 'url(' + IMAGE_PATH + '/collapsed.gif)';
+			content.style.display = 'none';
+		}
+
+		mxEvent.consume(evt);
+	});
+};
+
+/**
+ * Removes the palette for the given ID.
+ */
+Sidebar.prototype.removePalette = function(id) {
+	var elts = this.palettes[id];
+
+	if (elts != null) {
+		this.palettes[id] = null;
+
+		for (var i = 0; i < elts.length; i++) {
+			this.container.removeChild(elts[i]);
+		}
+
+		return true;
+	}
+
+	return false;
+};
+
+/**
+ * Adds the given image palette.
+ */
+Sidebar.prototype.addImagePalette = function(id, title, prefix, postfix, items,
+	titles) {
+	// Giuseppe Careri
+	// Expanded Sidebar only entry
+	this.addPalette(id, title, (id === 'entry'), mxUtils.bind(this, function(
+		content) {
+		var showTitles = titles != null;
+
+		for (var i = 0; i < items.length; i++) {
+			var icon = prefix + items[i] + postfix;
+			content.appendChild(this.createVertexTemplate('image;image=' + icon, 80,
+				80, items[i], (showTitles) ? titles[i] : null, showTitles));
+		}
+	}));
+};
+// ***********modified***************
+// Sidebar.prototype.addBpmnPalette = function(dir, expand) {
+// 	//Abigael Dawit
+//
+// 	var fns = [
+// 		this.createVertexTemplate(
+// 			'text;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;overflow=hidden;',
+// 			40, 20, 'Text', 'Text', null),
+// 		//this.createVertexTemplate('shape=ext;rounded=1;whiteSpace=wrap;dashed=1;dashPattern=1 4;', 120, 80, 'Event\nSub-Process', 'Event Sub-Process', null),
+// 		this.createVertexTemplate('shape=swimlane;whiteSpace=wrap;startSize=60;',
+// 			120, 160, 'Container', 'Container', null),
+// 		//	this.createVertexTemplate('shape=ellipse;', 120, 160, 'Ellips', 'Ellipse', null)
+// 		this.createVertexTemplate(
+// 			'shape=swimlane;html=1;horizontal=0;swimlaneFillColor=white;swimlaneLine=0;fillColor=none;rounded=1;verticalAlign=middle',
+// 			300, 120, 'Lane', 'Lane', null)
+//
+// 	]
+//
+// 	this.addPalette('utility', 'Utility ', false, mxUtils.bind(this, function(
+// 		content) {
+// 		//var showTitles = titles != null;
+// 		for (var i = 0; i < fns.length; i++) {
+// 			content.appendChild(fns[i]);
+// 		}
+// 	}));
+//
+// };
diff --git a/public/assets/plugins/jscripty/js/Toolbar.js b/public/assets/plugins/jscripty/js/Toolbar.js
index efbfd7d..7eab562 100644
--- a/public/assets/plugins/jscripty/js/Toolbar.js
+++ b/public/assets/plugins/jscripty/js/Toolbar.js
@@ -1 +1,407 @@
\ No newline at end of file
+/**
+ * $Id: Toolbar.js,v 1.3 2013-02-20 16:21:29 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Construcs a new toolbar for the given editor.
+ */
+function Toolbar(editorUi, container)
+{
+	this.editorUi = editorUi;
+	this.container = container;
+	this.init();
+
+	// Global handler to hide the current menu
+	mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt)
+	{
+		this.hideMenu();
+	}));
+};
+
+/**
+ * Adds the toolbar elements.
+ */
+Toolbar.prototype.init = function()
+{
+	this.addItems(['save', 'publish', '-', 'print', 'undo', 'redo', '-', 'copy', 'cut', 'paste', 'delete', '-', 'duplicate', '-', 'actualSize', 'zoomIn', 'zoomOut', '-']);
+	var fontElt = this.addMenu('Helvetica', mxResources.get('fontFamily'), true, 'fontFamily');
+	fontElt.style.whiteSpace = 'nowrap';
+	fontElt.style.overflow = 'hidden';
+	fontElt.style.width = '70px';
+	this.addSeparator();
+	var sizeElt = this.addMenu('12', mxResources.get('fontSize'), true, 'fontSize');
+	sizeElt.style.whiteSpace = 'nowrap';
+	sizeElt.style.overflow = 'hidden';
+	sizeElt.style.width = '30px';
+
+	this.addItems(['-', 'bold', 'italic', 'underline']);
+	// Giuseppe Careri
+	// var align = this.addMenuFunction('geSprite-left', mxResources.get('align'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_LEFT], 'geIcon geSprite geSprite-left', null).setAttribute('title', mxResources.get('left'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_CENTER], 'geIcon geSprite geSprite-center', null).setAttribute('title', mxResources.get('center'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_RIGHT], 'geIcon geSprite geSprite-right', null).setAttribute('title', mxResources.get('right'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_TOP], 'geIcon geSprite geSprite-top', null).setAttribute('title', mxResources.get('top'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_MIDDLE], 'geIcon geSprite geSprite-middle', null).setAttribute('title', mxResources.get('middle'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_BOTTOM], 'geIcon geSprite geSprite-bottom', null).setAttribute('title', mxResources.get('bottom'));
+	// }));
+	// this.addItems(['fontColor', '-']);
+	// var line = this.addMenuFunction('geSprite-straight', mxResources.get('line'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], [null], 'geIcon geSprite geSprite-straight', null).setAttribute('title', mxResources.get('straight'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], ['entityRelationEdgeStyle'], 'geIcon geSprite geSprite-entity', null).setAttribute('title', mxResources.get('entityRelation'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW], ['elbowEdgeStyle', 'horizontal'], 'geIcon geSprite geSprite-helbow', null).setAttribute('title', mxResources.get('horizontal'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW], ['elbowEdgeStyle', 'vertical'], 'geIcon geSprite geSprite-velbow', null).setAttribute('title', mxResources.get('vertical'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], ['segmentEdgeStyle'], 'geIcon geSprite geSprite-segment', null).setAttribute('title', mxResources.get('manual'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], ['orthogonalEdgeStyle'], 'geIcon geSprite geSprite-orthogonal', null).setAttribute('title', mxResources.get('automatic'));
+	// }));
+	// var linestart = this.addMenuFunction('geSprite-startclassic', mxResources.get('lineend'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.NONE, 0], 'geIcon geSprite geSprite-noarrow', null).setAttribute('title', mxResources.get('none'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC, 1], 'geIcon geSprite geSprite-startclassic', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OPEN, 1], 'geIcon geSprite geSprite-startopen', null).setAttribute('title', mxResources.get('openArrow'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK, 1], 'geIcon geSprite geSprite-startblock', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OVAL, 1], 'geIcon geSprite geSprite-startoval', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND, 1], 'geIcon geSprite geSprite-startdiamond', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND_THIN, 1], 'geIcon geSprite geSprite-startthindiamond', null).setAttribute('title', mxResources.get('diamondThin'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC, 0], 'geIcon geSprite geSprite-startclassictrans', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK, 0], 'geIcon geSprite geSprite-startblocktrans', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OVAL, 0], 'geIcon geSprite geSprite-startovaltrans', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND, 0], 'geIcon geSprite geSprite-startdiamondtrans', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND_THIN, 0], 'geIcon geSprite geSprite-startthindiamondtrans', null).setAttribute('title', mxResources.get('diamondThin'));
+	// }));
+	// var lineend = this.addMenuFunction('geSprite-endclassic', mxResources.get('lineend'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.NONE, 0], 'geIcon geSprite geSprite-noarrow', null).setAttribute('title', mxResources.get('none'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC, 1], 'geIcon geSprite geSprite-endclassic', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OPEN, 1], 'geIcon geSprite geSprite-endopen', null).setAttribute('title', mxResources.get('openArrow'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK, 1], 'geIcon geSprite geSprite-endblock', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OVAL, 1], 'geIcon geSprite geSprite-endoval', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND, 1], 'geIcon geSprite geSprite-enddiamond', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND_THIN, 1], 'geIcon geSprite geSprite-endthindiamond', null).setAttribute('title', mxResources.get('diamondThin'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC, 0], 'geIcon geSprite geSprite-endclassictrans', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK, 0], 'geIcon geSprite geSprite-endblocktrans', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OVAL, 0], 'geIcon geSprite geSprite-endovaltrans', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND, 0], 'geIcon geSprite geSprite-enddiamondtrans', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND_THIN, 0], 'geIcon geSprite geSprite-endthindiamondtrans', null).setAttribute('title', mxResources.get('diamondThin'));
+	// }));
+	// this.addItems(['-', 'strokeColor', 'image', 'fillColor']);
+	// this.addItem('geSprite-gradientcolor', 'gradientColor').setAttribute('title', mxResources.get('gradient'));
+	// this.addItems(['shadow']);
+
+	var graph = this.editorUi.editor.graph;
+
+	// Update font size and font family labels
+	var update = mxUtils.bind(this, function()
+	{
+		var ff = 'Helvetica';
+		var fs = '12';
+    	var state = graph.getView().getState(graph.getSelectionCell());
+
+    	if (state != null)
+    	{
+    		ff = state.style[mxConstants.STYLE_FONTFAMILY] || ff;
+    		fs = state.style[mxConstants.STYLE_FONTSIZE] || fs;
+
+    		if (ff.length > 10)
+    		{
+    			ff = ff.substring(0, 8) + '...';
+    		}
+
+    		fontElt.innerHTML = ff;
+    		sizeElt.innerHTML = fs;
+    	}
+	});
+
+    graph.getSelectionModel().addListener(mxEvent.CHANGE, update);
+    graph.getModel().addListener(mxEvent.CHANGE, update);
+
+	// Giuseppe Careri
+	// Updates button states
+  //this.addEdgeSelectionHandler([line, linestart, lineend]);
+	//this.addSelectionHandler([align]);
+};
+
+/**
+ * Hides the current menu.
+ */
+Toolbar.prototype.hideMenu = function()
+{
+	if (this.currentMenu != null)
+	{
+		this.currentMenu.hideMenu();
+		this.currentMenu.destroy();
+		this.currentMenu = null;
+	}
+};
+
+/**
+ * Adds a label to the toolbar.
+ */
+Toolbar.prototype.addMenu = function(label, tooltip, showLabels, name)
+{
+	var menu = this.editorUi.menus.get(name);
+	var elt = this.addMenuFunction(label, tooltip, showLabels, menu.funct);
+
+	menu.addListener('stateChanged', function()
+	{
+		elt.setEnabled(menu.enabled);
+	});
+
+	return elt;
+};
+
+/**
+ * Adds a label to the toolbar.
+ */
+Toolbar.prototype.addMenuFunction = function(label, tooltip, showLabels, funct)
+{
+	var elt = (showLabels) ? this.createLabel(label) : this.createButton(label);
+	this.initElement(elt, tooltip);
+	this.addMenuHandler(elt, showLabels, funct);
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Adds a separator to the separator.
+ */
+Toolbar.prototype.addSeparator = function()
+{
+	var elt = document.createElement('div');
+	elt.className = 'geSeparator';
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Adds given action item
+ */
+Toolbar.prototype.addItems = function(keys)
+{
+	for (var i = 0; i < keys.length; i++)
+	{
+		var key = keys[i];
+
+		if (key == '-')
+		{
+			this.addSeparator();
+		}
+		else
+		{
+			this.addItem('geSprite-' + key.toLowerCase(), key);
+		}
+	}
+};
+
+/**
+ * Adds given action item
+ */
+Toolbar.prototype.addItem = function(sprite, key)
+{
+	var action = this.editorUi.actions.get(key);
+	var elt = null;
+
+	if (action != null)
+	{
+		elt = this.addButton(sprite, action.label, action.funct);
+		elt.setEnabled(action.enabled);
+
+		action.addListener('stateChanged', function()
+		{
+			elt.setEnabled(action.enabled);
+		});
+	}
+
+	return elt;
+};
+
+/**
+ * Adds a button to the toolbar.
+ */
+Toolbar.prototype.addButton = function(classname, tooltip, funct)
+{
+	var elt = this.createButton(classname);
+
+	this.initElement(elt, tooltip);
+	this.addClickHandler(elt, funct);
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Updates the states of the given toolbar items based on the selection.
+ */
+Toolbar.prototype.addSelectionHandler = function(items)
+{
+	var graph = this.editorUi.editor.graph;
+
+	var selectionListener = function()
+    {
+    	var selected = !graph.isSelectionEmpty();
+
+    	for (var i = 0; i < items.length; i++)
+    	{
+    		items[i].setEnabled(selected);
+    	}
+    };
+
+    graph.getSelectionModel().addListener(mxEvent.CHANGE, selectionListener);
+    selectionListener();
+};
+
+/**
+ * Updates the states of the given toolbar items based on the selection.
+ */
+Toolbar.prototype.addEdgeSelectionHandler = function(items)
+{
+	var graph = this.editorUi.editor.graph;
+
+	var selectionListener = function()
+    {
+		var edgeSelected = false;
+
+		if (!graph.isSelectionEmpty())
+		{
+			var cells = graph.getSelectionCells();
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (graph.getModel().isEdge(cells[i]))
+				{
+					edgeSelected = true;
+					break;
+				}
+			}
+		}
+
+    	for (var i = 0; i < items.length; i++)
+    	{
+    		items[i].setEnabled(edgeSelected);
+    	}
+    };
+
+    graph.getSelectionModel().addListener(mxEvent.CHANGE, selectionListener);
+    selectionListener();
+};
+
+/**
+ * Initializes the given toolbar element.
+ */
+Toolbar.prototype.initElement = function(elt, tooltip)
+{
+	elt.setAttribute('tabindex', '0');
+
+	// Adds tooltip
+	if (tooltip != null)
+	{
+		elt.setAttribute('title', tooltip);
+	}
+
+	this.addEnabledState(elt);
+};
+
+/**
+ * Adds enabled state with setter to DOM node (avoids JS wrapper).
+ */
+Toolbar.prototype.addEnabledState = function(elt)
+{
+	var classname = elt.className;
+
+	elt.setEnabled = function(value)
+	{
+		elt.enabled = value;
+
+		if (value)
+		{
+			elt.className = classname;
+		}
+		else
+		{
+			elt.className = classname + ' geDisabled';
+		}
+	};
+
+	elt.setEnabled(true);
+};
+
+/**
+ * Adds enabled state with setter to DOM node (avoids JS wrapper).
+ */
+Toolbar.prototype.addClickHandler = function(elt, funct)
+{
+	if (funct != null)
+	{
+		mxEvent.addListener(elt, 'click', function(evt)
+		{
+			if (elt.enabled)
+			{
+				funct(evt);
+			}
+
+			mxEvent.consume(evt);
+		});
+	}
+};
+
+/**
+ * Creates and returns a new button.
+ */
+Toolbar.prototype.createButton = function(classname)
+{
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geButton';
+
+	var inner = document.createElement('div');
+	inner.className = 'geSprite ' + classname;
+	elt.appendChild(inner);
+
+	return elt;
+};
+
+/**
+ * Creates and returns a new button.
+ */
+Toolbar.prototype.createLabel = function(label, tooltip)
+{
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geLabel';
+	mxUtils.write(elt, label);
+
+	return elt;
+};
+
+/**
+ * Adds a handler for showing a menu in the given element.
+ */
+Toolbar.prototype.addMenuHandler = function(elt, showLabels, funct, showAll)
+{
+	if (funct != null)
+	{
+		var graph = this.editorUi.editor.graph;
+		var menu = null;
+
+		mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)
+		{
+			if (elt.enabled == null || elt.enabled)
+			{
+				graph.panningHandler.hideMenu();
+				menu = new mxPopupMenu(funct);
+				menu.div.className += ' geToolbarMenu';
+				menu.showDisabled = showAll;
+				menu.labels = showLabels;
+				menu.autoExpand = true;
+
+				menu.popup(elt.offsetLeft, elt.offsetTop + elt.offsetHeight + 34, null, evt);
+				this.currentMenu = menu;
+			}
+
+			mxEvent.consume(evt);
+		}));
+	}
+};
diff --git a/public/assets/plugins/jscripty/js/custom.js b/public/assets/plugins/jscripty/js/custom.js
index 76d6753..a53e30b 100644
--- a/public/assets/plugins/jscripty/js/custom.js
+++ b/public/assets/plugins/jscripty/js/custom.js
@@ -1 +1,283 @@
\ No newline at end of file
+var urlParams = (function(url) {
+	var result = new Object();
+	var idx = url.lastIndexOf('?');
+
+	if (idx > 0) {
+		var params = url.substring(idx + 1).split('&');
+
+		for (var i = 0; i < params.length; i++) {
+			idx = params[i].indexOf('=');
+
+			if (idx > 0) {
+				result[params[i].substring(0, idx)] = params[i].substring(idx + 1);
+			}
+		}
+	}
+
+	return result;
+})(window.location.href);
+
+var t0 = new Date();
+
+// Public global variables
+var MAX_REQUEST_SIZE = 10485760;
+var MAX_WIDTH = 12000;
+var MAX_HEIGHT = 12000;
+
+// CUSTOM_PARAMETERS - URLs for save and export
+var EXPORT_URL = 'http://exp.draw.io/ImageExport2/export';
+var SHARE_HOST = 'http://localhost:3000';
+
+var SAVE_URL = 'api/jscripty/projects/';
+var VARIABLE_URL = 'api/square/variables/';
+
+// Paths and files
+var STENCIL_PATH = '/assets/plugins/jscripty/stencils';
+var SHAPES_PATH = '/assets/plugins/mxgraph/shapes';
+var IMAGE_PATH = '/assets/plugins/mxgraph/images';
+// Path for images inside the diagram
+//var GRAPH_IMAGE_PATH = 'assets/plugins/mxgraph/img';
+//var ICONFINDER_PATH = 'iconfinder';
+var STYLE_PATH = '/assets/plugins/mxgraph/styles';
+var CSS_PATH = '/assets/plugins/mxgraph/styles';
+var OPEN_FORM = 'api/jscripty/projects/open';
+var TEMPLATE_PATH = '/assets/plugins/jscripty/templates';
+
+// Directory for i18 files and basename for main i18n file
+var RESOURCES_PATH = '/assets/plugins/jscripty/resources';
+var RESOURCE_BASE = RESOURCES_PATH + '/dia';
+
+// Specifies connection mode for touch devices (at least one should be true)
+var tapAndHoldStartsConnection = true;
+var showConnectorImg = true;
+
+var mxLoadResources = false;
+// var mxLanguage = '';
+console.log('custom', localStorage.getItem("NG_TRANSLATE_LANG_KEY"));
+switch (localStorage.getItem("NG_TRANSLATE_LANG_KEY")) {
+	case 'en_EN':
+		mxLanguage = 'en';
+		break;
+	case 'it':
+		mxLanguage = 'it';
+		break;
+	default:
+		mxLanguage = 'it';
+}
+console.log('mxLanguage', mxLanguage);
+
+var mxLanguages = ['ar', 'bs', 'cs', 'da', 'de', 'el', 'es', 'es-ar', 'fr',
+	'hu', 'id', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt-br', 'pt', 'ro', 'ru',
+	'sr', 'sv', 'th', 'tr', 'uk', 'zh'
+];
+var mxBasePath = 'assets/plugins/mxgraph';
+var geBasePath = 'assets/plugins/mxgraph/js';
+
+var ISPEECHLANG = {
+	usenglishfemale: "US English Female",
+	usenglishmale: "US English Male",
+	ukenglishfemale: "UK English Female",
+	ukenglishmale: "UK English Male",
+	auenglishfemale: "Australian English",
+	usspanishfemale: "US Spanish Female",
+	usspanishmale: "US Spanish Male",
+	chchinesefemale: "Chinese Female",
+	chchinesemale: "Chinese Male",
+	hkchinesefemale: "Hong Kong Cantonese Female",
+	twchinesefemale: "Taiwan Chinese Female",
+	jpjapanesefemale: "Japanese Female",
+	jpjapanesemale: "Japanese Male",
+	krkoreanfemale: "Korean Female",
+	krkoreanmale: "Korean Male",
+	caenglishfemale: "Canadian English Female",
+	huhungarianfemale: "Hungarian Female",
+	brportuguesefemale: "Brazilian Portuguese Female",
+	eurportuguesefemale: "European Portuguese Female",
+	eurportuguesemale: "European Portuguese Male",
+	eurspanishfemale: "European Spanish Female",
+	eurspanishmale: "European Spanish Male",
+	eurcatalanfemale: "European Catalan Female",
+	eurczechfemale: "European Czech Female",
+	eurdanishfemale: "European Danish Female",
+	eurfinnishfemale: "European Finnish Female",
+	eurfrenchfemale: "European French Female",
+	eurfrenchmale: "European French Male",
+	eurnorwegianfemale: "European Norwegian Female",
+	eurdutchfemale: "European Dutch Female",
+	eurpolishfemale: "European Polish Female",
+	euritalianfemale: "European Italian Female",
+	eurturkishfemale: "European Turkish Female	",
+	eurturkishmale: "European Turkish Male",
+	eurgermanfemale: "European German Female",
+	eurgermanmale: "European German Male",
+	rurussianfemale: "Russian Female",
+	rurussianmale: "Russian Male",
+	swswedishfemale: "Swedish Female",
+	cafrenchfemale: "Canadian French Female",
+	cafrenchmale: "Canadian French Male"
+};
+
+var ISPEECHASRLANG = {
+	"en-US": "English (US)",
+	"en-CA": "English (Canada)",
+	"en-GB": "English (UK)",
+	"en-AU": "English (Australia)",
+	"es-ES": "Spanish (Spain)",
+	"es-MX": "Spanish (Mexico)",
+	"it-IT": "Italian (Italy) / Finnish (Finland)",
+	"fr-FR": "French (France)",
+	"fr-CA": "French (Canada)",
+	"pl-PL": "Polish (Poland)",
+	"pt-PT": "Portuguese (Portugal)",
+	"ca-ES": "Catalan (Catalan)",
+	"zh-TW": "Chinese (Taiwan)",
+	"da-DK": "Danish (Denmark)",
+	"fr-FR": "German (Germany)",
+	"ja-JP": "Japanese (Japan)",
+	"ko-KR": "Korean (Korea)",
+	"nl-NL": "Dutch (Netherlands)",
+	"nb-NO": "Norwegian (Norway)",
+	"pt-BR": "Portuguese (Brazil)",
+	"ru-RU": "Russian (Russia)",
+	"sv-SE": "Swedish (Sweden)",
+	"zh-CN": "Chinese (China)",
+	"zh-HK": "Chinese (Hong Kong)"
+};
+
+var ISPEECHASRMODEL = {
+	0: "Default",
+	date: "Date",
+	usmoney: "US Money",
+	numbersto9: "Numbers to 9",
+	numbersto99: "Numbers to 99",
+	numbersto999: "Numbers to 999",
+	time: "Time",
+	phonenumber: "Phone number",
+	streets: "Streets",
+	citystate: "US City/States"
+};
+
+var GOOGLETTSLANG = {
+	"af": "Afrikaans",
+	"sq": "Albanian",
+	"am": "Amharic",
+	"ar": "Arabic",
+	"hy": "Armenian",
+	"az": "Azerbaijani",
+	"eu": "Basque",
+	"be": "Belarusian",
+	"bn": "Bengali",
+	"bh": "Bihari",
+	"bs": "Bosnian",
+	"br": "Breton",
+	"bg": "Bulgarian",
+	"km": "Cambodian",
+	"ca": "Catalan",
+	"zh-CN": "Chinese (Simplified)",
+	"zh-TW": "Chinese (Traditional)",
+	"co": "Corsican",
+	"hr": "Croatian",
+	"cs": "Czech",
+	"da": "Danish",
+	"nl": "Dutch",
+	"en": "English",
+	"eo": "Esperanto",
+	"et": "Estonian",
+	"fo": "Faroese",
+	"tl": "Filipino",
+	"fi": "Finnish",
+	"fr": "French",
+	"fy": "Frisian",
+	"gl": "Galician",
+	"ka": "Georgian",
+	"de": "German",
+	"el": "Greek",
+	"gn": "Guarani",
+	"gu": "Gujarati",
+	"xx-hacker": "Hacker",
+	"ha": "Hausa",
+	"iw": "Hebrew",
+	"hi": "Hindi",
+	"hu": "Hungarian",
+	"is": "Icelandic",
+	"id": "Indonesian",
+	"ia": "Interlingua",
+	"ga": "Irish",
+	"it": "Italian",
+	"jw": "Javanese",
+	"ja": "Japanese",
+	"kn": "Kannada",
+	"kk": "Kazakh",
+	"rw": "Kinyarwanda",
+	"rn": "Kirundi",
+	"xx-klingon": "Klingon",
+	"ku": "Kurdish",
+	"ko": "Korean",
+	"ky": "Kyrgyz",
+	"lo": "Laothian",
+	"la": "Latin",
+	"lv": "Latvian",
+	"ln": "Lingala",
+	"lt": "Lithuanian",
+	"mk": "Macedonian",
+	"mg": "Malagasy",
+	"ms": "Malay",
+	"ml": "Malayalam",
+	"mt": "Maltese",
+	"mi": "Maori",
+	"mr": "Marathi",
+	"mo": "Moldavian",
+	"mn": "Mongolian",
+	"sr-ME": "Montenegrin",
+	"ne": "Nepali",
+	"no": "Norwegian",
+	"nn": "Norwegian (Nynorsk)",
+	"oc": "Occitan",
+	"or": "Oriya",
+	"om": "Oromo",
+	"ps": "Pashto",
+	"fa": "Persian",
+	"xx-pirate": "Pirate",
+	"pl": "Polish",
+	"pt-BR": "Portuguese (Brazil)",
+	"pt-PT": "Portuguese (Portugal)",
+	"pa": "Punjabi",
+	"qu": "Quechua",
+	"ro": "Romanian",
+	"rm": "Romansh",
+	"ru": "Russian",
+	"gd": "Scots Gaelic",
+	"sr": "Serbian",
+	"sh": "Serbo-Croatian",
+	"st": "Sesotho",
+	"sn": "Shona",
+	"sd": "Sindhi",
+	"si": "Sinhalese",
+	"sk": "Slovak",
+	"sl": "Slovenian",
+	"so": "Somali",
+	"es": "Spanish",
+	"su": "Sundanese",
+	"sw": "Swahili",
+	"sv": "Swedish",
+	"tg": "Tajik",
+	"ta": "Tamil",
+	"tt": "Tatar",
+	"te": "Telugu",
+	"th": "Thai",
+	"ti": "Tigrinya",
+	"to": "Tonga",
+	"tr": "Turkish",
+	"tk": "Turkmen",
+	"tw": "Twi",
+	"ug": "Uighur",
+	"uk": "Ukrainian",
+	"ur": "Urdu",
+	"uz": "Uzbek",
+	"vi": "Vietnamese",
+	"cy": "Welsh",
+	"xh": "Xhosa",
+	"yi": "Yiddish",
+	"yo": "Yoruba",
+	"zu": "Zulu"
+};
diff --git a/public/assets/plugins/mxgraph/js/mxClient.js b/public/assets/plugins/mxgraph/js/mxClient.js
index 5ab5c2a..e795889 100644
--- a/public/assets/plugins/mxgraph/js/mxClient.js
+++ b/public/assets/plugins/mxgraph/js/mxClient.js
@@ -1 +1,18737 @@
\ No newline at end of file
+var mxClient = {
+  VERSION: "2.0.0.0",
+  IS_IE: 0 <= navigator.userAgent.indexOf("MSIE"),
+  IS_IE6: 0 <= navigator.userAgent.indexOf("MSIE 6"),
+  IS_QUIRKS: 0 <= navigator.userAgent.indexOf("MSIE") && (null == document.documentMode || 5 == document.documentMode),
+  VML_PREFIX: "v",
+  OFFICE_PREFIX: "o",
+  IS_NS: 0 <= navigator.userAgent.indexOf("Mozilla/") && 0 > navigator.userAgent.indexOf("MSIE"),
+  IS_OP: 0 <= navigator.userAgent.indexOf("Opera/"),
+  IS_OT: 0 > navigator.userAgent.indexOf("Presto/2.4.") && 0 > navigator.userAgent.indexOf("Presto/2.3.") && 0 > navigator.userAgent.indexOf("Presto/2.2.") &&
+    0 > navigator.userAgent.indexOf("Presto/2.1.") && 0 > navigator.userAgent.indexOf("Presto/2.0.") && 0 > navigator.userAgent.indexOf("Presto/1."),
+  IS_SF: 0 <= navigator.userAgent.indexOf("AppleWebKit/") && 0 > navigator.userAgent.indexOf("Chrome/"),
+  IS_GC: 0 <= navigator.userAgent.indexOf("Chrome/"),
+  IS_MT: 0 <= navigator.userAgent.indexOf("Firefox/") && 0 > navigator.userAgent.indexOf("Firefox/1.") && 0 > navigator.userAgent.indexOf("Firefox/2.") || 0 <= navigator.userAgent.indexOf(
+      "Iceweasel/") && 0 > navigator.userAgent.indexOf("Iceweasel/1.") &&
+    0 > navigator.userAgent.indexOf("Iceweasel/2.") || 0 <= navigator.userAgent.indexOf("SeaMonkey/") && 0 > navigator.userAgent.indexOf("SeaMonkey/1.") || 0 <= navigator.userAgent.indexOf(
+      "Iceape/") && 0 > navigator.userAgent.indexOf("Iceape/1."),
+  IS_SVG: 0 <= navigator.userAgent.indexOf("Firefox/") || 0 <= navigator.userAgent.indexOf("Iceweasel/") || 0 <= navigator.userAgent.indexOf("Seamonkey/") || 0 <= navigator.userAgent.indexOf(
+      "Iceape/") || 0 <= navigator.userAgent.indexOf("Galeon/") || 0 <= navigator.userAgent.indexOf("Epiphany/") || 0 <=
+    navigator.userAgent.indexOf("AppleWebKit/") || 0 <= navigator.userAgent.indexOf("Gecko/") || 0 <= navigator.userAgent.indexOf("Opera/"),
+  NO_FO: 0 <= navigator.userAgent.indexOf("Firefox/1.") || 0 <= navigator.userAgent.indexOf("Iceweasel/1.") || 0 <= navigator.userAgent.indexOf("Firefox/2.") || 0 <= navigator.userAgent.indexOf(
+      "Iceweasel/2.") || 0 <= navigator.userAgent.indexOf("SeaMonkey/1.") || 0 <= navigator.userAgent.indexOf("Iceape/1.") || 0 <= navigator.userAgent.indexOf("Camino/1.") || 0 <= navigator.userAgent
+    .indexOf("Epiphany/2.") ||
+    0 <= navigator.userAgent.indexOf("Opera/") || 0 <= navigator.userAgent.indexOf("MSIE") || 0 <= navigator.userAgent.indexOf("Mozilla/2."),
+  IS_VML: "MICROSOFT INTERNET EXPLORER" == navigator.appName.toUpperCase(),
+  IS_MAC: 0 < navigator.userAgent.toUpperCase().indexOf("MACINTOSH"),
+  IS_TOUCH: 0 < navigator.userAgent.toUpperCase().indexOf("IPAD") || 0 < navigator.userAgent.toUpperCase().indexOf("IPOD") || 0 < navigator.userAgent.toUpperCase().indexOf("IPHONE") || 0 <
+    navigator.userAgent.toUpperCase().indexOf("ANDROID"),
+  IS_LOCAL: 0 > document.location.href.indexOf("http://") &&
+    0 > document.location.href.indexOf("https://"),
+  isBrowserSupported: function() {
+    return mxClient.IS_VML || mxClient.IS_SVG
+  },
+  link: function(a, b, c) {
+    c = c || document;
+    if (mxClient.IS_IE6) c.write('\x3clink rel\x3d"' + a + '" href\x3d"' + b + '" charset\x3d"ISO-8859-1" type\x3d"text/css"/\x3e');
+    else {
+      var d = c.createElement("link");
+      d.setAttribute("rel", a);
+      d.setAttribute("href", b);
+      d.setAttribute("charset", "ISO-8859-1");
+      d.setAttribute("type", "text/css");
+      c.getElementsByTagName("head")[0].appendChild(d)
+    }
+  },
+  include: function(a) {
+    document.write('\x3cscript src\x3d"' +
+      a + '"\x3e\x3c/script\x3e')
+  },
+  dispose: function() {
+    for (var a = 0; a < mxEvent.objects.length; a++) null != mxEvent.objects[a].mxListenerList && mxEvent.removeAllListeners(mxEvent.objects[a])
+  }
+};
+mxClient.IS_OP && mxClient.IS_OT ? mxClient.CSS_PREFIX = "O" : mxClient.IS_SF || mxClient.IS_GC ? mxClient.CSS_PREFIX = "Webkit" : mxClient.IS_MT ? mxClient.CSS_PREFIX = "Moz" : mxClient.IS_IE && 9 <=
+  document.documentMode && (mxClient.CSS_PREFIX = "ms");
+"undefined" == typeof mxLoadResources && (mxLoadResources = !0);
+"undefined" == typeof mxResourceExtension && (mxResourceExtension = ".txt");
+"undefined" == typeof mxLoadStylesheets && (mxLoadStylesheets = !0);
+"undefined" != typeof mxBasePath && 0 < mxBasePath.length ? ("/" == mxBasePath.substring(mxBasePath.length - 1) && (mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1)), mxClient.basePath =
+  mxBasePath) : mxClient.basePath = ".";
+"undefined" != typeof mxImageBasePath && 0 < mxImageBasePath.length ? ("/" == mxImageBasePath.substring(mxImageBasePath.length - 1) && (mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length -
+  1)), mxClient.imageBasePath = mxImageBasePath) : mxClient.imageBasePath = mxClient.basePath + "/images";
+mxClient.language = "undefined" != typeof mxLanguage ? mxLanguage : mxClient.IS_IE ? navigator.userLanguage : navigator.language;
+mxClient.defaultLanguage = "undefined" != typeof mxDefaultLanguage ? mxDefaultLanguage : "en";
+mxLoadStylesheets && mxClient.link("stylesheet", mxClient.basePath + "/css/common.css");
+"undefined" != typeof mxLanguages && (mxClient.languages = mxLanguages);
+if (mxClient.IS_IE) {
+  if (9 <= document.documentMode) mxClient.IS_VML = !1, mxClient.IS_SVG = !0;
+  else {
+    8 == document.documentMode ? (document.namespaces.add(mxClient.VML_PREFIX, "urn:schemas-microsoft-com:vml", "#default#VML"), document.namespaces.add(mxClient.OFFICE_PREFIX,
+      "urn:schemas-microsoft-com:office:office", "#default#VML")) : (document.namespaces.add(mxClient.VML_PREFIX, "urn:schemas-microsoft-com:vml"), document.namespaces.add(mxClient.OFFICE_PREFIX,
+      "urn:schemas-microsoft-com:office:office"));
+    var ss = document.createStyleSheet();
+    ss.cssText = "v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}";
+    mxLoadStylesheets && mxClient.link("stylesheet", mxClient.basePath + "/css/explorer.css")
+  }
+  window.attachEvent("onunload", mxClient.dispose)
+}
+var mxLog = {
+    consoleName: "Console",
+    TRACE: !1,
+    DEBUG: !0,
+    WARN: !0,
+    buffer: "",
+    init: function() {
+      if (null == mxLog.window && null != document.body) {
+        var a = mxLog.consoleName + " - mxGraph " + mxClient.VERSION,
+          b = document.createElement("table");
+        b.setAttribute("width", "100%");
+        b.setAttribute("height", "100%");
+        var c = document.createElement("tbody"),
+          d = document.createElement("tr"),
+          e = document.createElement("td");
+        e.style.verticalAlign = "top";
+        mxLog.textarea = document.createElement("textarea");
+        mxLog.textarea.setAttribute("readOnly", "true");
+        mxLog.textarea.style.height = "100%";
+        mxLog.textarea.style.resize = "none";
+        mxLog.textarea.value = mxLog.buffer;
+        mxLog.textarea.style.width = mxClient.IS_NS && "BackCompat" != document.compatMode ? "99%" : "100%";
+        e.appendChild(mxLog.textarea);
+        d.appendChild(e);
+        c.appendChild(d);
+        d = document.createElement("tr");
+        mxLog.td = document.createElement("td");
+        mxLog.td.style.verticalAlign = "top";
+        mxLog.td.setAttribute("height", "30px");
+        d.appendChild(mxLog.td);
+        c.appendChild(d);
+        b.appendChild(c);
+        mxLog.addButton("Info", function(a) {
+          mxLog.info()
+        });
+        mxLog.addButton("DOM", function(a) {
+          a = mxUtils.getInnerHtml(document.body);
+          mxLog.debug(a)
+        });
+        mxLog.addButton("Trace", function(a) {
+          mxLog.TRACE = !mxLog.TRACE;
+          mxLog.TRACE ? mxLog.debug("Tracing enabled") : mxLog.debug("Tracing disabled")
+        });
+        mxLog.addButton("Copy", function(a) {
+          try {
+            mxUtils.copy(mxLog.textarea.value)
+          } catch (b) {
+            mxUtils.alert(b)
+          }
+        });
+        mxLog.addButton("Show", function(a) {
+          try {
+            mxUtils.popup(mxLog.textarea.value)
+          } catch (b) {
+            mxUtils.alert(b)
+          }
+        });
+        mxLog.addButton("Clear", function(a) {
+          mxLog.textarea.value = ""
+        });
+        d = c = 0;
+        "number" === typeof window.innerWidth ? (c = window.innerHeight, d = window.innerWidth) : (c = document.documentElement.clientHeight || document.body.clientHeight, d = document.body.clientWidth);
+        mxLog.window = new mxWindow(a, b, Math.max(0, d - 320), Math.max(0, c - 210), 300, 160);
+        mxLog.window.setMaximizable(!0);
+        mxLog.window.setScrollable(!1);
+        mxLog.window.setResizable(!0);
+        mxLog.window.setClosable(!0);
+        mxLog.window.destroyOnClose = !1;
+        if ((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC && !mxClient.IS_SF && "BackCompat" != document.compatMode) {
+          var f =
+            mxLog.window.getElement(),
+            a = function(a, b) {
+              mxLog.textarea.style.height = Math.max(0, f.offsetHeight - 70) + "px"
+            };
+          mxLog.window.addListener(mxEvent.RESIZE_END, a);
+          mxLog.window.addListener(mxEvent.MAXIMIZE, a);
+          mxLog.window.addListener(mxEvent.NORMALIZE, a);
+          mxLog.textarea.style.height = "92px"
+        }
+      }
+    },
+    info: function() {
+      mxLog.writeln(mxUtils.toString(navigator))
+    },
+    addButton: function(a, b) {
+      var c = document.createElement("button");
+      mxUtils.write(c, a);
+      mxEvent.addListener(c, "click", b);
+      mxLog.td.appendChild(c)
+    },
+    isVisible: function() {
+      return null !=
+        mxLog.window ? mxLog.window.isVisible() : !1
+    },
+    show: function() {
+      mxLog.setVisible(!0)
+    },
+    setVisible: function(a) {
+      null == mxLog.window && mxLog.init();
+      null != mxLog.window && mxLog.window.setVisible(a)
+    },
+    enter: function(a) {
+      if (mxLog.TRACE) return mxLog.writeln("Entering " + a), (new Date).getTime()
+    },
+    leave: function(a, b) {
+      if (mxLog.TRACE) {
+        var c = 0 != b ? " (" + ((new Date).getTime() - b) + " ms)" : "";
+        mxLog.writeln("Leaving " + a + c)
+      }
+    },
+    debug: function() {
+      mxLog.DEBUG && mxLog.writeln.apply(this, arguments)
+    },
+    warn: function() {
+      mxLog.WARN && mxLog.writeln.apply(this,
+        arguments)
+    },
+    write: function() {
+      for (var a = "", b = 0; b < arguments.length; b++) a += arguments[b], b < arguments.length - 1 && (a += " ");
+      null != mxLog.textarea ? (mxLog.textarea.value += a, 0 <= navigator.userAgent.indexOf("Presto/2.5") && (mxLog.textarea.style.visibility = "hidden", mxLog.textarea.style.visibility = "visible"),
+        mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight) : mxLog.buffer += a
+    },
+    writeln: function() {
+      for (var a = "", b = 0; b < arguments.length; b++) a += arguments[b], b < arguments.length - 1 && (a += " ");
+      mxLog.write(a + "\n")
+    }
+  },
+  mxObjectIdentity = {
+    FIELD_NAME: "mxObjectId",
+    counter: 0,
+    get: function(a) {
+      if ("object" == typeof a && null == a[mxObjectIdentity.FIELD_NAME]) {
+        var b = mxUtils.getFunctionName(a.constructor);
+        a[mxObjectIdentity.FIELD_NAME] = b + "#" + mxObjectIdentity.counter++
+      }
+      return a[mxObjectIdentity.FIELD_NAME]
+    },
+    clear: function(a) {
+      "object" == typeof a && delete a[mxObjectIdentity.FIELD_NAME]
+    }
+  };
+
+function mxDictionary() {
+  this.clear()
+}
+mxDictionary.prototype.map = null;
+mxDictionary.prototype.clear = function() {
+  this.map = {}
+};
+mxDictionary.prototype.get = function(a) {
+  a = mxObjectIdentity.get(a);
+  return this.map[a]
+};
+mxDictionary.prototype.put = function(a, b) {
+  var c = mxObjectIdentity.get(a),
+    d = this.map[c];
+  this.map[c] = b;
+  return d
+};
+mxDictionary.prototype.remove = function(a) {
+  a = mxObjectIdentity.get(a);
+  var b = this.map[a];
+  delete this.map[a];
+  return b
+};
+mxDictionary.prototype.getKeys = function() {
+  var a = [],
+    b;
+  for (b in this.map) a.push(b);
+  return a
+};
+mxDictionary.prototype.getValues = function() {
+  var a = [],
+    b;
+  for (b in this.map) a.push(this.map[b]);
+  return a
+};
+mxDictionary.prototype.visit = function(a) {
+  for (var b in this.map) a(b, this.map[b])
+};
+var mxResources = {
+  resources: [],
+  extension: mxResourceExtension,
+  resourcesEncoded: !1,
+  loadDefaultBundle: !0,
+  loadSpecialBundle: !0,
+  isLanguageSupported: function(a) {
+    return null != mxClient.languages ? 0 <= mxUtils.indexOf(mxClient.languages, a) : !0
+  },
+  getDefaultBundle: function(a, b) {
+    return mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(b) ? a + mxResources.extension : null
+  },
+  getSpecialBundle: function(a, b) {
+    if (null == mxClient.languages || !this.isLanguageSupported(b)) {
+      var c = b.indexOf("-");
+      0 < c && (b = b.substring(0, c))
+    }
+    return mxResources.loadSpecialBundle &&
+      mxResources.isLanguageSupported(b) && b != mxClient.defaultLanguage ? a + "_" + b + mxResources.extension : null
+  },
+  add: function(a, b) {
+    b = null != b ? b : mxClient.language.toLowerCase();
+    if (b != mxConstants.NONE) {
+      var c = mxResources.getDefaultBundle(a, b);
+      if (null != c) try {
+        var d = mxUtils.load(c);
+        d.isReady() && mxResources.parse(d.getText())
+      } catch (e) {}
+      c = mxResources.getSpecialBundle(a, b);
+      if (null != c) try {
+        d = mxUtils.load(c), d.isReady() && mxResources.parse(d.getText())
+      } catch (f) {}
+    }
+  },
+  parse: function(a) {
+    if (null != a) {
+      a = a.split("\n");
+      for (var b = 0; b <
+        a.length; b++)
+        if ("#" != a[b].charAt(0)) {
+          var c = a[b].indexOf("\x3d");
+          if (0 < c) {
+            var d = a[b].substring(0, c),
+              e = a[b].length;
+            13 == a[b].charCodeAt(e - 1) && e--;
+            c = a[b].substring(c + 1, e);
+            this.resourcesEncoded ? (c = c.replace(/\\(?=u[a-fA-F\d]{4})/g, "%"), mxResources.resources[d] = unescape(c)) : mxResources.resources[d] = c
+          }
+        }
+    }
+  },
+  get: function(a, b, c) {
+    a = mxResources.resources[a];
+    null == a && (a = c);
+    if (null != a && null != b) {
+      c = [];
+      for (var d = null, e = 0; e < a.length; e++) {
+        var f = a.charAt(e);
+        "{" == f ? d = "" : null != d && "}" == f ? (d = parseInt(d) - 1, 0 <= d && d < b.length &&
+          c.push(b[d]), d = null) : null != d ? d += f : c.push(f)
+      }
+      a = c.join("")
+    }
+    return a
+  }
+};
+
+function mxPoint(a, b) {
+  this.x = null != a ? a : 0;
+  this.y = null != b ? b : 0
+}
+mxPoint.prototype.x = null;
+mxPoint.prototype.y = null;
+mxPoint.prototype.equals = function(a) {
+  return a.x == this.x && a.y == this.y
+};
+mxPoint.prototype.clone = function() {
+  return mxUtils.clone(this)
+};
+
+function mxRectangle(a, b, c, d) {
+  mxPoint.call(this, a, b);
+  this.width = null != c ? c : 0;
+  this.height = null != d ? d : 0
+}
+mxRectangle.prototype = new mxPoint;
+mxRectangle.prototype.constructor = mxRectangle;
+mxRectangle.prototype.width = null;
+mxRectangle.prototype.height = null;
+mxRectangle.prototype.setRect = function(a, b, c, d) {
+  this.x = a;
+  this.y = b;
+  this.width = c;
+  this.height = d
+};
+mxRectangle.prototype.getCenterX = function() {
+  return this.x + this.width / 2
+};
+mxRectangle.prototype.getCenterY = function() {
+  return this.y + this.height / 2
+};
+mxRectangle.prototype.add = function(a) {
+  if (null != a) {
+    var b = Math.min(this.x, a.x),
+      c = Math.min(this.y, a.y),
+      d = Math.max(this.x + this.width, a.x + a.width);
+    a = Math.max(this.y + this.height, a.y + a.height);
+    this.x = b;
+    this.y = c;
+    this.width = d - b;
+    this.height = a - c
+  }
+};
+mxRectangle.prototype.grow = function(a) {
+  this.x -= a;
+  this.y -= a;
+  this.width += 2 * a;
+  this.height += 2 * a
+};
+mxRectangle.prototype.getPoint = function() {
+  return new mxPoint(this.x, this.y)
+};
+mxRectangle.prototype.equals = function(a) {
+  return a.x == this.x && a.y == this.y && a.width == this.width && a.height == this.height
+};
+var mxEffects = {
+    animateChanges: function(a, b, c) {
+      var d = 0,
+        e = function() {
+          for (var g = !1, h = 0; h < b.length; h++) {
+            var k = b[h];
+            if (k instanceof mxGeometryChange || k instanceof mxTerminalChange || k instanceof mxValueChange || k instanceof mxChildChange || k instanceof mxStyleChange) {
+              var l = a.getView().getState(k.cell || k.child, !1);
+              if (null != l)
+                if (g = !0, k.constructor != mxGeometryChange || a.model.isEdge(k.cell)) mxUtils.setOpacity(l.shape.node, 100 * d / 10);
+                else {
+                  var m = a.getView().scale,
+                    n = (k.geometry.x - k.previous.x) * m,
+                    p = (k.geometry.y -
+                      k.previous.y) * m,
+                    q = (k.geometry.width - k.previous.width) * m,
+                    m = (k.geometry.height - k.previous.height) * m;
+                  0 == d ? (l.x -= n, l.y -= p, l.width -= q, l.height -= m) : (l.x += n / 10, l.y += p / 10, l.width += q / 10, l.height += m / 10);
+                  a.cellRenderer.redraw(l);
+                  mxEffects.cascadeOpacity(a, k.cell, 100 * d / 10)
+                }
+            }
+          }
+          10 > d && g ? (d++, window.setTimeout(e, f)) : null != c && c()
+        },
+        f = 30;
+      e()
+    },
+    cascadeOpacity: function(a, b, c) {
+      for (var d = a.model.getChildCount(b), e = 0; e < d; e++) {
+        var f = a.model.getChildAt(b, e),
+          g = a.getView().getState(f);
+        null != g && (mxUtils.setOpacity(g.shape.node,
+          c), mxEffects.cascadeOpacity(a, f, c))
+      }
+      b = a.model.getEdges(b);
+      if (null != b)
+        for (e = 0; e < b.length; e++) d = a.getView().getState(b[e]), null != d && mxUtils.setOpacity(d.shape.node, c)
+    },
+    fadeOut: function(a, b, c, d, e, f) {
+      d = d || 40;
+      e = e || 30;
+      var g = b || 100;
+      mxUtils.setOpacity(a, g);
+      if (f || null == f) {
+        var h = function() {
+          g = Math.max(g - d, 0);
+          mxUtils.setOpacity(a, g);
+          0 < g ? window.setTimeout(h, e) : (a.style.visibility = "hidden", c && a.parentNode && a.parentNode.removeChild(a))
+        };
+        window.setTimeout(h, e)
+      } else a.style.visibility = "hidden", c && a.parentNode && a.parentNode.removeChild(a)
+    }
+  },
+  mxUtils = {
+    errorResource: "none" != mxClient.language ? "error" : "",
+    closeResource: "none" != mxClient.language ? "close" : "",
+    errorImage: mxClient.imageBasePath + "/error.gif",
+    removeCursors: function(a) {
+      null != a.style && (a.style.cursor = "");
+      a = a.childNodes;
+      if (null != a)
+        for (var b = a.length, c = 0; c < b; c += 1) mxUtils.removeCursors(a[c])
+    },
+    getCurrentStyle: function() {
+      return mxClient.IS_IE ? function(a) {
+        return null != a ? a.currentStyle : null
+      } : function(a) {
+        return null != a ? window.getComputedStyle(a, "") : null
+      }
+    }(),
+    hasScrollbars: function(a) {
+      a = mxUtils.getCurrentStyle(a);
+      return null != a && ("scroll" == a.overflow || "auto" == a.overflow)
+    },
+    bind: function(a, b) {
+      return function() {
+        return b.apply(a, arguments)
+      }
+    },
+    eval: function(a) {
+      var b = null;
+      if (0 <= a.indexOf("function")) try {
+        eval("var _mxJavaScriptExpression\x3d" + a), b = _mxJavaScriptExpression, _mxJavaScriptExpression = null
+      } catch (c) {
+        mxLog.warn(c.message + " while evaluating " + a)
+      } else try {
+        b = eval(a)
+      } catch (d) {
+        mxLog.warn(d.message + " while evaluating " + a)
+      }
+      return b
+    },
+    findNode: function(a, b, c) {
+      var d = a.getAttribute(b);
+      if (null != d && d == c) return a;
+      for (a =
+        a.firstChild; null != a;) {
+        d = mxUtils.findNode(a, b, c);
+        if (null != d) return d;
+        a = a.nextSibling
+      }
+      return null
+    },
+    findNodeByAttribute: function() {
+      return 9 <= document.documentMode ? function(a, b, c) {
+        var d = null;
+        if (null != a)
+          if (a.nodeType == mxConstants.NODETYPE_ELEMENT && a.getAttribute(b) == c) d = a;
+          else
+            for (a = a.firstChild; null != a && null == d;) d = mxUtils.findNodeByAttribute(a, b, c), a = a.nextSibling;
+        return d
+      } : mxClient.IS_IE ? function(a, b, c) {
+        return null == a ? null : a.ownerDocument.selectSingleNode("//*[@" + b + "\x3d'" + c + "']")
+      } : function(a, b, c) {
+        return null ==
+          a ? null : a.ownerDocument.evaluate("//*[@" + b + "\x3d'" + c + "']", a.ownerDocument, null, XPathResult.ANY_TYPE, null).iterateNext()
+      }
+    }(),
+    getFunctionName: function(a) {
+      var b = null;
+      if (null != a)
+        if (null != a.name) b = a.name;
+        else {
+          a = a.toString();
+          for (b = 9;
+            " " == a.charAt(b);) b++;
+          var c = a.indexOf("(", b),
+            b = a.substring(b, c)
+        }
+      return b
+    },
+    indexOf: function(a, b) {
+      if (null != a && null != b)
+        for (var c = 0; c < a.length; c++)
+          if (a[c] == b) return c;
+      return -1
+    },
+    remove: function(a, b) {
+      var c = null;
+      if ("object" == typeof b)
+        for (var d = mxUtils.indexOf(b, a); 0 <= d;) b.splice(d,
+          1), c = a, d = mxUtils.indexOf(b, a);
+      for (var e in b) b[e] == a && (delete b[e], c = a);
+      return c
+    },
+    isNode: function(a, b, c, d) {
+      return null != a && !isNaN(a.nodeType) && (null == b || a.nodeName.toLowerCase() == b.toLowerCase()) ? null == c || a.getAttribute(c) == d : !1
+    },
+    getChildNodes: function(a, b) {
+      b = b || mxConstants.NODETYPE_ELEMENT;
+      for (var c = [], d = a.firstChild; null != d;) d.nodeType == b && c.push(d), d = d.nextSibling;
+      return c
+    },
+    createXmlDocument: function() {
+      var a = null;
+      document.implementation && document.implementation.createDocument ? a = document.implementation.createDocument("",
+        "", null) : window.ActiveXObject && (a = new ActiveXObject("Microsoft.XMLDOM"));
+      return a
+    },
+    parseXml: function() {
+      return mxClient.IS_IE && ("undefined" === typeof document.documentMode || 9 > document.documentMode) ? function(a) {
+        var b = mxUtils.createXmlDocument();
+        b.async = "false";
+        b.loadXML(a);
+        return b
+      } : function(a) {
+        return (new DOMParser).parseFromString(a, "text/xml")
+      }
+    }(),
+    clearSelection: function() {
+      if (document.selection) return function() {
+        document.selection.empty()
+      };
+      if (window.getSelection) return function() {
+        window.getSelection().removeAllRanges()
+      }
+    }(),
+    getPrettyXml: function(a, b, c) {
+      var d = [];
+      if (null != a)
+        if (b = b || "  ", c = c || "", a.nodeType == mxConstants.NODETYPE_TEXT) d.push(a.nodeValue);
+        else {
+          d.push(c + "\x3c" + a.nodeName);
+          var e = a.attributes;
+          if (null != e)
+            for (var f = 0; f < e.length; f++) {
+              var g = mxUtils.htmlEntities(e[f].nodeValue);
+              d.push(" " + e[f].nodeName + '\x3d"' + g + '"')
+            }
+          e = a.firstChild;
+          if (null != e) {
+            for (d.push("\x3e\n"); null != e;) d.push(mxUtils.getPrettyXml(e, b, c + b)), e = e.nextSibling;
+            d.push(c + "\x3c/" + a.nodeName + "\x3e\n")
+          } else d.push("/\x3e\n")
+        }
+      return d.join("")
+    },
+    removeWhitespace: function(a,
+      b) {
+      for (var c = b ? a.previousSibling : a.nextSibling; null != c && c.nodeType == mxConstants.NODETYPE_TEXT;) {
+        var d = b ? c.previousSibling : c.nextSibling,
+          e = mxUtils.getTextContent(c);
+        0 == mxUtils.trim(e).length && c.parentNode.removeChild(c);
+        c = d
+      }
+    },
+    htmlEntities: function(a, b) {
+      a = (a || "").replace(/&/g, "\x26amp;");
+      a = a.replace(/"/g, "\x26quot;");
+      a = a.replace(/\'/g, "\x26#39;");
+      a = a.replace(/</g, "\x26lt;");
+      a = a.replace(/>/g, "\x26gt;");
+      if (null == b || b) a = a.replace(/\n/g, "\x26#xa;");
+      return a
+    },
+    isVml: function(a) {
+      return null != a && "urn:schemas-microsoft-com:vml" ==
+        a.tagUrn
+    },
+    getXml: function(a, b) {
+      var c = "";
+      null != a && (c = a.xml, c = null == c ? a.innerHTML ? a.innerHTML : (new XMLSerializer).serializeToString(a) : c.replace(/\r\n\t[\t]*/g, "").replace(/>\r\n/g, "\x3e").replace(/\r\n/g, "\n"));
+      return c = c.replace(/\n/g, b || "\x26#xa;")
+    },
+    getTextContent: function(a) {
+      var b = "";
+      null != a && (null != a.firstChild && (a = a.firstChild), b = a.nodeValue || "");
+      return b
+    },
+    getInnerHtml: function() {
+      return mxClient.IS_IE ? function(a) {
+        return null != a ? a.innerHTML : ""
+      } : function(a) {
+        return null != a ? (new XMLSerializer).serializeToString(a) :
+          ""
+      }
+    }(),
+    getOuterHtml: function() {
+      return mxClient.IS_IE ? function(a) {
+        if (null != a) {
+          if (null != a.outerHTML) return a.outerHTML;
+          var b = [];
+          b.push("\x3c" + a.nodeName);
+          var c = a.attributes;
+          if (null != c)
+            for (var d = 0; d < c.length; d++) {
+              var e = c[d].nodeValue;
+              null != e && 0 < e.length && (b.push(" "), b.push(c[d].nodeName), b.push('\x3d"'), b.push(e), b.push('"'))
+            }
+          0 == a.innerHTML.length ? b.push("/\x3e") : (b.push("\x3e"), b.push(a.innerHTML), b.push("\x3c/" + a.nodeName + "\x3e"));
+          return b.join("")
+        }
+        return ""
+      } : function(a) {
+        return null != a ? (new XMLSerializer).serializeToString(a) :
+          ""
+      }
+    }(),
+    write: function(a, b) {
+      var c = a.ownerDocument.createTextNode(b);
+      null != a && a.appendChild(c);
+      return c
+    },
+    writeln: function(a, b) {
+      var c = a.ownerDocument.createTextNode(b);
+      null != a && (a.appendChild(c), a.appendChild(document.createElement("br")));
+      return c
+    },
+    br: function(a, b) {
+      b = b || 1;
+      for (var c = null, d = 0; d < b; d++) null != a && (c = a.ownerDocument.createElement("br"), a.appendChild(c));
+      return c
+    },
+    button: function(a, b, c) {
+      c = null != c ? c : document;
+      c = c.createElement("button");
+      mxUtils.write(c, a);
+      mxEvent.addListener(c, "click", function(a) {
+        b(a)
+      });
+      return c
+    },
+    para: function(a, b) {
+      var c = document.createElement("p");
+      mxUtils.write(c, b);
+      null != a && a.appendChild(c);
+      return c
+    },
+    addTransparentBackgroundFilter: function(a) {
+      a.style.filter += "progid:DXImageTransform.Microsoft.AlphaImageLoader(src\x3d'" + mxClient.imageBasePath + "/transparent.gif', sizingMethod\x3d'scale')"
+    },
+    linkAction: function(a, b, c, d, e) {
+      return mxUtils.link(a, b, function() {
+        c.execute(d)
+      }, e)
+    },
+    linkInvoke: function(a, b, c, d, e, f) {
+      return mxUtils.link(a, b, function() {
+        c[d](e)
+      }, f)
+    },
+    link: function(a, b, c, d) {
+      var e =
+        document.createElement("span");
+      e.style.color = "blue";
+      e.style.textDecoration = "underline";
+      e.style.cursor = "pointer";
+      null != d && (e.style.paddingLeft = d + "px");
+      mxEvent.addListener(e, "click", c);
+      mxUtils.write(e, b);
+      null != a && a.appendChild(e);
+      return e
+    },
+    fit: function(a) {
+      var b = parseInt(a.offsetLeft),
+        c = parseInt(a.offsetWidth),
+        d = document.body,
+        e = document.documentElement,
+        f = (d.scrollLeft || e.scrollLeft) + (d.clientWidth || e.clientWidth);
+      b + c > f && (a.style.left = Math.max(d.scrollLeft || e.scrollLeft, f - c) + "px");
+      b = parseInt(a.offsetTop);
+      c = parseInt(a.offsetHeight);
+      f = (d.scrollTop || e.scrollTop) + Math.max(d.clientHeight || 0, e.clientHeight);
+      b + c > f && (a.style.top = Math.max(d.scrollTop || e.scrollTop, f - c) + "px")
+    },
+    open: function(a) {
+      if (mxClient.IS_NS) {
+        try {
+          netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect")
+        } catch (b) {
+          return mxUtils.alert("Permission to read file denied."), ""
+        }
+        var c = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
+        c.initWithPath(a);
+        if (!c.exists()) return mxUtils.alert("File not found."),
+          "";
+        a = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
+        a.init(c, 1, 4, null);
+        c = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
+        c.init(a);
+        return c.read(c.available())
+      }
+      c = (new ActiveXObject("Scripting.FileSystemObject")).OpenTextFile(a, 1);
+      a = c.readAll();
+      c.close();
+      return a
+    },
+    save: function(a, b) {
+      if (mxClient.IS_NS) {
+        try {
+          netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect")
+        } catch (c) {
+          mxUtils.alert("Permission to write file denied.");
+          return
+        }
+        var d = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
+        d.initWithPath(a);
+        d.exists() || d.create(0, 420);
+        var e = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
+        e.init(d, 34, 4, null);
+        e.write(b, b.length);
+        e.flush();
+        e.close()
+      } else d = (new ActiveXObject("Scripting.FileSystemObject")).CreateTextFile(a, !0), d.Write(b), d.Close()
+    },
+    saveAs: function(a) {
+      var b = document.createElement("iframe");
+      b.setAttribute("src", "");
+      b.style.visibility = "hidden";
+      document.body.appendChild(b);
+      try {
+        if (mxClient.IS_NS) {
+          var c = b.contentDocument;
+          c.open();
+          c.write(a);
+          c.close();
+          try {
+            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"), b.focus(), saveDocument(c)
+          } catch (d) {
+            mxUtils.alert("Permission to save document denied.")
+          }
+        } else c = b.contentWindow.document, c.write(a), c.execCommand("SaveAs", !1, document.location)
+      } finally {
+        document.body.removeChild(b)
+      }
+    },
+    copy: function(a) {
+      if (window.clipboardData) window.clipboardData.setData("Text",
+        a);
+      else {
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        var b = Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard);
+        if (b) {
+          var c = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
+          if (c) {
+            c.addDataFlavor("text/unicode");
+            var d = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
+            d.data = a;
+            c.setTransferData("text/unicode",
+              d, 2 * a.length);
+            b.setData(c, null, Components.interfaces.nsIClipboard.kGlobalClipboard)
+          }
+        }
+      }
+    },
+    load: function(a) {
+      a = new mxXmlRequest(a, null, "GET", !1);
+      a.send();
+      return a
+    },
+    get: function(a, b, c) {
+      return (new mxXmlRequest(a, null, "GET")).send(b, c)
+    },
+    post: function(a, b, c, d) {
+      return (new mxXmlRequest(a, b)).send(c, d)
+    },
+    submit: function(a, b, c, d) {
+      return (new mxXmlRequest(a, b)).simulate(c, d)
+    },
+    loadInto: function(a, b, c) {
+      mxClient.IS_IE ? b.onreadystatechange = function() {
+        4 == b.readyState && c()
+      } : b.addEventListener("load", c, !1);
+      b.load(a)
+    },
+    getValue: function(a, b, c) {
+      a = null != a ? a[b] : null;
+      null == a && (a = c);
+      return a
+    },
+    getNumber: function(a, b, c) {
+      a = null != a ? a[b] : null;
+      null == a && (a = c || 0);
+      return Number(a)
+    },
+    getColor: function(a, b, c) {
+      a = null != a ? a[b] : null;
+      null == a ? a = c : a == mxConstants.NONE && (a = null);
+      return a
+    },
+    clone: function(a, b, c) {
+      c = null != c ? c : !1;
+      var d = null;
+      if (null != a && "function" == typeof a.constructor) {
+        var d = new a.constructor,
+          e;
+        for (e in a)
+          if (e != mxObjectIdentity.FIELD_NAME && (null == b || 0 > mxUtils.indexOf(b, e))) d[e] = !c && "object" == typeof a[e] ? mxUtils.clone(a[e]) : a[e]
+      }
+      return d
+    },
+    equalPoints: function(a, b) {
+      if (null == a && null != b || null != a && null == b || null != a && null != b && a.length != b.length) return !1;
+      if (null != a && null != b)
+        for (var c = 0; c < a.length; c++)
+          if (a[c] == b[c] || null != a[c] && !a[c].equals(b[c])) return !1;
+      return !0
+    },
+    equalEntries: function(a, b) {
+      if (null == a && null != b || null != a && null == b || null != a && null != b && a.length != b.length) return !1;
+      if (null != a && null != b)
+        for (var c in a)
+          if (a[c] != b[c]) return !1;
+      return !0
+    },
+    extend: function(a, b) {
+      var c = function() {};
+      c.prototype = b.prototype;
+      a.prototype = new c;
+      a.prototype.constructor =
+        a
+    },
+    toString: function(a) {
+      var b = "",
+        c;
+      for (c in a) try {
+        if (null == a[c]) b += c + " \x3d [null]\n";
+        else if ("function" == typeof a[c]) b += c + " \x3d\x3e [Function]\n";
+        else if ("object" == typeof a[c]) var d = mxUtils.getFunctionName(a[c].constructor),
+          b = b + (c + " \x3d\x3e [" + d + "]\n");
+        else b += c + " \x3d " + a[c] + "\n"
+      } catch (e) {
+        b += c + "\x3d" + e.message
+      }
+      return b
+    },
+    toRadians: function(a) {
+      return Math.PI * a / 180
+    },
+    arcToCurves: function(a, b, c, d, e, f, g, h, k) {
+      h -= a;
+      k -= b;
+      if (0 === c || 0 === d) return p;
+      c = Math.abs(c);
+      d = Math.abs(d);
+      var l = -h / 2,
+        m = -k / 2,
+        n = Math.cos(e *
+          Math.PI / 180),
+        p = Math.sin(e * Math.PI / 180);
+      e = n * l + p * m;
+      var l = -1 * p * l + n * m,
+        m = e * e,
+        q = l * l,
+        s = c * c,
+        r = d * d,
+        t = m / s + q / r;
+      1 < t ? (c *= Math.sqrt(t), d *= Math.sqrt(t), f = 0) : (t = 1, f === g && (t = -1), f = t * Math.sqrt((s * r - s * q - r * m) / (s * q + r * m)));
+      m = f * c * l / d;
+      q = -1 * f * d * e / c;
+      h = n * m - p * q + h / 2;
+      k = p * m + n * q + k / 2;
+      s = Math.atan2((l - q) / d, (e - m) / c) - Math.atan2(0, 1);
+      f = 0 <= s ? s : 2 * Math.PI + s;
+      s = Math.atan2((-l - q) / d, (-e - m) / c) - Math.atan2((l - q) / d, (e - m) / c);
+      e = 0 <= s ? s : 2 * Math.PI + s;
+      0 == g && 0 < e ? e -= 2 * Math.PI : 0 != g && 0 > e && (e += 2 * Math.PI);
+      g = 2 * e / Math.PI;
+      g = Math.ceil(0 > g ? -1 * g : g);
+      e /= g;
+      l = 8 / 3 * Math.sin(e /
+        4) * Math.sin(e / 4) / Math.sin(e / 2);
+      m = n * c;
+      n *= d;
+      c *= p;
+      d *= p;
+      for (var v = Math.cos(f), w = Math.sin(f), q = -l * (m * w + d * v), s = -l * (c * w - n * v), t = r = 0, p = [], u = 0; u < g; ++u) {
+        f += e;
+        var v = Math.cos(f),
+          w = Math.sin(f),
+          r = m * v - d * w + h,
+          t = c * v + n * w + k,
+          y = -l * (m * w + d * v),
+          v = -l * (c * w - n * v),
+          w = 6 * u;
+        p[w] = Number(q + a);
+        p[w + 1] = Number(s + b);
+        p[w + 2] = Number(r - y + a);
+        p[w + 3] = Number(t - v + b);
+        p[w + 4] = Number(r + a);
+        p[w + 5] = Number(t + b);
+        q = r + y;
+        s = t + v
+      }
+      return p
+    },
+    getBoundingBox: function(a, b) {
+      var c = null;
+      if (null != a && null != b && 0 != b) {
+        var d = mxUtils.toRadians(b),
+          c = Math.cos(d),
+          e = Math.sin(d),
+          f =
+          new mxPoint(a.x + a.width / 2, a.y + a.height / 2),
+          g = new mxPoint(a.x, a.y),
+          d = new mxPoint(a.x + a.width, a.y),
+          h = new mxPoint(d.x, a.y + a.height),
+          k = new mxPoint(a.x, h.y),
+          g = mxUtils.getRotatedPoint(g, c, e, f),
+          d = mxUtils.getRotatedPoint(d, c, e, f),
+          h = mxUtils.getRotatedPoint(h, c, e, f),
+          k = mxUtils.getRotatedPoint(k, c, e, f),
+          c = new mxRectangle(g.x, g.y, 0, 0);
+        c.add(new mxRectangle(d.x, d.y, 0, 0));
+        c.add(new mxRectangle(h.x, h.y, 0, 0));
+        c.add(new mxRectangle(k.x, k.y, 0, 0))
+      }
+      return c
+    },
+    getRotatedPoint: function(a, b, c, d) {
+      d = null != d ? d : new mxPoint;
+      var e =
+        a.x - d.x;
+      a = a.y - d.y;
+      return new mxPoint(e * b - a * c + d.x, a * b + e * c + d.y)
+    },
+    getPortConstraints: function(a, b, c, d) {
+      a = mxUtils.getValue(a.style, mxConstants.STYLE_PORT_CONSTRAINT, null);
+      if (null == a) return d;
+      d = a.toString();
+      a = mxConstants.DIRECTION_MASK_NONE;
+      0 <= d.indexOf(mxConstants.DIRECTION_NORTH) && (a |= mxConstants.DIRECTION_MASK_NORTH);
+      0 <= d.indexOf(mxConstants.DIRECTION_WEST) && (a |= mxConstants.DIRECTION_MASK_WEST);
+      0 <= d.indexOf(mxConstants.DIRECTION_SOUTH) && (a |= mxConstants.DIRECTION_MASK_SOUTH);
+      0 <= d.indexOf(mxConstants.DIRECTION_EAST) &&
+        (a |= mxConstants.DIRECTION_MASK_EAST);
+      return a
+    },
+    reversePortConstraints: function(a) {
+      var b = 0,
+        b = (a & mxConstants.DIRECTION_MASK_WEST) << 3,
+        b = b | (a & mxConstants.DIRECTION_MASK_NORTH) << 1,
+        b = b | (a & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
+      return b |= (a & mxConstants.DIRECTION_MASK_EAST) >> 3
+    },
+    findNearestSegment: function(a, b, c) {
+      var d = -1;
+      if (0 < a.absolutePoints.length)
+        for (var e = a.absolutePoints[0], f = null, g = 1; g < a.absolutePoints.length; g++) {
+          var h = a.absolutePoints[g],
+            e = mxUtils.ptSegDistSq(e.x, e.y, h.x, h.y, b, c);
+          if (null == f || e < f) f =
+            e, d = g - 1;
+          e = h
+        }
+      return d
+    },
+    rectangleIntersectsSegment: function(a, b, c) {
+      var d = a.y,
+        e = a.x,
+        f = d + a.height,
+        g = e + a.width;
+      a = b.x;
+      var h = c.x;
+      b.x > c.x && (a = c.x, h = b.x);
+      h > g && (h = g);
+      a < e && (a = e);
+      if (a > h) return !1;
+      var e = b.y,
+        g = c.y,
+        k = c.x - b.x;
+      1E-7 < Math.abs(k) && (c = (c.y - b.y) / k, b = b.y - c * b.x, e = c * a + b, g = c * h + b);
+      e > g && (b = g, g = e, e = b);
+      g > f && (g = f);
+      e < d && (e = d);
+      return e > g ? !1 : !0
+    },
+    contains: function(a, b, c) {
+      return a.x <= b && a.x + a.width >= b && a.y <= c && a.y + a.height >= c
+    },
+    intersects: function(a, b) {
+      var c = a.width,
+        d = a.height,
+        e = b.width,
+        f = b.height;
+      if (0 >= e || 0 >= f || 0 >= c ||
+        0 >= d) return !1;
+      var g = a.x,
+        h = a.y,
+        k = b.x,
+        l = b.y,
+        e = e + k,
+        f = f + l,
+        c = c + g,
+        d = d + h;
+      return (e < k || e > g) && (f < l || f > h) && (c < g || c > k) && (d < h || d > l)
+    },
+    intersectsHotspot: function(a, b, c, d, e, f) {
+      d = null != d ? d : 1;
+      e = null != e ? e : 0;
+      f = null != f ? f : 0;
+      if (0 < d) {
+        var g = a.getCenterX(),
+          h = a.getCenterY(),
+          k = a.width,
+          l = a.height,
+          m = mxUtils.getValue(a.style, mxConstants.STYLE_STARTSIZE) * a.view.scale;
+        0 < m && (mxUtils.getValue(a.style, mxConstants.STYLE_HORIZONTAL, !0) ? (h = a.y + m / 2, l = m) : (g = a.x + m / 2, k = m));
+        k = Math.max(e, k * d);
+        l = Math.max(e, l * d);
+        0 < f && (k = Math.min(k, f), l = Math.min(l,
+          f));
+        d = new mxRectangle(g - k / 2, h - l / 2, k, l);
+        g = mxUtils.toRadians(mxUtils.getValue(a.style, mxConstants.STYLE_ROTATION) || 0);
+        0 != g && (e = Math.cos(-g), f = Math.sin(-g), g = new mxPoint(a.getCenterX(), a.getCenterY()), a = mxUtils.getRotatedPoint(new mxPoint(b, c), e, f, g), b = a.x, c = a.y);
+        return mxUtils.contains(d, b, c)
+      }
+      return !0
+    },
+    getOffset: function(a, b) {
+      var c = 0,
+        d = 0;
+      if (null != b && b) var e = document.body,
+        f = document.documentElement,
+        c = c + (e.scrollLeft || f.scrollLeft),
+        d = d + (e.scrollTop || f.scrollTop);
+      for (; a.offsetParent;) c += a.offsetLeft, d +=
+        a.offsetTop, a = a.offsetParent;
+      return new mxPoint(c, d)
+    },
+    getScrollOrigin: function(a) {
+      for (var b = document.body, c = document.documentElement, d = new mxPoint(b.scrollLeft || c.scrollLeft, b.scrollTop || c.scrollTop); null != a && a != b && a != c;) !isNaN(a.scrollLeft) && !
+        isNaN(a.scrollTop) && (d.x += a.scrollLeft, d.y += a.scrollTop), a = a.parentNode;
+      return d
+    },
+    convertPoint: function(a, b, c) {
+      var d = mxUtils.getScrollOrigin(a);
+      a = mxUtils.getOffset(a);
+      a.x -= d.x;
+      a.y -= d.y;
+      return new mxPoint(b - a.x, c - a.y)
+    },
+    ltrim: function(a, b) {
+      return a.replace(RegExp("^[" +
+        (b || "\\s") + "]+", "g"), "")
+    },
+    rtrim: function(a, b) {
+      return a.replace(RegExp("[" + (b || "\\s") + "]+$", "g"), "")
+    },
+    trim: function(a, b) {
+      return mxUtils.ltrim(mxUtils.rtrim(a, b), b)
+    },
+    isNumeric: function(a) {
+      return null != a && (null == a.length || 0 < a.length && 0 > a.indexOf("0x") && 0 > a.indexOf("0X")) && !isNaN(a)
+    },
+    mod: function(a, b) {
+      return (a % b + b) % b
+    },
+    intersection: function(a, b, c, d, e, f, g, h) {
+      var k = (h - f) * (c - a) - (g - e) * (d - b);
+      g = ((g - e) * (b - f) - (h - f) * (a - e)) / k;
+      e = ((c - a) * (b - f) - (d - b) * (a - e)) / k;
+      return 0 <= g && 1 >= g && 0 <= e && 1 >= e ? new mxPoint(a + g * (c - a), b + g *
+        (d - b)) : null
+    },
+    ptSegDistSq: function(a, b, c, d, e, f) {
+      c -= a;
+      d -= b;
+      e -= a;
+      f -= b;
+      0 >= e * c + f * d ? c = 0 : (e = c - e, f = d - f, a = e * c + f * d, c = 0 >= a ? 0 : a * a / (c * c + d * d));
+      e = e * e + f * f - c;
+      0 > e && (e = 0);
+      return e
+    },
+    relativeCcw: function(a, b, c, d, e, f) {
+      c -= a;
+      d -= b;
+      e -= a;
+      f -= b;
+      a = e * d - f * c;
+      0 == a && (a = e * c + f * d, 0 < a && (a = (e - c) * c + (f - d) * d, 0 > a && (a = 0)));
+      return 0 > a ? -1 : 0 < a ? 1 : 0
+    },
+    animateChanges: function(a, b) {
+      mxEffects.animateChanges.apply(this, arguments)
+    },
+    cascadeOpacity: function(a, b, c) {
+      mxEffects.cascadeOpacity.apply(this, arguments)
+    },
+    fadeOut: function(a, b, c, d, e, f) {
+      mxEffects.fadeOut.apply(this,
+        arguments)
+    },
+    setOpacity: function(a, b) {
+      mxUtils.isVml(a) ? a.style.filter = 100 <= b ? null : "alpha(opacity\x3d" + b / 5 + ")" : mxClient.IS_IE && ("undefined" === typeof document.documentMode || 9 > document.documentMode) ? a.style
+        .filter = 100 <= b ? null : "alpha(opacity\x3d" + b + ")" : a.style.opacity = b / 100
+    },
+    createImage: function(a) {
+      var b = null;
+      mxClient.IS_IE6 && "CSS1Compat" != document.compatMode ? (b = document.createElement(mxClient.VML_PREFIX + ":image"), b.setAttribute("src", a), b.style.borderStyle = "none") : (b = document.createElement(
+        "img"), b.setAttribute("src",
+        a), b.setAttribute("border", "0"));
+      return b
+    },
+    sortCells: function(a, b) {
+      b = null != b ? b : !0;
+      var c = new mxDictionary;
+      a.sort(function(a, e) {
+        var f = c.get(a);
+        null == f && (f = mxCellPath.create(a).split(mxCellPath.PATH_SEPARATOR), c.put(a, f));
+        var g = c.get(e);
+        null == g && (g = mxCellPath.create(e).split(mxCellPath.PATH_SEPARATOR), c.put(e, g));
+        f = mxCellPath.compare(f, g);
+        return 0 == f ? 0 : 0 < f == b ? 1 : -1
+      });
+      return a
+    },
+    getStylename: function(a) {
+      return null != a && (a = a.split(";")[0], 0 > a.indexOf("\x3d")) ? a : ""
+    },
+    getStylenames: function(a) {
+      var b = [];
+      if (null !=
+        a) {
+        a = a.split(";");
+        for (var c = 0; c < a.length; c++) 0 > a[c].indexOf("\x3d") && b.push(a[c])
+      }
+      return b
+    },
+    indexOfStylename: function(a, b) {
+      if (null != a && null != b)
+        for (var c = a.split(";"), d = 0, e = 0; e < c.length; e++) {
+          if (c[e] == b) return d;
+          d += c[e].length + 1
+        }
+      return -1
+    },
+    addStylename: function(a, b) {
+      0 > mxUtils.indexOfStylename(a, b) && (null == a ? a = "" : 0 < a.length && ";" != a.charAt(a.length - 1) && (a += ";"), a += b);
+      return a
+    },
+    removeStylename: function(a, b) {
+      var c = [];
+      if (null != a)
+        for (var d = a.split(";"), e = 0; e < d.length; e++) d[e] != b && c.push(d[e]);
+      return c.join(";")
+    },
+    removeAllStylenames: function(a) {
+      var b = [];
+      if (null != a) {
+        a = a.split(";");
+        for (var c = 0; c < a.length; c++) 0 <= a[c].indexOf("\x3d") && b.push(a[c])
+      }
+      return b.join(";")
+    },
+    setCellStyles: function(a, b, c, d) {
+      if (null != b && 0 < b.length) {
+        a.beginUpdate();
+        try {
+          for (var e = 0; e < b.length; e++)
+            if (null != b[e]) {
+              var f = mxUtils.setStyle(a.getStyle(b[e]), c, d);
+              a.setStyle(b[e], f)
+            }
+        } finally {
+          a.endUpdate()
+        }
+      }
+    },
+    setStyle: function(a, b, c) {
+      var d = null != c && ("undefined" == typeof c.length || 0 < c.length);
+      if (null == a || 0 == a.length) d && (a = b + "\x3d" + c);
+      else {
+        var e = a.indexOf(b +
+          "\x3d");
+        0 > e ? d && (d = ";" == a.charAt(a.length - 1) ? "" : ";", a = a + d + b + "\x3d" + c) : (b = d ? b + "\x3d" + c : "", c = a.indexOf(";", e), d || c++, a = a.substring(0, e) + b + (c > e ?
+          a.substring(c) : ""))
+      }
+      return a
+    },
+    setCellStyleFlags: function(a, b, c, d, e) {
+      if (null != b && 0 < b.length) {
+        a.beginUpdate();
+        try {
+          for (var f = 0; f < b.length; f++)
+            if (null != b[f]) {
+              var g = mxUtils.setStyleFlag(a.getStyle(b[f]), c, d, e);
+              a.setStyle(b[f], g)
+            }
+        } finally {
+          a.endUpdate()
+        }
+      }
+    },
+    setStyleFlag: function(a, b, c, d) {
+      if (null == a || 0 == a.length) a = d || null == d ? b + "\x3d" + c : b + "\x3d0";
+      else {
+        var e = a.indexOf(b +
+          "\x3d");
+        if (0 > e) e = ";" == a.charAt(a.length - 1) ? "" : ";", a = d || null == d ? a + e + b + "\x3d" + c : a + e + b + "\x3d0";
+        else {
+          var f = a.indexOf(";", e),
+            g = "",
+            g = 0 > f ? a.substring(e + b.length + 1) : a.substring(e + b.length + 1, f),
+            g = null == d ? parseInt(g) ^ c : d ? parseInt(g) | c : parseInt(g) & ~c;
+          a = a.substring(0, e) + b + "\x3d" + g + (0 <= f ? a.substring(f) : "")
+        }
+      }
+      return a
+    },
+    getAlignmentAsPoint: function(a, b) {
+      var c = 0,
+        d = 0;
+      a == mxConstants.ALIGN_CENTER ? c = -0.5 : a == mxConstants.ALIGN_RIGHT && (c = -1);
+      b == mxConstants.ALIGN_MIDDLE ? d = -0.5 : b == mxConstants.ALIGN_BOTTOM && (d = -1);
+      return new mxPoint(c,
+        d)
+    },
+    getSizeForString: function(a, b, c) {
+      b = null != b ? b : mxConstants.DEFAULT_FONTSIZE;
+      c = null != c ? c : mxConstants.DEFAULT_FONTFAMILY;
+      var d = document.createElement("div");
+      d.style.fontFamily = c;
+      d.style.fontSize = Math.round(b) + "px";
+      d.style.lineHeight = Math.round(b * mxConstants.LINE_HEIGHT) + "px";
+      d.style.position = "absolute";
+      d.style.whiteSpace = "nowrap";
+      d.style.visibility = "hidden";
+      d.style.display = mxClient.IS_QUIRKS ? "inline" : "inline-block";
+      d.style.zoom = "1";
+      d.innerHTML = a;
+      document.body.appendChild(d);
+      a = new mxRectangle(0, 0,
+        d.offsetWidth, d.offsetHeight);
+      document.body.removeChild(d);
+      return a
+    },
+    getViewXml: function(a, b, c, d, e) {
+      d = null != d ? d : 0;
+      e = null != e ? e : 0;
+      b = null != b ? b : 1;
+      null == c && (c = [a.getModel().getRoot()]);
+      var f = a.getView(),
+        g = null,
+        h = f.isEventsEnabled();
+      f.setEventsEnabled(!1);
+      var k = f.drawPane,
+        l = f.overlayPane;
+      a.dialect == mxConstants.DIALECT_SVG ? (f.drawPane = document.createElementNS(mxConstants.NS_SVG, "g"), f.canvas.appendChild(f.drawPane), f.overlayPane = document.createElementNS(mxConstants.NS_SVG,
+        "g")) : (f.drawPane = f.drawPane.cloneNode(!1),
+        f.canvas.appendChild(f.drawPane), f.overlayPane = f.overlayPane.cloneNode(!1));
+      f.canvas.appendChild(f.overlayPane);
+      var m = f.getTranslate();
+      f.translate = new mxPoint(d, e);
+      b = new mxTemporaryCellStates(a.getView(), b, c);
+      try {
+        g = (new mxCodec).encode(a.getView())
+      } finally {
+        b.destroy(), f.translate = m, f.canvas.removeChild(f.drawPane), f.canvas.removeChild(f.overlayPane), f.drawPane = k, f.overlayPane = l, f.setEventsEnabled(h)
+      }
+      return g
+    },
+    getScaleForPageCount: function(a, b, c, d) {
+      if (1 > a) return 1;
+      c = null != c ? c : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+      d = null != d ? d : 0;
+      var e = c.width - 2 * d;
+      c = c.height - 2 * d;
+      d = b.getGraphBounds().clone();
+      b = b.getView().getScale();
+      d.width /= b;
+      d.height /= b;
+      b = d.width;
+      c = b / d.height / (e / c);
+      d = Math.sqrt(a);
+      var f = Math.sqrt(c);
+      c = d * f;
+      d /= f;
+      if (1 > c && d > a) {
+        var g = d / a;
+        d = a;
+        c /= g
+      }
+      1 > d && c > a && (g = c / a, c = a, d /= g);
+      g = Math.ceil(c) * Math.ceil(d);
+      for (f = 0; g > a;) {
+        var g = Math.floor(c) / c,
+          h = Math.floor(d) / d;
+        1 == g && (g = Math.floor(c - 1) / c);
+        1 == h && (h = Math.floor(d - 1) / d);
+        g = g > h ? g : h;
+        c *= g;
+        d *= g;
+        g = Math.ceil(c) * Math.ceil(d);
+        f++;
+        if (10 < f) break
+      }
+      return 0.99999 * (e * c / b)
+    },
+    show: function(a,
+      b, c, d) {
+      c = null != c ? c : 0;
+      d = null != d ? d : 0;
+      null == b ? b = window.open().document : b.open();
+      var e = a.getGraphBounds();
+      c = -e.x + c;
+      d = -e.y + d;
+      if (mxClient.IS_IE) {
+        for (var e = "\x3chtml\x3e\x3chead\x3e", f = document.getElementsByTagName("base"), g = 0; g < f.length; g++) e += f[g].outerHTML;
+        e += "\x3cstyle\x3e";
+        for (g = 0; g < document.styleSheets.length; g++) try {
+          e += document.styleSheets(g).cssText
+        } catch (h) {}
+        e += "\x3c/style\x3e\x3c/head\x3e";
+        e += "\x3cbody\x3e";
+        e += a.container.innerHTML;
+        e += "\x3c/body\x3e";
+        e += "\x3chtml\x3e";
+        b.writeln(e);
+        b.close();
+        a =
+          b.body.getElementsByTagName("DIV")[0];
+        null != a && (a.style.position = "absolute", a.style.left = c + "px", a.style.top = d + "px")
+      } else {
+        b.writeln("\x3chtml");
+        b.writeln("\x3chead\x3e");
+        f = document.getElementsByTagName("base");
+        for (g = 0; g < f.length; g++) b.writeln(mxUtils.getOuterHtml(f[g]));
+        f = document.getElementsByTagName("link");
+        for (g = 0; g < f.length; g++) b.writeln(mxUtils.getOuterHtml(f[g]));
+        f = document.getElementsByTagName("style");
+        for (g = 0; g < f.length; g++) b.writeln(mxUtils.getOuterHtml(f[g]));
+        b.writeln("\x3c/head\x3e");
+        b.writeln("\x3c/html\x3e");
+        b.close();
+        null == b.body && b.documentElement.appendChild(b.createElement("body"));
+        b.body.style.overflow = "auto";
+        for (a = a.container.firstChild; null != a;) g = a.cloneNode(!0), b.body.appendChild(g), a = a.nextSibling;
+        a = b.getElementsByTagName("g")[0];
+        null != a && (a.setAttribute("transform", "translate(" + c + "," + d + ")"), c = a.ownerSVGElement, c.setAttribute("width", e.width + Math.max(e.x, 0) + 3), c.setAttribute("height", e.height +
+          Math.max(e.y, 0) + 3))
+      }
+      mxUtils.removeCursors(b.body);
+      return b
+    },
+    printScreen: function(a) {
+      var b = window.open();
+      mxUtils.show(a, b.document);
+      a = function() {
+        b.focus();
+        b.print();
+        b.close()
+      };
+      mxClient.IS_GC ? b.setTimeout(a, 500) : a()
+    },
+    popup: function(a, b) {
+      if (b) {
+        var c = document.createElement("div");
+        c.style.overflow = "scroll";
+        c.style.width = "636px";
+        c.style.height = "460px";
+        var d = document.createElement("pre");
+        d.innerHTML = mxUtils.htmlEntities(a, !1).replace(/\n/g, "\x3cbr\x3e").replace(/ /g, "\x26nbsp;");
+        c.appendChild(d);
+        c = new mxWindow("Popup Window", c, document.body.clientWidth / 2 - 320, (document.body.clientHeight || document.documentElement.clientHeight) /
+          2 - 240, 640, 480, !1, !0);
+        c.setClosable(!0);
+        c.setVisible(!0)
+      } else mxClient.IS_NS ? (c = window.open(), c.document.writeln("\x3cpre\x3e" + mxUtils.htmlEntities(a) + "\x3c/pre"), c.document.close()) : (c = window.open(), d = c.document.createElement(
+        "pre"), d.innerHTML = mxUtils.htmlEntities(a, !1).replace(/\n/g, "\x3cbr\x3e").replace(/ /g, "\x26nbsp;"), c.document.body.appendChild(d))
+    },
+    alert: function(a) {
+      alert(a)
+    },
+    prompt: function(a, b) {
+      return prompt(a, b)
+    },
+    confirm: function(a) {
+      return confirm(a)
+    },
+    error: function(a, b, c, d) {
+      var e = document.createElement("div");
+      e.style.padding = "20px";
+      var f = document.createElement("img");
+      f.setAttribute("src", d || mxUtils.errorImage);
+      f.setAttribute("valign", "bottom");
+      f.style.verticalAlign = "middle";
+      e.appendChild(f);
+      e.appendChild(document.createTextNode("\u00a0"));
+      e.appendChild(document.createTextNode("\u00a0"));
+      e.appendChild(document.createTextNode("\u00a0"));
+      mxUtils.write(e, a);
+      a = document.body.clientWidth;
+      d = document.body.clientHeight || document.documentElement.clientHeight;
+      var g = new mxWindow(mxResources.get(mxUtils.errorResource) ||
+        mxUtils.errorResource, e, (a - b) / 2, d / 4, b, null, !1, !0);
+      c && (mxUtils.br(e), b = document.createElement("p"), c = document.createElement("button"), mxClient.IS_IE ? c.style.cssText = "float:right" : c.setAttribute("style", "float:right"), mxEvent.addListener(
+        c, "click",
+        function(a) {
+          g.destroy()
+        }), mxUtils.write(c, mxResources.get(mxUtils.closeResource) || mxUtils.closeResource), b.appendChild(c), e.appendChild(b), mxUtils.br(e), g.setClosable(!0));
+      g.setVisible(!0);
+      return g
+    },
+    makeDraggable: function(a, b, c, d, e, f, g, h, k, l) {
+      a = new mxDragSource(a,
+        c);
+      a.dragOffset = new mxPoint(null != e ? e : 0, null != f ? f : mxConstants.TOOLTIP_VERTICAL_OFFSET);
+      a.autoscroll = g;
+      a.setGuidesEnabled(!1);
+      null != k && (a.highlightDropTargets = k);
+      null != l && (a.getDropTarget = l);
+      a.getGraphForEvent = function(a) {
+        return "function" == typeof b ? b(a) : b
+      };
+      null != d && (a.createDragElement = function() {
+        return d.cloneNode(!0)
+      }, h && (a.createPreviewElement = function(a) {
+        var b = d.cloneNode(!0),
+          c = parseInt(b.style.width),
+          e = parseInt(b.style.height);
+        b.style.width = Math.round(c * a.view.scale) + "px";
+        b.style.height = Math.round(e *
+          a.view.scale) + "px";
+        return b
+      }));
+      return a
+    }
+  },
+  mxConstants = {
+    DEFAULT_HOTSPOT: 0.3,
+    MIN_HOTSPOT_SIZE: 8,
+    MAX_HOTSPOT_SIZE: 0,
+    RENDERING_HINT_EXACT: "exact",
+    RENDERING_HINT_FASTER: "faster",
+    RENDERING_HINT_FASTEST: "fastest",
+    DIALECT_SVG: "svg",
+    DIALECT_VML: "vml",
+    DIALECT_MIXEDHTML: "mixedHtml",
+    DIALECT_PREFERHTML: "preferHtml",
+    DIALECT_STRICTHTML: "strictHtml",
+    NS_SVG: "http://www.w3.org/2000/svg",
+    NS_XHTML: "http://www.w3.org/1999/xhtml",
+    NS_XLINK: "http://www.w3.org/1999/xlink",
+    SHADOWCOLOR: "gray",
+    SHADOW_OFFSET_X: 2,
+    SHADOW_OFFSET_Y: 3,
+    SHADOW_OPACITY: 1,
+    NODETYPE_ELEMENT: 1,
+    NODETYPE_ATTRIBUTE: 2,
+    NODETYPE_TEXT: 3,
+    NODETYPE_CDATA: 4,
+    NODETYPE_ENTITY_REFERENCE: 5,
+    NODETYPE_ENTITY: 6,
+    NODETYPE_PROCESSING_INSTRUCTION: 7,
+    NODETYPE_COMMENT: 8,
+    NODETYPE_DOCUMENT: 9,
+    NODETYPE_DOCUMENTTYPE: 10,
+    NODETYPE_DOCUMENT_FRAGMENT: 11,
+    NODETYPE_NOTATION: 12,
+    TOOLTIP_VERTICAL_OFFSET: 16,
+    DEFAULT_VALID_COLOR: "#00FF00",
+    DEFAULT_INVALID_COLOR: "#FF0000",
+    HIGHLIGHT_STROKEWIDTH: 3,
+    CURSOR_MOVABLE_VERTEX: "move",
+    CURSOR_MOVABLE_EDGE: "move",
+    CURSOR_LABEL_HANDLE: "default",
+    CURSOR_BEND_HANDLE: "pointer",
+    CURSOR_CONNECT: "pointer",
+    HIGHLIGHT_COLOR: "#00FF00",
+    CONNECT_TARGET_COLOR: "#0000FF",
+    INVALID_CONNECT_TARGET_COLOR: "#FF0000",
+    DROP_TARGET_COLOR: "#0000FF",
+    VALID_COLOR: "#00FF00",
+    INVALID_COLOR: "#FF0000",
+    EDGE_SELECTION_COLOR: "#00FF00",
+    VERTEX_SELECTION_COLOR: "#00FF00",
+    VERTEX_SELECTION_STROKEWIDTH: 1,
+    EDGE_SELECTION_STROKEWIDTH: 1,
+    VERTEX_SELECTION_DASHED: !0,
+    EDGE_SELECTION_DASHED: !0,
+    GUIDE_COLOR: "#FF0000",
+    GUIDE_STROKEWIDTH: 1,
+    OUTLINE_COLOR: "#0099FF",
+    OUTLINE_STROKEWIDTH: mxClient.IS_IE ? 2 : 3,
+    HANDLE_SIZE: 7,
+    LABEL_HANDLE_SIZE: 4,
+    HANDLE_FILLCOLOR: "#00FF00",
+    HANDLE_STROKECOLOR: "black",
+    LABEL_HANDLE_FILLCOLOR: "yellow",
+    CONNECT_HANDLE_FILLCOLOR: "#0000FF",
+    LOCKED_HANDLE_FILLCOLOR: "#FF0000",
+    OUTLINE_HANDLE_FILLCOLOR: "#00FFFF",
+    OUTLINE_HANDLE_STROKECOLOR: "#0033FF",
+    DEFAULT_FONTFAMILY: "Arial,Helvetica",
+    DEFAULT_FONTSIZE: 11,
+    LINE_HEIGHT: 1.2,
+    DEFAULT_FONTSTYLE: 0,
+    DEFAULT_STARTSIZE: 40,
+    DEFAULT_MARKERSIZE: 6,
+    DEFAULT_IMAGESIZE: 24,
+    ENTITY_SEGMENT: 30,
+    RECTANGLE_ROUNDING_FACTOR: 0.15,
+    LINE_ARCSIZE: 20,
+    ARROW_SPACING: 10,
+    ARROW_WIDTH: 30,
+    ARROW_SIZE: 30,
+    PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0,
+      0, 826, 1169),
+    PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 826),
+    PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),
+    PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),
+    NONE: "none",
+    STYLE_PERIMETER: "perimeter",
+    STYLE_SOURCE_PORT: "sourcePort",
+    STYLE_TARGET_PORT: "targetPort",
+    STYLE_PORT_CONSTRAINT: "portConstraint",
+    STYLE_OPACITY: "opacity",
+    STYLE_TEXT_OPACITY: "textOpacity",
+    STYLE_OVERFLOW: "overflow",
+    STYLE_ORTHOGONAL: "orthogonal",
+    STYLE_EXIT_X: "exitX",
+    STYLE_EXIT_Y: "exitY",
+    STYLE_EXIT_PERIMETER: "exitPerimeter",
+    STYLE_ENTRY_X: "entryX",
+    STYLE_ENTRY_Y: "entryY",
+    STYLE_ENTRY_PERIMETER: "entryPerimeter",
+    STYLE_WHITE_SPACE: "whiteSpace",
+    STYLE_ROTATION: "rotation",
+    STYLE_FILLCOLOR: "fillColor",
+    STYLE_SWIMLANE_FILLCOLOR: "swimlaneFillColor",
+    STYLE_GRADIENTCOLOR: "gradientColor",
+    STYLE_GRADIENT_DIRECTION: "gradientDirection",
+    STYLE_STROKECOLOR: "strokeColor",
+    STYLE_SEPARATORCOLOR: "separatorColor",
+    STYLE_STROKEWIDTH: "strokeWidth",
+    STYLE_ALIGN: "align",
+    STYLE_VERTICAL_ALIGN: "verticalAlign",
+    STYLE_LABEL_POSITION: "labelPosition",
+    STYLE_VERTICAL_LABEL_POSITION: "verticalLabelPosition",
+    STYLE_IMAGE_ASPECT: "imageAspect",
+    STYLE_IMAGE_ALIGN: "imageAlign",
+    STYLE_IMAGE_VERTICAL_ALIGN: "imageVerticalAlign",
+    STYLE_GLASS: "glass",
+    STYLE_IMAGE: "image",
+    STYLE_IMAGE_WIDTH: "imageWidth",
+    STYLE_IMAGE_HEIGHT: "imageHeight",
+    STYLE_IMAGE_BACKGROUND: "imageBackground",
+    STYLE_IMAGE_BORDER: "imageBorder",
+    STYLE_FLIPH: "flipH",
+    STYLE_FLIPV: "flipV",
+    STYLE_NOLABEL: "noLabel",
+    STYLE_NOEDGESTYLE: "noEdgeStyle",
+    STYLE_LABEL_BACKGROUNDCOLOR: "labelBackgroundColor",
+    STYLE_LABEL_BORDERCOLOR: "labelBorderColor",
+    STYLE_LABEL_PADDING: "labelPadding",
+    STYLE_INDICATOR_SHAPE: "indicatorShape",
+    STYLE_INDICATOR_IMAGE: "indicatorImage",
+    STYLE_INDICATOR_COLOR: "indicatorColor",
+    STYLE_INDICATOR_STROKECOLOR: "indicatorStrokeColor",
+    STYLE_INDICATOR_GRADIENTCOLOR: "indicatorGradientColor",
+    STYLE_INDICATOR_SPACING: "indicatorSpacing",
+    STYLE_INDICATOR_WIDTH: "indicatorWidth",
+    STYLE_INDICATOR_HEIGHT: "indicatorHeight",
+    STYLE_INDICATOR_DIRECTION: "indicatorDirection",
+    STYLE_SHADOW: "shadow",
+    STYLE_SEGMENT: "segment",
+    STYLE_ENDARROW: "endArrow",
+    STYLE_STARTARROW: "startArrow",
+    STYLE_ENDSIZE: "endSize",
+    STYLE_STARTSIZE: "startSize",
+    STYLE_SWIMLANE_LINE: "swimlaneLine",
+    STYLE_ENDFILL: "endFill",
+    STYLE_STARTFILL: "startFill",
+    STYLE_DASHED: "dashed",
+    STYLE_DASH_PATTERN: "dashPattern",
+    STYLE_ROUNDED: "rounded",
+    STYLE_CURVED: "curved",
+    STYLE_ARCSIZE: "arcSize",
+    STYLE_SMOOTH: "smooth",
+    STYLE_SOURCE_PERIMETER_SPACING: "sourcePerimeterSpacing",
+    STYLE_TARGET_PERIMETER_SPACING: "targetPerimeterSpacing",
+    STYLE_PERIMETER_SPACING: "perimeterSpacing",
+    STYLE_SPACING: "spacing",
+    STYLE_SPACING_TOP: "spacingTop",
+    STYLE_SPACING_LEFT: "spacingLeft",
+    STYLE_SPACING_BOTTOM: "spacingBottom",
+    STYLE_SPACING_RIGHT: "spacingRight",
+    STYLE_HORIZONTAL: "horizontal",
+    STYLE_DIRECTION: "direction",
+    STYLE_ELBOW: "elbow",
+    STYLE_FONTCOLOR: "fontColor",
+    STYLE_FONTFAMILY: "fontFamily",
+    STYLE_FONTSIZE: "fontSize",
+    STYLE_FONTSTYLE: "fontStyle",
+    STYLE_AUTOSIZE: "autosize",
+    STYLE_FOLDABLE: "foldable",
+    STYLE_EDITABLE: "editable",
+    STYLE_BENDABLE: "bendable",
+    STYLE_MOVABLE: "movable",
+    STYLE_RESIZABLE: "resizable",
+    STYLE_CLONEABLE: "cloneable",
+    STYLE_DELETABLE: "deletable",
+    STYLE_SHAPE: "shape",
+    STYLE_EDGE: "edgeStyle",
+    STYLE_LOOP: "loopStyle",
+    STYLE_ROUTING_CENTER_X: "routingCenterX",
+    STYLE_ROUTING_CENTER_Y: "routingCenterY",
+    FONT_BOLD: 1,
+    FONT_ITALIC: 2,
+    FONT_UNDERLINE: 4,
+    FONT_SHADOW: 8,
+    SHAPE_RECTANGLE: "rectangle",
+    SHAPE_ELLIPSE: "ellipse",
+    SHAPE_DOUBLE_ELLIPSE: "doubleEllipse",
+    SHAPE_RHOMBUS: "rhombus",
+    SHAPE_LINE: "line",
+    SHAPE_IMAGE: "image",
+    SHAPE_ARROW: "arrow",
+    SHAPE_LABEL: "label",
+    SHAPE_CYLINDER: "cylinder",
+    SHAPE_SWIMLANE: "swimlane",
+    SHAPE_CONNECTOR: "connector",
+    SHAPE_ACTOR: "actor",
+    SHAPE_CLOUD: "cloud",
+    SHAPE_TRIANGLE: "triangle",
+    SHAPE_HEXAGON: "hexagon",
+    ARROW_CLASSIC: "classic",
+    ARROW_BLOCK: "block",
+    ARROW_OPEN: "open",
+    ARROW_OVAL: "oval",
+    ARROW_DIAMOND: "diamond",
+    ARROW_DIAMOND_THIN: "diamondThin",
+    ALIGN_LEFT: "left",
+    ALIGN_CENTER: "center",
+    ALIGN_RIGHT: "right",
+    ALIGN_TOP: "top",
+    ALIGN_MIDDLE: "middle",
+    ALIGN_BOTTOM: "bottom",
+    DIRECTION_NORTH: "north",
+    DIRECTION_SOUTH: "south",
+    DIRECTION_EAST: "east",
+    DIRECTION_WEST: "west",
+    DIRECTION_MASK_NONE: 0,
+    DIRECTION_MASK_WEST: 1,
+    DIRECTION_MASK_NORTH: 2,
+    DIRECTION_MASK_SOUTH: 4,
+    DIRECTION_MASK_EAST: 8,
+    DIRECTION_MASK_ALL: 15,
+    ELBOW_VERTICAL: "vertical",
+    ELBOW_HORIZONTAL: "horizontal",
+    EDGESTYLE_ELBOW: "elbowEdgeStyle",
+    EDGESTYLE_ENTITY_RELATION: "entityRelationEdgeStyle",
+    EDGESTYLE_LOOP: "loopEdgeStyle",
+    EDGESTYLE_SIDETOSIDE: "sideToSideEdgeStyle",
+    EDGESTYLE_TOPTOBOTTOM: "topToBottomEdgeStyle",
+    EDGESTYLE_ORTHOGONAL: "orthogonalEdgeStyle",
+    EDGESTYLE_SEGMENT: "segmentEdgeStyle",
+    PERIMETER_ELLIPSE: "ellipsePerimeter",
+    PERIMETER_RECTANGLE: "rectanglePerimeter",
+    PERIMETER_RHOMBUS: "rhombusPerimeter",
+    PERIMETER_TRIANGLE: "trianglePerimeter"
+  };
+
+function mxEventObject(a) {
+  this.name = a;
+  this.properties = [];
+  for (var b = 1; b < arguments.length; b += 2) null != arguments[b + 1] && (this.properties[arguments[b]] = arguments[b + 1])
+}
+mxEventObject.prototype.name = null;
+mxEventObject.prototype.properties = null;
+mxEventObject.prototype.consumed = !1;
+mxEventObject.prototype.getName = function() {
+  return this.name
+};
+mxEventObject.prototype.getProperties = function() {
+  return this.properties
+};
+mxEventObject.prototype.getProperty = function(a) {
+  return this.properties[a]
+};
+mxEventObject.prototype.isConsumed = function() {
+  return this.consumed
+};
+mxEventObject.prototype.consume = function() {
+  this.consumed = !0
+};
+
+function mxMouseEvent(a, b) {
+  this.evt = a;
+  this.state = b
+}
+mxMouseEvent.prototype.consumed = !1;
+mxMouseEvent.prototype.evt = null;
+mxMouseEvent.prototype.graphX = null;
+mxMouseEvent.prototype.graphY = null;
+mxMouseEvent.prototype.state = null;
+mxMouseEvent.prototype.getEvent = function() {
+  return this.evt
+};
+mxMouseEvent.prototype.getSource = function() {
+  return mxEvent.getSource(this.evt)
+};
+mxMouseEvent.prototype.isSource = function(a) {
+  if (null != a)
+    for (var b = this.getSource(); null != b;) {
+      if (b == a.node) return !0;
+      b = b.parentNode
+    }
+  return !1
+};
+mxMouseEvent.prototype.getX = function() {
+  return mxEvent.getClientX(this.getEvent())
+};
+mxMouseEvent.prototype.getY = function() {
+  return mxEvent.getClientY(this.getEvent())
+};
+mxMouseEvent.prototype.getGraphX = function() {
+  return this.graphX
+};
+mxMouseEvent.prototype.getGraphY = function() {
+  return this.graphY
+};
+mxMouseEvent.prototype.getState = function() {
+  return this.state
+};
+mxMouseEvent.prototype.getCell = function() {
+  var a = this.getState();
+  return null != a ? a.cell : null
+};
+mxMouseEvent.prototype.isPopupTrigger = function() {
+  return mxEvent.isPopupTrigger(this.getEvent())
+};
+mxMouseEvent.prototype.isConsumed = function() {
+  return this.consumed
+};
+mxMouseEvent.prototype.consume = function(a) {
+  (null != a ? a : 1) && this.evt.preventDefault && this.evt.preventDefault();
+  this.evt.returnValue = !1;
+  this.consumed = !0
+};
+
+function mxEventSource(a) {
+  this.setEventSource(a)
+}
+mxEventSource.prototype.eventListeners = null;
+mxEventSource.prototype.eventsEnabled = !0;
+mxEventSource.prototype.eventSource = null;
+mxEventSource.prototype.isEventsEnabled = function() {
+  return this.eventsEnabled
+};
+mxEventSource.prototype.setEventsEnabled = function(a) {
+  this.eventsEnabled = a
+};
+mxEventSource.prototype.getEventSource = function() {
+  return this.eventSource
+};
+mxEventSource.prototype.setEventSource = function(a) {
+  this.eventSource = a
+};
+mxEventSource.prototype.addListener = function(a, b) {
+  null == this.eventListeners && (this.eventListeners = []);
+  this.eventListeners.push(a);
+  this.eventListeners.push(b)
+};
+mxEventSource.prototype.removeListener = function(a) {
+  if (null != this.eventListeners)
+    for (var b = 0; b < this.eventListeners.length;) this.eventListeners[b + 1] == a ? this.eventListeners.splice(b, 2) : b += 2
+};
+mxEventSource.prototype.fireEvent = function(a, b) {
+  if (null != this.eventListeners && this.isEventsEnabled()) {
+    null == a && (a = new mxEventObject);
+    null == b && (b = this.getEventSource());
+    null == b && (b = this);
+    for (var c = [b, a], d = 0; d < this.eventListeners.length; d += 2) {
+      var e = this.eventListeners[d];
+      (null == e || e == a.getName()) && this.eventListeners[d + 1].apply(this, c)
+    }
+  }
+};
+var mxEvent = {
+  objects: [],
+  addListener: function() {
+    var a = function(a, c, d) {
+      null == a.mxListenerList && (a.mxListenerList = [], mxEvent.objects.push(a));
+      a.mxListenerList.push({
+        name: c,
+        f: d
+      })
+    };
+    return window.addEventListener ? function(b, c, d) {
+      b.addEventListener(c, d, !1);
+      a(b, c, d)
+    } : function(b, c, d) {
+      b.attachEvent("on" + c, d);
+      a(b, c, d)
+    }
+  }(),
+  removeListener: function() {
+    var a = function(a, c, d) {
+      if (null != a.mxListenerList) {
+        c = a.mxListenerList.length;
+        for (var e = 0; e < c; e++)
+          if (a.mxListenerList[e].f == d) {
+            a.mxListenerList.splice(e, 1);
+            break
+          }
+        0 ==
+          a.mxListenerList.length && (a.mxListenerList = null)
+      }
+    };
+    return window.removeEventListener ? function(b, c, d) {
+      b.removeEventListener(c, d, !1);
+      a(b, c, d)
+    } : function(b, c, d) {
+      b.detachEvent("on" + c, d);
+      a(b, c, d)
+    }
+  }(),
+  removeAllListeners: function(a) {
+    var b = a.mxListenerList;
+    if (null != b)
+      for (; 0 < b.length;) {
+        var c = b[0];
+        mxEvent.removeListener(a, c.name, c.f)
+      }
+  },
+  addGestureListeners: function(a, b, c, d) {
+    null != b && mxEvent.addListener(a, "mousedown", b);
+    null != c && mxEvent.addListener(a, "mousemove", c);
+    null != d && mxEvent.addListener(a, "mouseup", d);
+    mxClient.IS_TOUCH && (null != b && mxEvent.addListener(a, "touchstart", b), null != c && mxEvent.addListener(a, "touchmove", c), null != d && mxEvent.addListener(a, "touchend", d))
+  },
+  removeGestureListeners: function(a, b, c, d) {
+    null != b && mxEvent.removeListener(a, "mousedown", b);
+    null != c && mxEvent.removeListener(a, "mousemove", c);
+    null != d && mxEvent.removeListener(a, "mouseup", d);
+    mxClient.IS_TOUCH && (null != b && mxEvent.removeListener(a, "touchstart", b), null != c && mxEvent.removeListener(a, "touchmove", c), null != d && mxEvent.removeListener(a, "touchend",
+      d))
+  },
+  redirectMouseEvents: function(a, b, c, d, e, f, g) {
+    var h = function(a) {
+      return "function" == typeof c ? c(a) : c
+    };
+    mxEvent.addGestureListeners(a, function(a) {
+      null != d ? d(a) : mxEvent.isConsumed(a) || b.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(a, h(a)))
+    }, function(a) {
+      null != e ? e(a) : mxEvent.isConsumed(a) || b.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(a, h(a)))
+    }, function(a) {
+      null != f ? f(a) : mxEvent.isConsumed(a) || b.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(a, h(a)))
+    });
+    mxEvent.addListener(a, "dblclick", function(a) {
+      if (null !=
+        g) g(a);
+      else if (!mxEvent.isConsumed(a)) {
+        var c = h(a);
+        b.dblClick(a, null != c ? c.cell : null)
+      }
+    })
+  },
+  release: function(a) {
+    if (null != a && (mxEvent.removeAllListeners(a), a = a.childNodes, null != a))
+      for (var b = a.length, c = 0; c < b; c += 1) mxEvent.release(a[c])
+  },
+  addMouseWheelListener: function(a) {
+    if (null != a) {
+      var b = function(b) {
+        null == b && (b = window.event);
+        var d = 0,
+          d = mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC ? -b.detail / 2 : b.wheelDelta / 120;
+        0 != d && a(b, 0 < d)
+      };
+      mxClient.IS_NS ? mxEvent.addListener(window, mxClient.IS_SF || mxClient.IS_GC ? "mousewheel" :
+        "DOMMouseScroll", b) : mxEvent.addListener(document, "mousewheel", b)
+    }
+  },
+  disableContextMenu: function() {
+    return mxClient.IS_IE && ("undefined" === typeof document.documentMode || 9 > document.documentMode) ? function(a) {
+      mxEvent.addListener(a, "contextmenu", function() {
+        return !1
+      })
+    } : function(a) {
+      a.setAttribute("oncontextmenu", "return false;")
+    }
+  }(),
+  getSource: function(a) {
+    return null != a.srcElement ? a.srcElement : a.target
+  },
+  isConsumed: function(a) {
+    return null != a.isConsumed && a.isConsumed
+  },
+  isLeftMouseButton: function(a) {
+    return a.button ==
+      (mxClient.IS_IE && ("undefined" === typeof document.documentMode || 9 > document.documentMode) ? 1 : 0)
+  },
+  isRightMouseButton: function(a) {
+    return 2 == a.button
+  },
+  isPopupTrigger: function(a) {
+    return mxEvent.isRightMouseButton(a) || mxEvent.isShiftDown(a) && !mxEvent.isControlDown(a)
+  },
+  isShiftDown: function(a) {
+    return null != a ? a.shiftKey : !1
+  },
+  isAltDown: function(a) {
+    return null != a ? a.altKey : !1
+  },
+  isControlDown: function(a) {
+    return null != a ? a.ctrlKey : !1
+  },
+  isMetaDown: function(a) {
+    return null != a ? a.metaKey : !1
+  },
+  getMainEvent: function(a) {
+    ("touchstart" ==
+      a.type || "touchmove" == a.type) && null != a.touches && null != a.touches[0] ? a = a.touches[0] : "touchend" == a.type && (null != a.changedTouches && null != a.changedTouches[0]) && (a =
+      a.changedTouches[0]);
+    return a
+  },
+  getClientX: function(a) {
+    return mxEvent.getMainEvent(a).clientX
+  },
+  getClientY: function(a) {
+    return mxEvent.getMainEvent(a).clientY
+  },
+  consume: function(a, b, c) {
+    c = null != c ? c : !0;
+    if (null != b ? b : 1) a.preventDefault ? (c && a.stopPropagation(), a.preventDefault()) : c && (a.cancelBubble = !0);
+    a.isConsumed = !0;
+    a.returnValue = !1
+  },
+  LABEL_HANDLE: -1,
+  ROTATION_HANDLE: -2,
+  MOUSE_DOWN: "mouseDown",
+  MOUSE_MOVE: "mouseMove",
+  MOUSE_UP: "mouseUp",
+  ACTIVATE: "activate",
+  RESIZE_START: "resizeStart",
+  RESIZE: "resize",
+  RESIZE_END: "resizeEnd",
+  MOVE_START: "moveStart",
+  MOVE: "move",
+  MOVE_END: "moveEnd",
+  PAN_START: "panStart",
+  PAN: "pan",
+  PAN_END: "panEnd",
+  MINIMIZE: "minimize",
+  NORMALIZE: "normalize",
+  MAXIMIZE: "maximize",
+  HIDE: "hide",
+  SHOW: "show",
+  CLOSE: "close",
+  DESTROY: "destroy",
+  REFRESH: "refresh",
+  SIZE: "size",
+  SELECT: "select",
+  FIRED: "fired",
+  GET: "get",
+  RECEIVE: "receive",
+  CONNECT: "connect",
+  DISCONNECT: "disconnect",
+  SUSPEND: "suspend",
+  RESUME: "resume",
+  MARK: "mark",
+  SESSION: "session",
+  ROOT: "root",
+  POST: "post",
+  OPEN: "open",
+  SAVE: "save",
+  BEFORE_ADD_VERTEX: "beforeAddVertex",
+  ADD_VERTEX: "addVertex",
+  AFTER_ADD_VERTEX: "afterAddVertex",
+  DONE: "done",
+  EXECUTE: "execute",
+  EXECUTED: "executed",
+  BEGIN_UPDATE: "beginUpdate",
+  START_EDIT: "startEdit",
+  END_UPDATE: "endUpdate",
+  END_EDIT: "endEdit",
+  BEFORE_UNDO: "beforeUndo",
+  UNDO: "undo",
+  REDO: "redo",
+  CHANGE: "change",
+  NOTIFY: "notify",
+  LAYOUT_CELLS: "layoutCells",
+  CLICK: "click",
+  SCALE: "scale",
+  TRANSLATE: "translate",
+  SCALE_AND_TRANSLATE: "scaleAndTranslate",
+  UP: "up",
+  DOWN: "down",
+  ADD: "add",
+  REMOVE: "remove",
+  CLEAR: "clear",
+  ADD_CELLS: "addCells",
+  CELLS_ADDED: "cellsAdded",
+  MOVE_CELLS: "moveCells",
+  CELLS_MOVED: "cellsMoved",
+  RESIZE_CELLS: "resizeCells",
+  CELLS_RESIZED: "cellsResized",
+  TOGGLE_CELLS: "toggleCells",
+  CELLS_TOGGLED: "cellsToggled",
+  ORDER_CELLS: "orderCells",
+  CELLS_ORDERED: "cellsOrdered",
+  REMOVE_CELLS: "removeCells",
+  CELLS_REMOVED: "cellsRemoved",
+  GROUP_CELLS: "groupCells",
+  UNGROUP_CELLS: "ungroupCells",
+  REMOVE_CELLS_FROM_PARENT: "removeCellsFromParent",
+  FOLD_CELLS: "foldCells",
+  CELLS_FOLDED: "cellsFolded",
+  ALIGN_CELLS: "alignCells",
+  LABEL_CHANGED: "labelChanged",
+  CONNECT_CELL: "connectCell",
+  CELL_CONNECTED: "cellConnected",
+  SPLIT_EDGE: "splitEdge",
+  FLIP_EDGE: "flipEdge",
+  START_EDITING: "startEditing",
+  ADD_OVERLAY: "addOverlay",
+  REMOVE_OVERLAY: "removeOverlay",
+  UPDATE_CELL_SIZE: "updateCellSize",
+  ESCAPE: "escape",
+  CLICK: "click",
+  DOUBLE_CLICK: "doubleClick",
+  START: "start",
+  RESET: "reset"
+};
+
+function mxXmlRequest(a, b, c, d, e, f) {
+  this.url = a;
+  this.params = b;
+  this.method = c || "POST";
+  this.async = null != d ? d : !0;
+  this.username = e;
+  this.password = f
+}
+mxXmlRequest.prototype.url = null;
+mxXmlRequest.prototype.params = null;
+mxXmlRequest.prototype.method = null;
+mxXmlRequest.prototype.async = null;
+mxXmlRequest.prototype.binary = !1;
+mxXmlRequest.prototype.username = null;
+mxXmlRequest.prototype.password = null;
+mxXmlRequest.prototype.request = null;
+mxXmlRequest.prototype.isBinary = function() {
+  return this.binary
+};
+mxXmlRequest.prototype.setBinary = function(a) {
+  this.binary = a
+};
+mxXmlRequest.prototype.getText = function() {
+  return this.request.responseText
+};
+mxXmlRequest.prototype.isReady = function() {
+  return 4 == this.request.readyState
+};
+mxXmlRequest.prototype.getDocumentElement = function() {
+  var a = this.getXml();
+  return null != a ? a.documentElement : null
+};
+mxXmlRequest.prototype.getXml = function() {
+  var a = this.request.responseXML;
+  if (9 <= document.documentMode || null == a || null == a.documentElement) a = mxUtils.parseXml(this.request.responseText);
+  return a
+};
+mxXmlRequest.prototype.getText = function() {
+  return this.request.responseText
+};
+mxXmlRequest.prototype.getStatus = function() {
+  return this.request.status
+};
+mxXmlRequest.prototype.create = function() {
+  if (window.XMLHttpRequest) return function() {
+    var a = new XMLHttpRequest;
+    this.isBinary() && a.overrideMimeType && a.overrideMimeType("text/plain; charset\x3dx-user-defined");
+    return a
+  };
+  if ("undefined" != typeof ActiveXObject) return function() {
+    return new ActiveXObject("Microsoft.XMLHTTP")
+  }
+}();
+mxXmlRequest.prototype.send = function(a, b) {
+  this.request = this.create();
+  null != this.request && (null != a && (this.request.onreadystatechange = mxUtils.bind(this, function() {
+    this.isReady() && (a(this), this.onreadystatechaange = null)
+  })), this.request.open(this.method, this.url, this.async, this.username, this.password), this.setRequestHeaders(this.request, this.params), this.request.send(this.params))
+};
+mxXmlRequest.prototype.setRequestHeaders = function(a, b) {
+  null != b && a.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
+};
+mxXmlRequest.prototype.simulate = function(a, b) {
+  a = a || document;
+  var c = null;
+  a == document && (c = window.onbeforeunload, window.onbeforeunload = null);
+  var d = a.createElement("form");
+  d.setAttribute("method", this.method);
+  d.setAttribute("action", this.url);
+  null != b && d.setAttribute("target", b);
+  d.style.display = "none";
+  d.style.visibility = "hidden";
+  for (var e = 0 < this.params.indexOf("\x26") ? this.params.split("\x26") : this.params.split(), f = 0; f < e.length; f++) {
+    var g = e[f].indexOf("\x3d");
+    if (0 < g) {
+      var h = e[f].substring(0, g),
+        k = e[f].substring(g +
+          1),
+        g = a.createElement("textarea");
+      g.setAttribute("name", h);
+      k = k.replace(/\n/g, "\x26#xa;");
+      h = a.createTextNode(k);
+      g.appendChild(h);
+      d.appendChild(g)
+    }
+  }
+  a.body.appendChild(d);
+  d.submit();
+  a.body.removeChild(d);
+  null != c && (window.onbeforeunload = c)
+};
+var mxClipboard = {
+  STEPSIZE: 10,
+  insertCount: 1,
+  cells: null,
+  isEmpty: function() {
+    return null == mxClipboard.cells
+  },
+  cut: function(a, b) {
+    b = mxClipboard.copy(a, b);
+    mxClipboard.insertCount = 0;
+    mxClipboard.removeCells(a, b);
+    return b
+  },
+  removeCells: function(a, b) {
+    a.removeCells(b)
+  },
+  copy: function(a, b) {
+    b = b || a.getSelectionCells();
+    var c = a.getExportableCells(b);
+    mxClipboard.insertCount = 1;
+    mxClipboard.cells = a.cloneCells(c);
+    return c
+  },
+  paste: function(a) {
+    if (null != mxClipboard.cells) {
+      var b = a.getImportableCells(mxClipboard.cells),
+        c = mxClipboard.insertCount *
+        mxClipboard.STEPSIZE,
+        d = a.getDefaultParent(),
+        b = a.importCells(b, c, c, d);
+      mxClipboard.insertCount++;
+      a.setSelectionCells(b)
+    }
+  }
+};
+
+function mxWindow(a, b, c, d, e, f, g, h, k, l) {
+  null != b && (g = null != g ? g : !0, this.content = b, this.init(c, d, e, f, l), this.installMaximizeHandler(), this.installMinimizeHandler(), this.installCloseHandler(), this.setMinimizable(g),
+    this.setTitle(a), (null == h || h) && this.installMoveHandler(), null != k && null != k.parentNode ? k.parentNode.replaceChild(this.div, k) : document.body.appendChild(this.div))
+}
+mxWindow.prototype = new mxEventSource;
+mxWindow.prototype.constructor = mxWindow;
+mxWindow.prototype.closeImage = mxClient.imageBasePath + "/close.gif";
+mxWindow.prototype.minimizeImage = mxClient.imageBasePath + "/minimize.gif";
+mxWindow.prototype.normalizeImage = mxClient.imageBasePath + "/normalize.gif";
+mxWindow.prototype.maximizeImage = mxClient.imageBasePath + "/maximize.gif";
+mxWindow.prototype.resizeImage = mxClient.imageBasePath + "/resize.gif";
+mxWindow.prototype.visible = !1;
+mxWindow.prototype.content = !1;
+mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);
+mxWindow.prototype.title = !1;
+mxWindow.prototype.content = !1;
+mxWindow.prototype.destroyOnClose = !0;
+mxWindow.prototype.init = function(a, b, c, d, e) {
+  e = null != e ? e : "mxWindow";
+  this.div = document.createElement("div");
+  this.div.className = e;
+  this.div.style.left = a + "px";
+  this.div.style.top = b + "px";
+  this.table = document.createElement("table");
+  this.table.className = e;
+  null != c && (mxClient.IS_IE || (this.div.style.width = c + "px"), this.table.style.width = c + "px");
+  null != d && (mxClient.IS_IE || (this.div.style.height = d + "px"), this.table.style.height = d + "px");
+  a = document.createElement("tbody");
+  b = document.createElement("tr");
+  this.title = document.createElement("td");
+  this.title.className = e + "Title";
+  b.appendChild(this.title);
+  a.appendChild(b);
+  b = document.createElement("tr");
+  this.td = document.createElement("td");
+  this.td.className = e + "Pane";
+  this.contentWrapper = document.createElement("div");
+  this.contentWrapper.className = e + "Pane";
+  this.contentWrapper.style.width = "100%";
+  this.contentWrapper.appendChild(this.content);
+  if (mxClient.IS_IE || "DIV" != this.content.nodeName.toUpperCase()) this.contentWrapper.style.height = "100%";
+  this.td.appendChild(this.contentWrapper);
+  b.appendChild(this.td);
+  a.appendChild(b);
+  this.table.appendChild(a);
+  this.div.appendChild(this.table);
+  e = mxUtils.bind(this, function(a) {
+    this.activate()
+  });
+  mxEvent.addListener(this.title, "mousedown", e);
+  mxEvent.addListener(this.table, "mousedown", e);
+  mxClient.IS_TOUCH && (mxEvent.addListener(this.title, "touchstart", e), mxEvent.addListener(this.table, "touchstart", e));
+  this.hide()
+};
+mxWindow.prototype.setTitle = function(a) {
+  for (var b = this.title.firstChild; null != b;) {
+    var c = b.nextSibling;
+    b.nodeType == mxConstants.NODETYPE_TEXT && b.parentNode.removeChild(b);
+    b = c
+  }
+  mxUtils.write(this.title, a || "")
+};
+mxWindow.prototype.setScrollable = function(a) {
+  0 > navigator.userAgent.indexOf("Presto/2.5") && (this.contentWrapper.style.overflow = a ? "auto" : "hidden")
+};
+mxWindow.prototype.activate = function() {
+  if (mxWindow.activeWindow != this) {
+    var a = mxUtils.getCurrentStyle(this.getElement()),
+      a = null != a ? a.zIndex : 3;
+    if (mxWindow.activeWindow) {
+      var b = mxWindow.activeWindow.getElement();
+      null != b && null != b.style && (b.style.zIndex = a)
+    }
+    b = mxWindow.activeWindow;
+    this.getElement().style.zIndex = parseInt(a) + 1;
+    mxWindow.activeWindow = this;
+    this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, "previousWindow", b))
+  }
+};
+mxWindow.prototype.getElement = function() {
+  return this.div
+};
+mxWindow.prototype.fit = function() {
+  mxUtils.fit(this.div)
+};
+mxWindow.prototype.isResizable = function() {
+  return null != this.resize ? "none" != this.resize.style.display : !1
+};
+mxWindow.prototype.setResizable = function(a) {
+  a ? null == this.resize ? (this.resize = document.createElement("img"), this.resize.style.position = "absolute", this.resize.style.bottom = "2px", this.resize.style.right = "2px", this.resize.setAttribute(
+      "src", mxClient.imageBasePath + "/resize.gif"), this.resize.style.cursor = "nw-resize", mxEvent.addGestureListeners(this.resize, mxUtils.bind(this, function(a) {
+      this.activate();
+      var c = mxEvent.getClientX(a),
+        d = mxEvent.getClientY(a),
+        e = this.div.offsetWidth,
+        f = this.div.offsetHeight,
+        g = mxUtils.bind(this,
+          function(a) {
+            var b = mxEvent.getClientX(a) - c,
+              g = mxEvent.getClientY(a) - d;
+            this.setSize(e + b, f + g);
+            this.fireEvent(new mxEventObject(mxEvent.RESIZE, "event", a));
+            mxEvent.consume(a)
+          }),
+        h = mxUtils.bind(this, function(a) {
+          mxEvent.removeGestureListeners(document, null, g, h);
+          this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, "event", a));
+          mxEvent.consume(a)
+        });
+      mxEvent.addGestureListeners(document, null, g, h);
+      this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, "event", a));
+      mxEvent.consume(a)
+    }), null, null), this.div.appendChild(this.resize)) :
+    this.resize.style.display = "inline" : null != this.resize && (this.resize.style.display = "none")
+};
+mxWindow.prototype.setSize = function(a, b) {
+  a = Math.max(this.minimumSize.width, a);
+  b = Math.max(this.minimumSize.height, b);
+  mxClient.IS_IE || (this.div.style.width = a + "px", this.div.style.height = b + "px");
+  this.table.style.width = a + "px";
+  this.table.style.height = b + "px";
+  mxClient.IS_IE || (this.contentWrapper.style.height = this.div.offsetHeight - this.title.offsetHeight - 2 + "px")
+};
+mxWindow.prototype.setMinimizable = function(a) {
+  this.minimize.style.display = a ? "" : "none"
+};
+mxWindow.prototype.getMinimumSize = function() {
+  return new mxRectangle(0, 0, 0, this.title.offsetHeight)
+};
+mxWindow.prototype.installMinimizeHandler = function() {
+  this.minimize = document.createElement("img");
+  this.minimize.setAttribute("src", this.minimizeImage);
+  this.minimize.setAttribute("align", "right");
+  this.minimize.setAttribute("title", "Minimize");
+  this.minimize.style.cursor = "pointer";
+  this.minimize.style.marginRight = "1px";
+  this.minimize.style.display = "none";
+  this.title.appendChild(this.minimize);
+  var a = !1,
+    b = null,
+    c = null,
+    d = mxUtils.bind(this, function(d) {
+      this.activate();
+      if (a) a = !1, this.minimize.setAttribute("src",
+        this.minimizeImage), this.minimize.setAttribute("title", "Minimize"), this.contentWrapper.style.display = "", this.maximize.style.display = b, mxClient.IS_IE || (this.div.style.height =
+        c), this.table.style.height = c, null != this.resize && (this.resize.style.visibility = ""), this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, "event", d));
+      else {
+        a = !0;
+        this.minimize.setAttribute("src", this.normalizeImage);
+        this.minimize.setAttribute("title", "Normalize");
+        this.contentWrapper.style.display = "none";
+        b = this.maximize.style.display;
+        this.maximize.style.display = "none";
+        c = this.table.style.height;
+        var f = this.getMinimumSize();
+        0 < f.height && (mxClient.IS_IE || (this.div.style.height = f.height + "px"), this.table.style.height = f.height + "px");
+        0 < f.width && (mxClient.IS_IE || (this.div.style.width = f.width + "px"), this.table.style.width = f.width + "px");
+        null != this.resize && (this.resize.style.visibility = "hidden");
+        this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, "event", d))
+      }
+      mxEvent.consume(d)
+    });
+  mxEvent.addListener(this.minimize, "mousedown", d);
+  mxClient.IS_TOUCH &&
+    mxEvent.addListener(this.minimize, "touchstart", d)
+};
+mxWindow.prototype.setMaximizable = function(a) {
+  this.maximize.style.display = a ? "" : "none"
+};
+mxWindow.prototype.installMaximizeHandler = function() {
+  this.maximize = document.createElement("img");
+  this.maximize.setAttribute("src", this.maximizeImage);
+  this.maximize.setAttribute("align", "right");
+  this.maximize.setAttribute("title", "Maximize");
+  this.maximize.style.cursor = "default";
+  this.maximize.style.marginLeft = "1px";
+  this.maximize.style.cursor = "pointer";
+  this.maximize.style.display = "none";
+  this.title.appendChild(this.maximize);
+  var a = !1,
+    b = null,
+    c = null,
+    d = null,
+    e = null,
+    f = mxUtils.bind(this, function(f) {
+      this.activate();
+      if ("none" != this.maximize.style.display) {
+        if (a) {
+          a = !1;
+          this.maximize.setAttribute("src", this.maximizeImage);
+          this.maximize.setAttribute("title", "Maximize");
+          this.contentWrapper.style.display = "";
+          this.minimize.style.visibility = "";
+          this.div.style.left = b + "px";
+          this.div.style.top = c + "px";
+          if (!mxClient.IS_IE && (this.div.style.height = d, this.div.style.width = e, h = mxUtils.getCurrentStyle(this.contentWrapper), "auto" == h.overflow || null != this.resize)) this.contentWrapper
+            .style.height = this.div.offsetHeight - this.title.offsetHeight -
+            2 + "px";
+          this.table.style.height = d;
+          this.table.style.width = e;
+          null != this.resize && (this.resize.style.visibility = "");
+          this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, "event", f))
+        } else {
+          a = !0;
+          this.maximize.setAttribute("src", this.normalizeImage);
+          this.maximize.setAttribute("title", "Normalize");
+          this.contentWrapper.style.display = "";
+          this.minimize.style.visibility = "hidden";
+          b = parseInt(this.div.style.left);
+          c = parseInt(this.div.style.top);
+          d = this.table.style.height;
+          e = this.table.style.width;
+          this.div.style.left = "0px";
+          this.div.style.top = "0px";
+          mxClient.IS_IE || (this.div.style.height = document.body.clientHeight - 2 + "px", this.div.style.width = document.body.clientWidth - 2 + "px");
+          this.table.style.width = document.body.clientWidth - 2 + "px";
+          this.table.style.height = document.body.clientHeight - 2 + "px";
+          null != this.resize && (this.resize.style.visibility = "hidden");
+          if (!mxClient.IS_IE) {
+            var h = mxUtils.getCurrentStyle(this.contentWrapper);
+            if ("auto" == h.overflow || null != this.resize) this.contentWrapper.style.height = this.div.offsetHeight - this.title.offsetHeight -
+              2 + "px"
+          }
+          this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, "event", f))
+        }
+        mxEvent.consume(f)
+      }
+    });
+  mxEvent.addGestureListeners(this.maximize, f);
+  mxEvent.addListener(this.title, "dblclick", f)
+};
+mxWindow.prototype.installMoveHandler = function() {
+  this.title.style.cursor = "move";
+  mxEvent.addGestureListeners(this.title, mxUtils.bind(this, function(a) {
+    var b = mxEvent.getClientX(a),
+      c = mxEvent.getClientY(a),
+      d = this.getX(),
+      e = this.getY(),
+      f = mxUtils.bind(this, function(a) {
+        var f = mxEvent.getClientX(a) - b,
+          g = mxEvent.getClientY(a) - c;
+        this.setLocation(d + f, e + g);
+        this.fireEvent(new mxEventObject(mxEvent.MOVE, "event", a));
+        mxEvent.consume(a)
+      }),
+      g = mxUtils.bind(this, function(a) {
+        mxEvent.removeGestureListeners(document, null, f,
+          g);
+        this.fireEvent(new mxEventObject(mxEvent.MOVE_END, "event", a));
+        mxEvent.consume(a)
+      });
+    mxEvent.addGestureListeners(document, null, f, g);
+    this.fireEvent(new mxEventObject(mxEvent.MOVE_START, "event", a));
+    mxEvent.consume(a)
+  }))
+};
+mxWindow.prototype.setLocation = function(a, b) {
+  this.div.style.left = a + "px";
+  this.div.style.top = b + "px"
+};
+mxWindow.prototype.getX = function() {
+  return parseInt(this.div.style.left)
+};
+mxWindow.prototype.getY = function() {
+  return parseInt(this.div.style.top)
+};
+mxWindow.prototype.installCloseHandler = function() {
+  this.closeImg = document.createElement("img");
+  this.closeImg.setAttribute("src", this.closeImage);
+  this.closeImg.setAttribute("align", "right");
+  this.closeImg.setAttribute("title", "Close");
+  this.closeImg.style.marginLeft = "2px";
+  this.closeImg.style.cursor = "pointer";
+  this.closeImg.style.display = "none";
+  this.title.insertBefore(this.closeImg, this.title.firstChild);
+  mxEvent.addGestureListeners(this.closeImg, mxUtils.bind(this, function(a) {
+    this.fireEvent(new mxEventObject(mxEvent.CLOSE,
+      "event", a));
+    this.destroyOnClose ? this.destroy() : this.setVisible(!1);
+    mxEvent.consume(a)
+  }))
+};
+mxWindow.prototype.setImage = function(a) {
+  this.image = document.createElement("img");
+  this.image.setAttribute("src", a);
+  this.image.setAttribute("align", "left");
+  this.image.style.marginRight = "4px";
+  this.image.style.marginLeft = "0px";
+  this.image.style.marginTop = "-2px";
+  this.title.insertBefore(this.image, this.title.firstChild)
+};
+mxWindow.prototype.setClosable = function(a) {
+  this.closeImg.style.display = a ? "" : "none"
+};
+mxWindow.prototype.isVisible = function() {
+  return null != this.div ? "hidden" != this.div.style.visibility : !1
+};
+mxWindow.prototype.setVisible = function(a) {
+  null != this.div && this.isVisible() != a && (a ? this.show() : this.hide())
+};
+mxWindow.prototype.show = function() {
+  this.div.style.visibility = "";
+  this.activate();
+  var a = mxUtils.getCurrentStyle(this.contentWrapper);
+  if (!mxClient.IS_IE && ("auto" == a.overflow || null != this.resize)) this.contentWrapper.style.height = this.div.offsetHeight - this.title.offsetHeight - 2 + "px";
+  this.fireEvent(new mxEventObject(mxEvent.SHOW))
+};
+mxWindow.prototype.hide = function() {
+  this.div.style.visibility = "hidden";
+  this.fireEvent(new mxEventObject(mxEvent.HIDE))
+};
+mxWindow.prototype.destroy = function() {
+  this.fireEvent(new mxEventObject(mxEvent.DESTROY));
+  null != this.div && (mxEvent.release(this.div), this.div.parentNode.removeChild(this.div), this.div = null);
+  this.contentWrapper = this.content = this.title = null
+};
+
+function mxForm(a) {
+  this.table = document.createElement("table");
+  this.table.className = a;
+  this.body = document.createElement("tbody");
+  this.table.appendChild(this.body)
+}
+mxForm.prototype.table = null;
+mxForm.prototype.body = !1;
+mxForm.prototype.getTable = function() {
+  return this.table
+};
+mxForm.prototype.addButtons = function(a, b) {
+  var c = document.createElement("tr"),
+    d = document.createElement("td");
+  c.appendChild(d);
+  var d = document.createElement("td"),
+    e = document.createElement("button");
+  mxUtils.write(e, mxResources.get("ok") || "OK");
+  d.appendChild(e);
+  mxEvent.addListener(e, "click", function() {
+    a()
+  });
+  e = document.createElement("button");
+  mxUtils.write(e, mxResources.get("cancel") || "Cancel");
+  d.appendChild(e);
+  mxEvent.addListener(e, "click", function() {
+    b()
+  });
+  c.appendChild(d);
+  this.body.appendChild(c)
+};
+mxForm.prototype.addText = function(a, b) {
+  var c = document.createElement("input");
+  c.setAttribute("type", "text");
+  c.value = b;
+  return this.addField(a, c)
+};
+mxForm.prototype.addCheckbox = function(a, b) {
+  var c = document.createElement("input");
+  c.setAttribute("type", "checkbox");
+  this.addField(a, c);
+  b && (c.checked = !0);
+  return c
+};
+mxForm.prototype.addTextarea = function(a, b, c) {
+  var d = document.createElement("textarea");
+  mxClient.IS_NS && c--;
+  d.setAttribute("rows", c || 2);
+  d.value = b;
+  return this.addField(a, d)
+};
+mxForm.prototype.addCombo = function(a, b, c) {
+  var d = document.createElement("select");
+  null != c && d.setAttribute("size", c);
+  b && d.setAttribute("multiple", "true");
+  return this.addField(a, d)
+};
+mxForm.prototype.addOption = function(a, b, c, d) {
+  var e = document.createElement("option");
+  mxUtils.writeln(e, b);
+  e.setAttribute("value", c);
+  d && e.setAttribute("selected", d);
+  a.appendChild(e)
+};
+mxForm.prototype.addField = function(a, b) {
+  var c = document.createElement("tr"),
+    d = document.createElement("td");
+  mxUtils.write(d, a);
+  c.appendChild(d);
+  d = document.createElement("td");
+  d.appendChild(b);
+  c.appendChild(d);
+  this.body.appendChild(c);
+  return b
+};
+
+function mxImage(a, b, c) {
+  this.src = a;
+  this.width = b;
+  this.height = c
+}
+mxImage.prototype.src = null;
+mxImage.prototype.width = null;
+mxImage.prototype.height = null;
+
+function mxDivResizer(a, b) {
+  if ("div" == a.nodeName.toLowerCase()) {
+    null == b && (b = window);
+    this.div = a;
+    var c = mxUtils.getCurrentStyle(a);
+    null != c && (this.resizeWidth = "auto" == c.width, this.resizeHeight = "auto" == c.height);
+    mxEvent.addListener(b, "resize", mxUtils.bind(this, function(a) {
+      this.handlingResize || (this.handlingResize = !0, this.resize(), this.handlingResize = !1)
+    }));
+    this.resize()
+  }
+}
+mxDivResizer.prototype.resizeWidth = !0;
+mxDivResizer.prototype.resizeHeight = !0;
+mxDivResizer.prototype.handlingResize = !1;
+mxDivResizer.prototype.resize = function() {
+  var a = this.getDocumentWidth(),
+    b = this.getDocumentHeight(),
+    c = parseInt(this.div.style.left),
+    d = parseInt(this.div.style.right),
+    e = parseInt(this.div.style.top),
+    f = parseInt(this.div.style.bottom);
+  this.resizeWidth && (!isNaN(c) && !isNaN(d) && 0 <= c && 0 <= d && 0 < a - d - c) && (this.div.style.width = a - d - c + "px");
+  this.resizeHeight && (!isNaN(e) && !isNaN(f) && 0 <= e && 0 <= f && 0 < b - e - f) && (this.div.style.height = b - e - f + "px")
+};
+mxDivResizer.prototype.getDocumentWidth = function() {
+  return document.body.clientWidth
+};
+mxDivResizer.prototype.getDocumentHeight = function() {
+  return document.body.clientHeight
+};
+
+function mxDragSource(a, b) {
+  this.element = a;
+  this.dropHandler = b;
+  mxEvent.addGestureListeners(a, mxUtils.bind(this, this.mouseDown))
+}
+mxDragSource.prototype.element = null;
+mxDragSource.prototype.dropHandler = null;
+mxDragSource.prototype.dragOffset = null;
+mxDragSource.prototype.dragElement = null;
+mxDragSource.prototype.previewElement = null;
+mxDragSource.prototype.enabled = !0;
+mxDragSource.prototype.currentGraph = null;
+mxDragSource.prototype.currentDropTarget = null;
+mxDragSource.prototype.currentPoint = null;
+mxDragSource.prototype.currentGuide = null;
+mxDragSource.prototype.currentHighlight = null;
+mxDragSource.prototype.autoscroll = !0;
+mxDragSource.prototype.guidesEnabled = !0;
+mxDragSource.prototype.gridEnabled = !0;
+mxDragSource.prototype.highlightDropTargets = !0;
+mxDragSource.prototype.dragElementZIndex = 100;
+mxDragSource.prototype.dragElementOpacity = 70;
+mxDragSource.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxDragSource.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxDragSource.prototype.isGuidesEnabled = function() {
+  return this.guidesEnabled
+};
+mxDragSource.prototype.setGuidesEnabled = function(a) {
+  this.guidesEnabled = a
+};
+mxDragSource.prototype.isGridEnabled = function() {
+  return this.gridEnabled
+};
+mxDragSource.prototype.setGridEnabled = function(a) {
+  this.gridEnabled = a
+};
+mxDragSource.prototype.getGraphForEvent = function(a) {
+  return null
+};
+mxDragSource.prototype.getDropTarget = function(a, b, c) {
+  return a.getCellAt(b, c)
+};
+mxDragSource.prototype.createDragElement = function(a) {
+  return this.element.cloneNode(!0)
+};
+mxDragSource.prototype.createPreviewElement = function(a) {
+  return null
+};
+mxDragSource.prototype.mouseDown = function(a) {
+  this.enabled && (!mxEvent.isConsumed(a) && null == this.mouseMoveHandler) && (this.startDrag(a), this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove), this.mouseUpHandler = mxUtils.bind(
+    this, this.mouseUp), mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler), mxEvent.consume(a, !0, !1))
+};
+mxDragSource.prototype.startDrag = function(a) {
+  this.dragElement = this.createDragElement(a);
+  this.dragElement.style.position = "absolute";
+  this.dragElement.style.zIndex = this.dragElementZIndex;
+  mxUtils.setOpacity(this.dragElement, this.dragElementOpacity)
+};
+mxDragSource.prototype.stopDrag = function(a) {
+  null != this.dragElement && (null != this.dragElement.parentNode && this.dragElement.parentNode.removeChild(this.dragElement), this.dragElement = null)
+};
+mxDragSource.prototype.graphContainsEvent = function(a, b) {
+  var c = mxEvent.getClientX(b),
+    d = mxEvent.getClientY(b),
+    e = mxUtils.getOffset(a.container),
+    f = mxUtils.getScrollOrigin();
+  return c >= e.x - f.x && d >= e.y - f.y && c <= e.x - f.x + a.container.offsetWidth && d <= e.y - f.y + a.container.offsetHeight
+};
+mxDragSource.prototype.mouseMove = function(a) {
+  var b = this.getGraphForEvent(a);
+  null != b && !this.graphContainsEvent(b, a) && (b = null);
+  b != this.currentGraph && (null != this.currentGraph && this.dragExit(this.currentGraph), this.currentGraph = b, null != this.currentGraph && this.dragEnter(this.currentGraph));
+  null != this.currentGraph && this.dragOver(this.currentGraph, a);
+  if (null != this.dragElement && (null == this.previewElement || "visible" != this.previewElement.style.visibility)) {
+    var b = mxEvent.getClientX(a),
+      c = mxEvent.getClientY(a);
+    null == this.dragElement.parentNode && document.body.appendChild(this.dragElement);
+    this.dragElement.style.visibility = "visible";
+    null != this.dragOffset && (b += this.dragOffset.x, c += this.dragOffset.y);
+    b += document.body.scrollLeft || document.documentElement.scrollLeft;
+    c += document.body.scrollTop || document.documentElement.scrollTop;
+    this.dragElement.style.left = b + "px";
+    this.dragElement.style.top = c + "px"
+  } else null != this.dragElement && (this.dragElement.style.visibility = "hidden");
+  mxEvent.consume(a)
+};
+mxDragSource.prototype.mouseUp = function(a) {
+  if (null != this.currentGraph) {
+    if (null != this.currentPoint && (null == this.previewElement || "hidden" != this.previewElement.style.visibility)) {
+      var b = this.currentGraph.view.scale,
+        c = this.currentGraph.view.translate;
+      this.drop(this.currentGraph, a, this.currentDropTarget, this.currentPoint.x / b - c.x, this.currentPoint.y / b - c.y)
+    }
+    this.dragExit(this.currentGraph)
+  }
+  this.stopDrag(a);
+  mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+  this.currentGraph =
+    this.mouseUpHandler = this.mouseMoveHandler = null;
+  mxEvent.consume(a)
+};
+mxDragSource.prototype.dragEnter = function(a) {
+  a.isMouseDown = !0;
+  this.previewElement = this.createPreviewElement(a);
+  this.isGuidesEnabled() && null != this.previewElement && (this.currentGuide = new mxGuide(a, a.graphHandler.getGuideStates()));
+  this.highlightDropTargets && (this.currentHighlight = new mxCellHighlight(a, mxConstants.DROP_TARGET_COLOR))
+};
+mxDragSource.prototype.dragExit = function(a) {
+  this.currentPoint = this.currentDropTarget = null;
+  a.isMouseDown = !1;
+  null != this.previewElement && (null != this.previewElement.parentNode && this.previewElement.parentNode.removeChild(this.previewElement), this.previewElement = null);
+  null != this.currentGuide && (this.currentGuide.destroy(), this.currentGuide = null);
+  null != this.currentHighlight && (this.currentHighlight.destroy(), this.currentHighlight = null)
+};
+mxDragSource.prototype.dragOver = function(a, b) {
+  var c = mxUtils.getOffset(a.container),
+    d = mxUtils.getScrollOrigin(a.container),
+    e = mxEvent.getClientX(b) - c.x + d.x,
+    c = mxEvent.getClientY(b) - c.y + d.y;
+  a.autoScroll && (null == this.autoscroll || this.autoscroll) && a.scrollPointToVisible(e, c, a.autoExtend);
+  null != this.currentHighlight && a.isDropEnabled() && (this.currentDropTarget = this.getDropTarget(a, e, c), d = a.getView().getState(this.currentDropTarget), this.currentHighlight.highlight(d));
+  if (null != this.previewElement) {
+    null == this.previewElement.parentNode &&
+      (a.container.appendChild(this.previewElement), this.previewElement.style.zIndex = "3", this.previewElement.style.position = "absolute");
+    var d = this.isGridEnabled() && a.isGridEnabledEvent(b),
+      f = !0;
+    if (null != this.currentGuide && this.currentGuide.isEnabledForEvent(b)) var f = parseInt(this.previewElement.style.width),
+      g = parseInt(this.previewElement.style.height),
+      f = new mxRectangle(0, 0, f, g),
+      c = new mxPoint(e, c),
+      c = this.currentGuide.move(f, c, d),
+      f = !1,
+      e = c.x,
+      c = c.y;
+    else if (d) var d = a.view.scale,
+      g = a.view.translate,
+      h = a.gridSize /
+      2,
+      e = (a.snap(e / d - g.x - h) + g.x) * d,
+      c = (a.snap(c / d - g.y - h) + g.y) * d;
+    null != this.currentGuide && f && this.currentGuide.hide();
+    null != this.previewOffset && (e += this.previewOffset.x, c += this.previewOffset.y);
+    this.previewElement.style.left = Math.round(e) + "px";
+    this.previewElement.style.top = Math.round(c) + "px";
+    this.previewElement.style.visibility = "visible"
+  }
+  this.currentPoint = new mxPoint(e, c)
+};
+mxDragSource.prototype.drop = function(a, b, c, d, e) {
+  this.dropHandler(a, b, c, d, e);
+  a.container.focus()
+};
+
+function mxToolbar(a) {
+  this.container = a
+}
+mxToolbar.prototype = new mxEventSource;
+mxToolbar.prototype.constructor = mxToolbar;
+mxToolbar.prototype.container = null;
+mxToolbar.prototype.enabled = !0;
+mxToolbar.prototype.noReset = !1;
+mxToolbar.prototype.updateDefaultMode = !0;
+mxToolbar.prototype.addItem = function(a, b, c, d, e, f) {
+  var g = document.createElement(null != b ? "img" : "button"),
+    h = e || (null != f ? "mxToolbarMode" : "mxToolbarItem");
+  g.className = h;
+  g.setAttribute("src", b);
+  null != a && (null != b ? g.setAttribute("title", a) : mxUtils.write(g, a));
+  this.container.appendChild(g);
+  null != c && (mxEvent.addListener(g, "click", c), mxClient.IS_TOUCH && mxEvent.addListener(g, "touchend", c));
+  a = mxUtils.bind(this, function(a) {
+    null != d ? g.setAttribute("src", b) : g.style.backgroundColor = ""
+  });
+  mxEvent.addGestureListeners(g,
+    mxUtils.bind(this, function(a) {
+      null != d ? g.setAttribute("src", d) : g.style.backgroundColor = "gray";
+      if (null != f) {
+        null == this.menu && (this.menu = new mxPopupMenu, this.menu.init());
+        var b = this.currentImg;
+        this.menu.isMenuShowing() && this.menu.hideMenu();
+        b != g && (this.currentImg = g, this.menu.factoryMethod = f, b = new mxPoint(g.offsetLeft, g.offsetTop + g.offsetHeight), this.menu.popup(b.x, b.y, null, a), this.menu.isMenuShowing() &&
+          (g.className = h + "Selected", this.menu.hideMenu = function() {
+            mxPopupMenu.prototype.hideMenu.apply(this);
+            g.className = h;
+            this.currentImg = null
+          }))
+      }
+    }), null, a);
+  mxEvent.addListener(g, "mouseout", a);
+  return g
+};
+mxToolbar.prototype.addCombo = function(a) {
+  var b = document.createElement("div");
+  b.style.display = "inline";
+  b.className = "mxToolbarComboContainer";
+  var c = document.createElement("select");
+  c.className = a || "mxToolbarCombo";
+  b.appendChild(c);
+  this.container.appendChild(b);
+  return c
+};
+mxToolbar.prototype.addActionCombo = function(a, b) {
+  var c = document.createElement("select");
+  c.className = b || "mxToolbarCombo";
+  this.addOption(c, a, null);
+  mxEvent.addListener(c, "change", function(a) {
+    var b = c.options[c.selectedIndex];
+    c.selectedIndex = 0;
+    null != b.funct && b.funct(a)
+  });
+  this.container.appendChild(c);
+  return c
+};
+mxToolbar.prototype.addOption = function(a, b, c) {
+  var d = document.createElement("option");
+  mxUtils.writeln(d, b);
+  "function" == typeof c ? d.funct = c : d.setAttribute("value", c);
+  a.appendChild(d);
+  return d
+};
+mxToolbar.prototype.addSwitchMode = function(a, b, c, d, e) {
+  var f = document.createElement("img");
+  f.initialClassName = e || "mxToolbarMode";
+  f.className = f.initialClassName;
+  f.setAttribute("src", b);
+  f.altIcon = d;
+  null != a && f.setAttribute("title", a);
+  mxEvent.addListener(f, "click", mxUtils.bind(this, function(a) {
+    a = this.selectedMode.altIcon;
+    null != a ? (this.selectedMode.altIcon = this.selectedMode.getAttribute("src"), this.selectedMode.setAttribute("src", a)) : this.selectedMode.className = this.selectedMode.initialClassName;
+    this.updateDefaultMode &&
+      (this.defaultMode = f);
+    this.selectedMode = f;
+    a = f.altIcon;
+    null != a ? (f.altIcon = f.getAttribute("src"), f.setAttribute("src", a)) : f.className = f.initialClassName + "Selected";
+    this.fireEvent(new mxEventObject(mxEvent.SELECT));
+    c()
+  }));
+  this.container.appendChild(f);
+  null == this.defaultMode && (this.defaultMode = f, this.selectMode(f), c());
+  return f
+};
+mxToolbar.prototype.addMode = function(a, b, c, d, e, f) {
+  f = null != f ? f : !0;
+  var g = document.createElement(null != b ? "img" : "button");
+  g.initialClassName = e || "mxToolbarMode";
+  g.className = g.initialClassName;
+  g.setAttribute("src", b);
+  g.altIcon = d;
+  null != a && g.setAttribute("title", a);
+  this.enabled && f && (mxEvent.addListener(g, "click", mxUtils.bind(this, function(a) {
+      this.selectMode(g, c);
+      this.noReset = !1
+    })), mxEvent.addListener(g, "dblclick", mxUtils.bind(this, function(a) {
+      this.selectMode(g, c);
+      this.noReset = !0
+    })), null == this.defaultMode &&
+    (this.defaultMode = g, this.defaultFunction = c, this.selectMode(g, c)));
+  this.container.appendChild(g);
+  return g
+};
+mxToolbar.prototype.selectMode = function(a, b) {
+  if (this.selectedMode != a) {
+    if (null != this.selectedMode) {
+      var c = this.selectedMode.altIcon;
+      null != c ? (this.selectedMode.altIcon = this.selectedMode.getAttribute("src"), this.selectedMode.setAttribute("src", c)) : this.selectedMode.className = this.selectedMode.initialClassName
+    }
+    this.selectedMode = a;
+    c = this.selectedMode.altIcon;
+    null != c ? (this.selectedMode.altIcon = this.selectedMode.getAttribute("src"), this.selectedMode.setAttribute("src", c)) : this.selectedMode.className = this.selectedMode.initialClassName +
+      "Selected";
+    this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", b))
+  }
+};
+mxToolbar.prototype.resetMode = function(a) {
+  (a || !this.noReset) && this.selectedMode != this.defaultMode && this.selectMode(this.defaultMode, this.defaultFunction)
+};
+mxToolbar.prototype.addSeparator = function(a) {
+  return this.addItem(null, a, null)
+};
+mxToolbar.prototype.addBreak = function() {
+  mxUtils.br(this.container)
+};
+mxToolbar.prototype.addLine = function() {
+  var a = document.createElement("hr");
+  a.style.marginRight = "6px";
+  a.setAttribute("size", "1");
+  this.container.appendChild(a)
+};
+mxToolbar.prototype.destroy = function() {
+  mxEvent.release(this.container);
+  this.selectedMode = this.defaultFunction = this.defaultMode = this.container = null;
+  null != this.menu && this.menu.destroy()
+};
+
+function mxSession(a, b, c, d) {
+  this.model = a;
+  this.urlInit = b;
+  this.urlPoll = c;
+  this.urlNotify = d;
+  null != a && (this.codec = new mxCodec, this.codec.lookup = function(b) {
+    return a.getCell(b)
+  });
+  a.addListener(mxEvent.NOTIFY, mxUtils.bind(this, function(a, b) {
+    var c = b.getProperty("edit");
+    (null != c && this.debug || this.connected && !this.suspended) && this.notify("\x3cedit\x3e" + this.encodeChanges(c.changes, c.undone) + "\x3c/edit\x3e")
+  }))
+}
+mxSession.prototype = new mxEventSource;
+mxSession.prototype.constructor = mxSession;
+mxSession.prototype.model = null;
+mxSession.prototype.urlInit = null;
+mxSession.prototype.urlPoll = null;
+mxSession.prototype.urlNotify = null;
+mxSession.prototype.codec = null;
+mxSession.prototype.linefeed = "\x26#xa;";
+mxSession.prototype.escapePostData = !0;
+mxSession.prototype.significantRemoteChanges = !0;
+mxSession.prototype.sent = 0;
+mxSession.prototype.received = 0;
+mxSession.prototype.debug = !1;
+mxSession.prototype.connected = !1;
+mxSession.prototype.suspended = !1;
+mxSession.prototype.polling = !1;
+mxSession.prototype.start = function() {
+  this.debug ? (this.connected = !0, this.fireEvent(new mxEventObject(mxEvent.CONNECT))) : this.connected || this.get(this.urlInit, mxUtils.bind(this, function(a) {
+    this.connected = !0;
+    this.fireEvent(new mxEventObject(mxEvent.CONNECT));
+    this.poll()
+  }))
+};
+mxSession.prototype.suspend = function() {
+  this.connected && !this.suspended && (this.suspended = !0, this.fireEvent(new mxEventObject(mxEvent.SUSPEND)))
+};
+mxSession.prototype.resume = function(a, b, c) {
+  this.connected && this.suspended && (this.suspended = !1, this.fireEvent(new mxEventObject(mxEvent.RESUME)), this.polling || this.poll())
+};
+mxSession.prototype.stop = function(a) {
+  this.connected && (this.connected = !1);
+  this.fireEvent(new mxEventObject(mxEvent.DISCONNECT, "reason", a))
+};
+mxSession.prototype.poll = function() {
+  this.connected && !this.suspended && null != this.urlPoll ? (this.polling = !0, this.get(this.urlPoll, mxUtils.bind(this, function() {
+    this.poll()
+  }))) : this.polling = !1
+};
+mxSession.prototype.notify = function(a, b, c) {
+  null != a && 0 < a.length && (null != this.urlNotify && (this.debug ? (mxLog.show(), mxLog.debug("mxSession.notify: " + this.urlNotify + " xml\x3d" + a)) : (a = "\x3cmessage\x3e\x3cdelta\x3e" + a +
+    "\x3c/delta\x3e\x3c/message\x3e", this.escapePostData && (a = encodeURIComponent(a)), mxUtils.post(this.urlNotify, "xml\x3d" + a, b, c))), this.sent += a.length, this.fireEvent(new mxEventObject(
+    mxEvent.NOTIFY, "url", this.urlNotify, "xml", a)))
+};
+mxSession.prototype.get = function(a, b, c) {
+  if ("undefined" != typeof mxUtils) {
+    var d = mxUtils.bind(this, function(a) {
+      null != c ? c(a) : this.stop(a)
+    });
+    mxUtils.get(a, mxUtils.bind(this, function(c) {
+        if ("undefined" != typeof mxUtils)
+          if (c.isReady() && 404 != c.getStatus()) {
+            if (this.received += c.getText().length, this.fireEvent(new mxEventObject(mxEvent.GET, "url", a, "request", c)), this.isValidResponse(c)) {
+              if (0 < c.getText().length) {
+                var f = c.getDocumentElement();
+                null == f ? d("Invalid response: " + c.getText()) : this.receive(f)
+              }
+              null != b && b(c)
+            }
+          } else d("Response not ready")
+      }),
+      function(a) {
+        d("Transmission error")
+      })
+  }
+};
+mxSession.prototype.isValidResponse = function(a) {
+  return 0 > a.getText().indexOf("\x3c?php")
+};
+mxSession.prototype.encodeChanges = function(a, b) {
+  for (var c = "", d = b ? -1 : 1, e = b ? a.length - 1 : 0; 0 <= e && e < a.length; e += d) var f = this.codec.encode(a[e]),
+    c = c + mxUtils.getXml(f, this.linefeed);
+  return c
+};
+mxSession.prototype.receive = function(a) {
+  if (null != a && a.nodeType == mxConstants.NODETYPE_ELEMENT) {
+    var b = a.getAttribute("namespace");
+    null != b && (this.model.prefix = b + "-");
+    for (b = a.firstChild; null != b;) {
+      var c = b.nodeName.toLowerCase();
+      "state" == c ? this.processState(b) : "delta" == c && this.processDelta(b);
+      b = b.nextSibling
+    }
+    this.fireEvent(new mxEventObject(mxEvent.RECEIVE, "node", a))
+  }
+};
+mxSession.prototype.processState = function(a) {
+  (new mxCodec(a.ownerDocument)).decode(a.firstChild, this.model)
+};
+mxSession.prototype.processDelta = function(a) {
+  for (a = a.firstChild; null != a;) "edit" == a.nodeName && this.processEdit(a), a = a.nextSibling
+};
+mxSession.prototype.processEdit = function(a) {
+  a = this.decodeChanges(a);
+  if (0 < a.length) {
+    var b = this.createUndoableEdit(a);
+    this.model.fireEvent(new mxEventObject(mxEvent.CHANGE, "edit", b, "changes", a));
+    this.model.fireEvent(new mxEventObject(mxEvent.UNDO, "edit", b));
+    this.fireEvent(new mxEventObject(mxEvent.FIRED, "edit", b))
+  }
+};
+mxSession.prototype.createUndoableEdit = function(a) {
+  var b = new mxUndoableEdit(this.model, this.significantRemoteChanges);
+  b.changes = a;
+  b.notify = function() {
+    b.source.fireEvent(new mxEventObject(mxEvent.CHANGE, "edit", b, "changes", b.changes));
+    b.source.fireEvent(new mxEventObject(mxEvent.NOTIFY, "edit", b, "changes", b.changes))
+  };
+  return b
+};
+mxSession.prototype.decodeChanges = function(a) {
+  this.codec.document = a.ownerDocument;
+  var b = [];
+  for (a = a.firstChild; null != a;) {
+    var c = this.decodeChange(a);
+    null != c && b.push(c);
+    a = a.nextSibling
+  }
+  return b
+};
+mxSession.prototype.decodeChange = function(a) {
+  var b = null;
+  a.nodeType == mxConstants.NODETYPE_ELEMENT && (b = "mxRootChange" == a.nodeName ? (new mxCodec(a.ownerDocument)).decode(a) : this.codec.decode(a), null != b && (b.model = this.model, b.execute(),
+    "mxChildChange" == a.nodeName && null == b.parent && this.cellRemoved(b.child)));
+  return b
+};
+mxSession.prototype.cellRemoved = function(a, b) {
+  this.codec.putObject(a.getId(), a);
+  for (var c = this.model.getChildCount(a), d = 0; d < c; d++) this.cellRemoved(this.model.getChildAt(a, d))
+};
+
+function mxUndoableEdit(a, b) {
+  this.source = a;
+  this.changes = [];
+  this.significant = null != b ? b : !0
+}
+mxUndoableEdit.prototype.source = null;
+mxUndoableEdit.prototype.changes = null;
+mxUndoableEdit.prototype.significant = null;
+mxUndoableEdit.prototype.undone = !1;
+mxUndoableEdit.prototype.redone = !1;
+mxUndoableEdit.prototype.isEmpty = function() {
+  return 0 == this.changes.length
+};
+mxUndoableEdit.prototype.isSignificant = function() {
+  return this.significant
+};
+mxUndoableEdit.prototype.add = function(a) {
+  this.changes.push(a)
+};
+mxUndoableEdit.prototype.notify = function() {};
+mxUndoableEdit.prototype.die = function() {};
+mxUndoableEdit.prototype.undo = function() {
+  if (!this.undone) {
+    this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+    for (var a = this.changes.length - 1; 0 <= a; a--) {
+      var b = this.changes[a];
+      null != b.execute ? b.execute() : null != b.undo && b.undo();
+      this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, "change", b))
+    }
+    this.undone = !0;
+    this.redone = !1;
+    this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT))
+  }
+  this.notify()
+};
+mxUndoableEdit.prototype.redo = function() {
+  if (!this.redone) {
+    this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+    for (var a = this.changes.length, b = 0; b < a; b++) {
+      var c = this.changes[b];
+      null != c.execute ? c.execute() : null != c.redo && c.redo();
+      this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, "change", c))
+    }
+    this.undone = !1;
+    this.redone = !0;
+    this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT))
+  }
+  this.notify()
+};
+
+function mxUndoManager(a) {
+  this.size = null != a ? a : 100;
+  this.clear()
+}
+mxUndoManager.prototype = new mxEventSource;
+mxUndoManager.prototype.constructor = mxUndoManager;
+mxUndoManager.prototype.size = null;
+mxUndoManager.prototype.history = null;
+mxUndoManager.prototype.indexOfNextAdd = 0;
+mxUndoManager.prototype.isEmpty = function() {
+  return 0 == this.history.length
+};
+mxUndoManager.prototype.clear = function() {
+  this.history = [];
+  this.indexOfNextAdd = 0;
+  this.fireEvent(new mxEventObject(mxEvent.CLEAR))
+};
+mxUndoManager.prototype.canUndo = function() {
+  return 0 < this.indexOfNextAdd
+};
+mxUndoManager.prototype.undo = function() {
+  for (; 0 < this.indexOfNextAdd;) {
+    var a = this.history[--this.indexOfNextAdd];
+    a.undo();
+    if (a.isSignificant()) {
+      this.fireEvent(new mxEventObject(mxEvent.UNDO, "edit", a));
+      break
+    }
+  }
+};
+mxUndoManager.prototype.canRedo = function() {
+  return this.indexOfNextAdd < this.history.length
+};
+mxUndoManager.prototype.redo = function() {
+  for (var a = this.history.length; this.indexOfNextAdd < a;) {
+    var b = this.history[this.indexOfNextAdd++];
+    b.redo();
+    if (b.isSignificant()) {
+      this.fireEvent(new mxEventObject(mxEvent.REDO, "edit", b));
+      break
+    }
+  }
+};
+mxUndoManager.prototype.undoableEditHappened = function(a) {
+  this.trim();
+  0 < this.size && this.size == this.history.length && this.history.shift();
+  this.history.push(a);
+  this.indexOfNextAdd = this.history.length;
+  this.fireEvent(new mxEventObject(mxEvent.ADD, "edit", a))
+};
+mxUndoManager.prototype.trim = function() {
+  if (this.history.length > this.indexOfNextAdd)
+    for (var a = this.history.splice(this.indexOfNextAdd, this.history.length - this.indexOfNextAdd), b = 0; b < a.length; b++) a[b].die()
+};
+var mxUrlConverter = function(a) {
+  var b = !0,
+    c = null,
+    d = null;
+  return {
+    isEnabled: function() {
+      return b
+    },
+    setEnabled: function(a) {
+      b = a
+    },
+    getBaseUrl: function() {
+      return c
+    },
+    setBaseUrl: function(a) {
+      c = a
+    },
+    getBaseDomain: function() {
+      return c
+    },
+    setBaseDomain: function(a) {
+      c = a
+    },
+    isRelativeUrl: function(a) {
+      return "http://" != a.substring(0, 7) && "https://" != a.substring(0, 8) && "data:image" != a.substring(0, 10)
+    },
+    convert: function(a) {
+      if (b && this.isRelativeUrl(a)) {
+        if (null == c) {
+          d = location.protocol + "//" + location.host;
+          c = d + location.pathname;
+          var f = c.lastIndexOf("/");
+          0 < f && (c = c.substring(0, f + 1))
+        }
+        a = "/" == a.charAt(0) ? d + a : c + a
+      }
+      return a
+    }
+  }
+};
+
+function mxPanningManager(a) {
+  this.thread = null;
+  this.active = !1;
+  this.dy = this.dx = this.t0y = this.t0x = this.tdy = this.tdx = 0;
+  this.scrollbars = !1;
+  this.scrollTop = this.scrollLeft = 0;
+  this.mouseListener = {
+    mouseDown: function(a, b) {},
+    mouseMove: function(a, b) {},
+    mouseUp: mxUtils.bind(this, function(a, b) {
+      this.active && this.stop()
+    })
+  };
+  a.addMouseListener(this.mouseListener);
+  mxEvent.addListener(document, "mouseup", mxUtils.bind(this, function() {
+    this.active && this.stop()
+  }));
+  var b = mxUtils.bind(this, function() {
+    this.scrollbars = mxUtils.hasScrollbars(a.container);
+    this.scrollLeft = a.container.scrollLeft;
+    this.scrollTop = a.container.scrollTop;
+    return window.setInterval(mxUtils.bind(this, function() {
+      this.tdx -= this.dx;
+      this.tdy -= this.dy;
+      if (this.scrollbars) {
+        var b = -a.container.scrollLeft - Math.ceil(this.dx),
+          d = -a.container.scrollTop - Math.ceil(this.dy);
+        a.panGraph(b, d);
+        a.panDx = this.scrollLeft - a.container.scrollLeft;
+        a.panDy = this.scrollTop - a.container.scrollTop;
+        a.fireEvent(new mxEventObject(mxEvent.PAN))
+      } else a.panGraph(this.getDx(), this.getDy())
+    }), this.delay)
+  });
+  this.isActive =
+    function() {
+      return active
+    };
+  this.getDx = function() {
+    return Math.round(this.tdx)
+  };
+  this.getDy = function() {
+    return Math.round(this.tdy)
+  };
+  this.start = function() {
+    this.t0x = a.view.translate.x;
+    this.t0y = a.view.translate.y;
+    this.active = !0
+  };
+  this.panTo = function(c, d, e, f) {
+    this.active || this.start();
+    this.scrollLeft = a.container.scrollLeft;
+    this.scrollTop = a.container.scrollTop;
+    f = null != f ? f : 0;
+    var g = a.container;
+    this.dx = c + (null != e ? e : 0) - g.scrollLeft - g.clientWidth;
+    0 > this.dx && Math.abs(this.dx) < this.border ? this.dx = this.border + this.dx :
+      this.dx = this.handleMouseOut ? Math.max(this.dx, 0) : 0;
+    0 == this.dx && (this.dx = c - g.scrollLeft, this.dx = 0 < this.dx && this.dx < this.border ? this.dx - this.border : this.handleMouseOut ? Math.min(0, this.dx) : 0);
+    this.dy = d + f - g.scrollTop - g.clientHeight;
+    0 > this.dy && Math.abs(this.dy) < this.border ? this.dy = this.border + this.dy : this.dy = this.handleMouseOut ? Math.max(this.dy, 0) : 0;
+    0 == this.dy && (this.dy = d - g.scrollTop, this.dy = 0 < this.dy && this.dy < this.border ? this.dy - this.border : this.handleMouseOut ? Math.min(0, this.dy) : 0);
+    0 != this.dx || 0 != this.dy ?
+      (this.dx *= this.damper, this.dy *= this.damper, null == this.thread && (this.thread = b())) : null != this.thread && (window.clearInterval(this.thread), this.thread = null)
+  };
+  this.stop = function() {
+    if (this.active)
+      if (this.active = !1, null != this.thread && (window.clearInterval(this.thread), this.thread = null), this.tdy = this.tdx = 0, this.scrollbars) a.panDx = 0, a.panDy = 0, a.fireEvent(new mxEventObject(
+        mxEvent.PAN));
+      else {
+        var b = a.panDx,
+          d = a.panDy;
+        if (0 != b || 0 != d) a.panGraph(0, 0), a.view.setTranslate(this.t0x + b / a.view.scale, this.t0y + d / a.view.scale)
+      }
+  };
+  this.destroy = function() {
+    a.removeMouseListener(this.mouseListener)
+  }
+}
+mxPanningManager.prototype.damper = 1 / 6;
+mxPanningManager.prototype.delay = 10;
+mxPanningManager.prototype.handleMouseOut = !0;
+mxPanningManager.prototype.border = 0;
+
+function mxPopupMenu(a) {
+  this.factoryMethod = a;
+  null != a && this.init()
+}
+mxPopupMenu.prototype = new mxEventSource;
+mxPopupMenu.prototype.constructor = mxPopupMenu;
+mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + "/submenu.gif";
+mxPopupMenu.prototype.zIndex = 10006;
+mxPopupMenu.prototype.factoryMethod = null;
+mxPopupMenu.prototype.useLeftButtonForPopup = !1;
+mxPopupMenu.prototype.enabled = !0;
+mxPopupMenu.prototype.itemCount = 0;
+mxPopupMenu.prototype.autoExpand = !1;
+mxPopupMenu.prototype.smartSeparators = !1;
+mxPopupMenu.prototype.labels = !0;
+mxPopupMenu.prototype.init = function() {
+  this.table = document.createElement("table");
+  this.table.className = "mxPopupMenu";
+  this.tbody = document.createElement("tbody");
+  this.table.appendChild(this.tbody);
+  this.div = document.createElement("div");
+  this.div.className = "mxPopupMenu";
+  this.div.style.display = "inline";
+  this.div.style.zIndex = this.zIndex;
+  this.div.appendChild(this.table);
+  mxEvent.disableContextMenu(this.div)
+};
+mxPopupMenu.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxPopupMenu.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxPopupMenu.prototype.isPopupTrigger = function(a) {
+  return a.isPopupTrigger() || this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(a.getEvent())
+};
+mxPopupMenu.prototype.addItem = function(a, b, c, d, e, f) {
+  d = d || this;
+  this.itemCount++;
+  d.willAddSeparator && (d.containsItems && this.addSeparator(d, !0), d.willAddSeparator = !1);
+  d.containsItems = !0;
+  var g = document.createElement("tr");
+  g.className = "mxPopupMenuItem";
+  var h = document.createElement("td");
+  h.className = "mxPopupMenuIcon";
+  null != b ? (e = document.createElement("img"), e.src = b, h.appendChild(e)) : null != e && (b = document.createElement("div"), b.className = e, h.appendChild(b));
+  g.appendChild(h);
+  this.labels && (h = document.createElement("td"),
+    h.className = "mxPopupMenuItem" + (null != f && !f ? " disabled" : ""), mxUtils.write(h, a), h.align = "left", g.appendChild(h), a = document.createElement("td"), a.className =
+    "mxPopupMenuItem" + (null != f && !f ? " disabled" : ""), a.style.paddingRight = "6px", a.style.textAlign = "right", g.appendChild(a), null == d.div && this.createSubmenu(d));
+  d.tbody.appendChild(g);
+  if (null == f || f) mxEvent.addGestureListeners(g, mxUtils.bind(this, function(a) {
+    this.eventReceiver = g;
+    d.activeRow != g && d.activeRow != d && (null != d.activeRow && null != d.activeRow.div.parentNode &&
+      this.hideSubmenu(d), null != g.div && (this.showSubmenu(d, g), d.activeRow = g));
+    mxEvent.consume(a)
+  }), mxUtils.bind(this, function(a) {
+    d.activeRow != g && d.activeRow != d && (null != d.activeRow && null != d.activeRow.div.parentNode && this.hideSubmenu(d), this.autoExpand && null != g.div && (this.showSubmenu(d, g), d.activeRow =
+      g));
+    g.className = "mxPopupMenuItemHover"
+  }), mxUtils.bind(this, function(a) {
+    this.eventReceiver == g && (d.activeRow != g && this.hideMenu(), null != c && c(a));
+    this.eventReceiver = null;
+    mxEvent.consume(a)
+  })), mxEvent.addListener(g,
+    "mouseout", mxUtils.bind(this, function(a) {
+      g.className = "mxPopupMenuItem"
+    }));
+  return g
+};
+mxPopupMenu.prototype.createSubmenu = function(a) {
+  a.table = document.createElement("table");
+  a.table.className = "mxPopupMenu";
+  a.tbody = document.createElement("tbody");
+  a.table.appendChild(a.tbody);
+  a.div = document.createElement("div");
+  a.div.className = "mxPopupMenu";
+  a.div.style.position = "absolute";
+  a.div.style.display = "inline";
+  a.div.style.zIndex = this.zIndex;
+  a.div.appendChild(a.table);
+  var b = document.createElement("img");
+  b.setAttribute("src", this.submenuImage);
+  td = a.firstChild.nextSibling.nextSibling;
+  td.appendChild(b)
+};
+mxPopupMenu.prototype.showSubmenu = function(a, b) {
+  if (null != b.div) {
+    b.div.style.left = a.div.offsetLeft + b.offsetLeft + b.offsetWidth - 1 + "px";
+    b.div.style.top = a.div.offsetTop + b.offsetTop + "px";
+    document.body.appendChild(b.div);
+    var c = parseInt(b.div.offsetLeft),
+      d = parseInt(b.div.offsetWidth),
+      e = document.body,
+      f = document.documentElement;
+    if (c + d > (e.scrollLeft || f.scrollLeft) + (e.clientWidth || f.clientWidth)) b.div.style.left = a.div.offsetLeft - d + (mxClient.IS_IE ? 6 : -6) + "px";
+    mxUtils.fit(b.div)
+  }
+};
+mxPopupMenu.prototype.addSeparator = function(a, b) {
+  a = a || this;
+  if (this.smartSeparators && !b) a.willAddSeparator = !0;
+  else if (null != a.tbody) {
+    a.willAddSeparator = !1;
+    var c = document.createElement("tr"),
+      d = document.createElement("td");
+    d.className = "mxPopupMenuIcon";
+    d.style.padding = "0 0 0 0px";
+    c.appendChild(d);
+    d = document.createElement("td");
+    d.style.padding = "0 0 0 0px";
+    d.setAttribute("colSpan", "2");
+    var e = document.createElement("hr");
+    e.setAttribute("size", "1");
+    d.appendChild(e);
+    c.appendChild(d);
+    a.tbody.appendChild(c)
+  }
+};
+mxPopupMenu.prototype.popup = function(a, b, c, d) {
+  if (null != this.div && null != this.tbody && null != this.factoryMethod) {
+    this.div.style.left = a + "px";
+    for (this.div.style.top = b + "px"; null != this.tbody.firstChild;) mxEvent.release(this.tbody.firstChild), this.tbody.removeChild(this.tbody.firstChild);
+    this.itemCount = 0;
+    this.factoryMethod(this, c, d);
+    0 < this.itemCount && (this.showMenu(), this.fireEvent(new mxEventObject(mxEvent.SHOW)))
+  }
+};
+mxPopupMenu.prototype.isMenuShowing = function() {
+  return null != this.div && this.div.parentNode == document.body
+};
+mxPopupMenu.prototype.showMenu = function() {
+  9 <= document.documentMode && (this.div.style.filter = "none");
+  document.body.appendChild(this.div);
+  mxUtils.fit(this.div)
+};
+mxPopupMenu.prototype.hideMenu = function() {
+  null != this.div && (null != this.div.parentNode && this.div.parentNode.removeChild(this.div), this.hideSubmenu(this), this.containsItems = !1)
+};
+mxPopupMenu.prototype.hideSubmenu = function(a) {
+  null != a.activeRow && (this.hideSubmenu(a.activeRow), null != a.activeRow.div.parentNode && a.activeRow.div.parentNode.removeChild(a.activeRow.div), a.activeRow = null)
+};
+mxPopupMenu.prototype.destroy = function() {
+  null != this.div && (mxEvent.release(this.div), null != this.div.parentNode && this.div.parentNode.removeChild(this.div), this.div = null)
+};
+
+function mxAutoSaveManager(a) {
+  this.changeHandler = mxUtils.bind(this, function(a, c) {
+    this.isEnabled() && this.graphModelChanged(c.getProperty("edit").changes)
+  });
+  this.setGraph(a)
+}
+mxAutoSaveManager.prototype = new mxEventSource;
+mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
+mxAutoSaveManager.prototype.graph = null;
+mxAutoSaveManager.prototype.autoSaveDelay = 10;
+mxAutoSaveManager.prototype.autoSaveThrottle = 2;
+mxAutoSaveManager.prototype.autoSaveThreshold = 5;
+mxAutoSaveManager.prototype.ignoredChanges = 0;
+mxAutoSaveManager.prototype.lastSnapshot = 0;
+mxAutoSaveManager.prototype.enabled = !0;
+mxAutoSaveManager.prototype.changeHandler = null;
+mxAutoSaveManager.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxAutoSaveManager.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxAutoSaveManager.prototype.setGraph = function(a) {
+  null != this.graph && this.graph.getModel().removeListener(this.changeHandler);
+  this.graph = a;
+  null != this.graph && this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler)
+};
+mxAutoSaveManager.prototype.save = function() {};
+mxAutoSaveManager.prototype.graphModelChanged = function(a) {
+  a = ((new Date).getTime() - this.lastSnapshot) / 1E3;
+  a > this.autoSaveDelay || this.ignoredChanges >= this.autoSaveThreshold && a > this.autoSaveThrottle ? (this.save(), this.reset()) : this.ignoredChanges++
+};
+mxAutoSaveManager.prototype.reset = function() {
+  this.lastSnapshot = (new Date).getTime();
+  this.ignoredChanges = 0
+};
+mxAutoSaveManager.prototype.destroy = function() {
+  this.setGraph(null)
+};
+
+function mxAnimation(a) {
+  this.delay = null != a ? a : 20
+}
+mxAnimation.prototype = new mxEventSource;
+mxAnimation.prototype.constructor = mxAnimation;
+mxAnimation.prototype.delay = null;
+mxAnimation.prototype.thread = null;
+mxAnimation.prototype.startAnimation = function() {
+  null == this.thread && (this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay))
+};
+mxAnimation.prototype.updateAnimation = function() {
+  this.fireEvent(new mxEventObject(mxEvent.EXECUTE))
+};
+mxAnimation.prototype.stopAnimation = function() {
+  null != this.thread && (window.clearInterval(this.thread), this.thread = null, this.fireEvent(new mxEventObject(mxEvent.DONE)))
+};
+
+function mxMorphing(a, b, c, d) {
+  mxAnimation.call(this, d);
+  this.graph = a;
+  this.steps = null != b ? b : 6;
+  this.ease = null != c ? c : 1.5
+}
+mxMorphing.prototype = new mxAnimation;
+mxMorphing.prototype.constructor = mxMorphing;
+mxMorphing.prototype.graph = null;
+mxMorphing.prototype.steps = null;
+mxMorphing.prototype.step = 0;
+mxMorphing.prototype.ease = null;
+mxMorphing.prototype.cells = null;
+mxMorphing.prototype.updateAnimation = function() {
+  var a = new mxCellStatePreview(this.graph);
+  if (null != this.cells)
+    for (var b = 0; b < this.cells.length; b++) this.animateCell(cells[b], a, !1);
+  else this.animateCell(this.graph.getModel().getRoot(), a, !0);
+  this.show(a);
+  (a.isEmpty() || this.step++ >= this.steps) && this.stopAnimation()
+};
+mxMorphing.prototype.show = function(a) {
+  a.show()
+};
+mxMorphing.prototype.animateCell = function(a, b, c) {
+  var d = this.graph.getView().getState(a),
+    e = null;
+  if (null != d && (e = this.getDelta(d), this.graph.getModel().isVertex(a) && (0 != e.x || 0 != e.y))) {
+    var f = this.graph.view.getTranslate(),
+      g = this.graph.view.getScale();
+    e.x += f.x * g;
+    e.y += f.y * g;
+    b.moveState(d, -e.x / this.ease, -e.y / this.ease)
+  }
+  if (c && !this.stopRecursion(d, e)) {
+    d = this.graph.getModel().getChildCount(a);
+    for (e = 0; e < d; e++) this.animateCell(this.graph.getModel().getChildAt(a, e), b, c)
+  }
+};
+mxMorphing.prototype.stopRecursion = function(a, b) {
+  return null != b && (0 != b.x || 0 != b.y)
+};
+mxMorphing.prototype.getDelta = function(a) {
+  var b = this.getOriginForCell(a.cell),
+    c = this.graph.getView().getTranslate(),
+    d = this.graph.getView().getScale();
+  a = new mxPoint(a.x / d - c.x, a.y / d - c.y);
+  return new mxPoint((b.x - a.x) * d, (b.y - a.y) * d)
+};
+mxMorphing.prototype.getOriginForCell = function(a) {
+  var b = null;
+  null != a && (b = this.getOriginForCell(this.graph.getModel().getParent(a)), a = this.graph.getCellGeometry(a), null != a && (b.x += a.x, b.y += a.y));
+  null == b && (b = this.graph.view.getTranslate(), b = new mxPoint(-b.x, -b.y));
+  return b
+};
+
+function mxImageBundle(a) {
+  this.images = [];
+  this.alt = null != a ? a : !1
+}
+mxImageBundle.prototype.images = null;
+mxImageBundle.prototype.images = null;
+mxImageBundle.prototype.putImage = function(a, b, c) {
+  this.images[a] = {
+    value: b,
+    fallback: c
+  }
+};
+mxImageBundle.prototype.getImage = function(a) {
+  var b = null;
+  null != a && (a = this.images[a], null != a && (b = this.alt ? a.fallback : a.value));
+  return b
+};
+
+function mxImageExport() {}
+mxImageExport.prototype.includeOverlays = !1;
+mxImageExport.prototype.drawState = function(a, b) {
+  null != a && (this.visitStatesRecursive(a, b, this.drawCellState), this.includeOverlays && this.visitStatesRecursive(a, b, this.drawOverlays))
+};
+mxImageExport.prototype.visitStatesRecursive = function(a, b, c) {
+  if (null != a) {
+    c(a, b);
+    for (var d = a.view.graph, e = d.model.getChildCount(a.cell), f = 0; f < e; f++) {
+      var g = d.view.getState(d.model.getChildAt(a.cell, f));
+      this.visitStatesRecursive(g, b, c)
+    }
+  }
+};
+mxImageExport.prototype.drawCellState = function(a, b) {
+  a.shape instanceof mxShape && (b.save(), a.shape.paint(b), b.restore());
+  null != a.text && (b.save(), a.text.paint(b), b.restore())
+};
+mxImageExport.prototype.drawOverlays = function(a, b) {
+  null != a.overlays && a.overlays.visit(function(a, d) {
+    d instanceof mxShape && d.paint(b)
+  })
+};
+
+function mxAbstractCanvas2D() {
+  this.converter = this.createUrlConverter();
+  this.reset()
+}
+mxAbstractCanvas2D.prototype.state = null;
+mxAbstractCanvas2D.prototype.states = null;
+mxAbstractCanvas2D.prototype.path = null;
+mxAbstractCanvas2D.prototype.rotateHtml = !0;
+mxAbstractCanvas2D.prototype.lastX = 0;
+mxAbstractCanvas2D.prototype.lastY = 0;
+mxAbstractCanvas2D.prototype.moveOp = "M";
+mxAbstractCanvas2D.prototype.lineOp = "L";
+mxAbstractCanvas2D.prototype.quadOp = "Q";
+mxAbstractCanvas2D.prototype.curveOp = "C";
+mxAbstractCanvas2D.prototype.closeOp = "Z";
+mxAbstractCanvas2D.prototype.pointerEvents = !1;
+mxAbstractCanvas2D.prototype.createUrlConverter = function() {
+  return new mxUrlConverter
+};
+mxAbstractCanvas2D.prototype.reset = function() {
+  this.state = this.createState();
+  this.states = []
+};
+mxAbstractCanvas2D.prototype.createState = function() {
+  return {
+    dx: 0,
+    dy: 0,
+    scale: 1,
+    alpha: 1,
+    fillColor: null,
+    fillAlpha: 1,
+    gradientColor: null,
+    gradientAlpha: 1,
+    gradientDirection: null,
+    strokeColor: null,
+    strokeWidth: 1,
+    dashed: !1,
+    dashPattern: "3 3",
+    lineCap: "flat",
+    lineJoin: "miter",
+    miterLimit: 10,
+    fontColor: "#000000",
+    fontBackgroundColor: null,
+    fontBorderColor: null,
+    fontSize: mxConstants.DEFAULT_FONTSIZE,
+    fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+    fontStyle: 0,
+    shadow: !1,
+    shadowColor: mxConstants.SHADOWCOLOR,
+    shadowAlpha: mxConstants.SHADOW_OPACITY,
+    shadowDx: mxConstants.SHADOW_OFFSET_X,
+    shadowDy: mxConstants.SHADOW_OFFSET_Y,
+    rotation: 0,
+    rotationCx: 0,
+    rotationCy: 0
+  }
+};
+mxAbstractCanvas2D.prototype.format = function(a) {
+  return Math.round(parseFloat(a))
+};
+mxAbstractCanvas2D.prototype.addOp = function() {
+  if (null != this.path && (this.path.push(arguments[0]), 2 < arguments.length))
+    for (var a = this.state, b = 2; b < arguments.length; b += 2) this.lastX = arguments[b - 1], this.lastY = arguments[b], this.path.push(this.format((this.lastX + a.dx) * a.scale)), this.path.push(
+      this.format((this.lastY + a.dy) * a.scale))
+};
+mxAbstractCanvas2D.prototype.rotatePoint = function(a, b, c, d, e) {
+  c *= Math.PI / 180;
+  return mxUtils.getRotatedPoint(new mxPoint(a, b), Math.cos(c), Math.sin(c), new mxPoint(d, e))
+};
+mxAbstractCanvas2D.prototype.save = function() {
+  this.states.push(this.state);
+  this.state = mxUtils.clone(this.state)
+};
+mxAbstractCanvas2D.prototype.restore = function() {
+  this.state = this.states.pop()
+};
+mxAbstractCanvas2D.prototype.scale = function(a) {
+  this.state.scale *= a;
+  this.state.strokeWidth *= a
+};
+mxAbstractCanvas2D.prototype.translate = function(a, b) {
+  this.state.dx += a;
+  this.state.dy += b
+};
+mxAbstractCanvas2D.prototype.setAlpha = function(a) {
+  this.state.alpha = a
+};
+mxAbstractCanvas2D.prototype.setFillColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  this.state.fillColor = a;
+  this.state.gradientColor = null
+};
+mxAbstractCanvas2D.prototype.setGradient = function(a, b, c, d, e, f, g, h, k) {
+  c = this.state;
+  c.fillColor = a;
+  c.fillAlpha = null != h ? h : 1;
+  c.gradientColor = b;
+  c.gradientAlpha = null != k ? k : 1;
+  c.gradientDirection = g
+};
+mxAbstractCanvas2D.prototype.setStrokeColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  this.state.strokeColor = a
+};
+mxAbstractCanvas2D.prototype.setStrokeWidth = function(a) {
+  this.state.strokeWidth = a
+};
+mxAbstractCanvas2D.prototype.setDashed = function(a) {
+  this.state.dashed = a
+};
+mxAbstractCanvas2D.prototype.setDashPattern = function(a) {
+  this.state.dashPattern = a
+};
+mxAbstractCanvas2D.prototype.setLineCap = function(a) {
+  this.state.lineCap = a
+};
+mxAbstractCanvas2D.prototype.setLineJoin = function(a) {
+  this.state.lineJoin = a
+};
+mxAbstractCanvas2D.prototype.setMiterLimit = function(a) {
+  this.state.miterLimit = a
+};
+mxAbstractCanvas2D.prototype.setFontColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  this.state.fontColor = a
+};
+mxAbstractCanvas2D.prototype.setFontBackgroundColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  this.state.fontBackgroundColor = a
+};
+mxAbstractCanvas2D.prototype.setFontBorderColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  this.state.fontBorderColor = a
+};
+mxAbstractCanvas2D.prototype.setFontSize = function(a) {
+  this.state.fontSize = a
+};
+mxAbstractCanvas2D.prototype.setFontFamily = function(a) {
+  this.state.fontFamily = a
+};
+mxAbstractCanvas2D.prototype.setFontStyle = function(a) {
+  null == a && (a = 0);
+  this.state.fontStyle = a
+};
+mxAbstractCanvas2D.prototype.setShadow = function(a) {
+  this.state.shadow = a
+};
+mxAbstractCanvas2D.prototype.setShadowColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  this.state.shadowColor = a
+};
+mxAbstractCanvas2D.prototype.setShadowAlpha = function(a) {
+  this.state.shadowAlpha = a
+};
+mxAbstractCanvas2D.prototype.setShadowOffset = function(a, b) {
+  this.state.shadowDx = a;
+  this.state.shadowDy = b
+};
+mxAbstractCanvas2D.prototype.begin = function() {
+  this.lastY = this.lastX = 0;
+  this.path = []
+};
+mxAbstractCanvas2D.prototype.moveTo = function(a, b) {
+  this.addOp(this.moveOp, a, b)
+};
+mxAbstractCanvas2D.prototype.lineTo = function(a, b) {
+  this.addOp(this.lineOp, a, b)
+};
+mxAbstractCanvas2D.prototype.quadTo = function(a, b, c, d) {
+  this.addOp(this.quadOp, a, b, c, d)
+};
+mxAbstractCanvas2D.prototype.curveTo = function(a, b, c, d, e, f) {
+  this.addOp(this.curveOp, a, b, c, d, e, f)
+};
+mxAbstractCanvas2D.prototype.arcTo = function(a, b, c, d, e, f, g) {
+  a = mxUtils.arcToCurves(this.lastX, this.lastY, a, b, c, d, e, f, g);
+  for (b = 0; b < a.length; b += 6) this.curveTo(a[b], a[b + 1], a[b + 2], a[b + 3], a[b + 4], a[b + 5])
+};
+mxAbstractCanvas2D.prototype.close = function(a, b, c, d, e, f) {
+  this.addOp(this.closeOp)
+};
+mxAbstractCanvas2D.prototype.end = function() {};
+
+function mxXmlCanvas2D(a) {
+  mxAbstractCanvas2D.call(this);
+  this.root = a;
+  this.writeDefaults()
+}
+mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);
+mxXmlCanvas2D.prototype.textEnabled = !0;
+mxXmlCanvas2D.prototype.compressed = !0;
+mxXmlCanvas2D.prototype.writeDefaults = function() {
+  var a;
+  a = this.createElement("fontfamily");
+  a.setAttribute("family", mxConstants.DEFAULT_FONTFAMILY);
+  this.root.appendChild(a);
+  a = this.createElement("fontsize");
+  a.setAttribute("size", mxConstants.DEFAULT_FONTSIZE);
+  this.root.appendChild(a);
+  a = this.createElement("shadowcolor");
+  a.setAttribute("color", mxConstants.SHADOWCOLOR);
+  this.root.appendChild(a);
+  a = this.createElement("shadowalpha");
+  a.setAttribute("alpha", mxConstants.SHADOW_OPACITY);
+  this.root.appendChild(a);
+  a = this.createElement("shadowoffset");
+  a.setAttribute("dx", mxConstants.SHADOW_OFFSET_X);
+  a.setAttribute("dy", mxConstants.SHADOW_OFFSET_Y);
+  this.root.appendChild(a)
+};
+mxXmlCanvas2D.prototype.format = function(a) {
+  return parseFloat(parseFloat(a).toFixed(2))
+};
+mxXmlCanvas2D.prototype.createElement = function(a) {
+  return this.root.ownerDocument.createElement(a)
+};
+mxXmlCanvas2D.prototype.save = function() {
+  this.compressed && mxAbstractCanvas2D.prototype.save.apply(this, arguments);
+  this.root.appendChild(this.createElement("save"))
+};
+mxXmlCanvas2D.prototype.restore = function() {
+  this.compressed && mxAbstractCanvas2D.prototype.restore.apply(this, arguments);
+  this.root.appendChild(this.createElement("restore"))
+};
+mxXmlCanvas2D.prototype.scale = function(a) {
+  if (this.compressed) {
+    if (this.state.scale == a) return;
+    mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments)
+  }
+  var b = this.createElement("scale");
+  b.setAttribute("scale", a);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.translate = function(a, b) {
+  var c = this.createElement("translate");
+  c.setAttribute("dx", this.format(a));
+  c.setAttribute("dy", this.format(b));
+  this.root.appendChild(c)
+};
+mxXmlCanvas2D.prototype.rotate = function(a, b, c, d, e) {
+  var f = this.createElement("rotate");
+  if (0 != a || b || c) f.setAttribute("theta", this.format(a)), f.setAttribute("flipH", b ? "1" : "0"), f.setAttribute("flipV", c ? "1" : "0"), f.setAttribute("cx", this.format(d)), f.setAttribute(
+    "cy", this.format(e)), this.root.appendChild(f)
+};
+mxXmlCanvas2D.prototype.setAlpha = function(a) {
+  if (this.compressed) {
+    if (this.state.alpha == a) return;
+    mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments)
+  }
+  var b = this.createElement("alpha");
+  b.setAttribute("alpha", this.format(a));
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setFillColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  if (this.compressed) {
+    if (this.state.fillColor == a) return;
+    mxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments)
+  }
+  var b = this.createElement("fillcolor");
+  b.setAttribute("color", null != a ? a : mxConstants.NONE);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setGradient = function(a, b, c, d, e, f, g, h, k) {
+  if (null != a && null != b) {
+    mxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);
+    var l = this.createElement("gradient");
+    l.setAttribute("c1", a);
+    l.setAttribute("c2", b);
+    l.setAttribute("x", this.format(c));
+    l.setAttribute("y", this.format(d));
+    l.setAttribute("w", this.format(e));
+    l.setAttribute("h", this.format(f));
+    null != g && l.setAttribute("direction", g);
+    null != h && l.setAttribute("alpha1", h);
+    null != k && l.setAttribute("alpha2", k);
+    this.root.appendChild(l)
+  }
+};
+mxXmlCanvas2D.prototype.setStrokeColor = function(a) {
+  a == mxConstants.NONE && (a = null);
+  if (this.compressed) {
+    if (this.state.strokeColor == a) return;
+    mxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments)
+  }
+  var b = this.createElement("strokecolor");
+  b.setAttribute("color", null != a ? a : mxConstants.NONE);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setStrokeWidth = function(a) {
+  if (this.compressed) {
+    if (this.state.strokeWidth == a) return;
+    mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments)
+  }
+  var b = this.createElement("strokewidth");
+  b.setAttribute("width", this.format(a));
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setDashed = function(a) {
+  if (this.compressed) {
+    if (this.state.dashed == a) return;
+    mxAbstractCanvas2D.prototype.setDashed.apply(this, arguments)
+  }
+  var b = this.createElement("dashed");
+  b.setAttribute("dashed", a ? "1" : "0");
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setDashPattern = function(a) {
+  if (this.compressed) {
+    if (this.state.dashPattern == a) return;
+    mxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments)
+  }
+  var b = this.createElement("dashpattern");
+  b.setAttribute("pattern", a);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setLineCap = function(a) {
+  if (this.compressed) {
+    if (this.state.lineCap == a) return;
+    mxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments)
+  }
+  var b = this.createElement("linecap");
+  b.setAttribute("cap", a);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setLineJoin = function(a) {
+  if (this.compressed) {
+    if (this.state.lineJoin == a) return;
+    mxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments)
+  }
+  var b = this.createElement("linejoin");
+  b.setAttribute("join", a);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setMiterLimit = function(a) {
+  if (this.compressed) {
+    if (this.state.miterLimit == a) return;
+    mxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments)
+  }
+  var b = this.createElement("miterlimit");
+  b.setAttribute("limit", a);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setFontColor = function(a) {
+  if (this.textEnabled) {
+    a == mxConstants.NONE && (a = null);
+    if (this.compressed) {
+      if (this.state.fontColor == a) return;
+      mxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments)
+    }
+    var b = this.createElement("fontcolor");
+    b.setAttribute("color", null != a ? a : mxConstants.NONE);
+    this.root.appendChild(b)
+  }
+};
+mxXmlCanvas2D.prototype.setFontBackgroundColor = function(a) {
+  if (this.textEnabled) {
+    a == mxConstants.NONE && (a = null);
+    if (this.compressed) {
+      if (this.state.fontBackgroundColor == a) return;
+      mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments)
+    }
+    var b = this.createElement("fontbackgroundcolor");
+    b.setAttribute("color", null != a ? a : mxConstants.NONE);
+    this.root.appendChild(b)
+  }
+};
+mxXmlCanvas2D.prototype.setFontBorderColor = function(a) {
+  if (this.textEnabled) {
+    a == mxConstants.NONE && (a = null);
+    if (this.compressed) {
+      if (this.state.fontBorderColor == a) return;
+      mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments)
+    }
+    var b = this.createElement("fontbordercolor");
+    b.setAttribute("color", null != a ? a : mxConstants.NONE);
+    this.root.appendChild(b)
+  }
+};
+mxXmlCanvas2D.prototype.setFontSize = function(a) {
+  if (this.textEnabled) {
+    if (this.compressed) {
+      if (this.state.fontSize == a) return;
+      mxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments)
+    }
+    var b = this.createElement("fontsize");
+    b.setAttribute("size", a);
+    this.root.appendChild(b)
+  }
+};
+mxXmlCanvas2D.prototype.setFontFamily = function(a) {
+  if (this.textEnabled) {
+    if (this.compressed) {
+      if (this.state.fontFamily == a) return;
+      mxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments)
+    }
+    var b = this.createElement("fontfamily");
+    b.setAttribute("family", a);
+    this.root.appendChild(b)
+  }
+};
+mxXmlCanvas2D.prototype.setFontStyle = function(a) {
+  if (this.textEnabled) {
+    null == a && (a = 0);
+    if (this.compressed) {
+      if (this.state.fontStyle == a) return;
+      mxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments)
+    }
+    var b = this.createElement("fontstyle");
+    b.setAttribute("style", a);
+    this.root.appendChild(b)
+  }
+};
+mxXmlCanvas2D.prototype.setShadow = function(a) {
+  if (this.compressed) {
+    if (this.state.shadow == a) return;
+    mxAbstractCanvas2D.prototype.setShadow.apply(this, arguments)
+  }
+  var b = this.createElement("shadow");
+  b.setAttribute("enabled", a ? "1" : "0");
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setShadowColor = function(a) {
+  if (this.compressed) {
+    a == mxConstants.NONE && (a = null);
+    if (this.state.shadowColor == a) return;
+    mxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments)
+  }
+  var b = this.createElement("shadowcolor");
+  b.setAttribute("color", null != a ? a : mxConstants.NONE);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setShadowAlpha = function(a) {
+  if (this.compressed) {
+    if (this.state.shadowAlpha == a) return;
+    mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments)
+  }
+  var b = this.createElement("shadowalpha");
+  b.setAttribute("alpha", a);
+  this.root.appendChild(b)
+};
+mxXmlCanvas2D.prototype.setShadowOffset = function(a, b) {
+  if (this.compressed) {
+    if (this.state.shadowDx == a && this.state.shadowDy == b) return;
+    mxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments)
+  }
+  var c = this.createElement("shadowoffset");
+  c.setAttribute("dx", a);
+  c.setAttribute("dy", b);
+  this.root.appendChild(c)
+};
+mxXmlCanvas2D.prototype.rect = function(a, b, c, d) {
+  var e = this.createElement("rect");
+  e.setAttribute("x", this.format(a));
+  e.setAttribute("y", this.format(b));
+  e.setAttribute("w", this.format(c));
+  e.setAttribute("h", this.format(d));
+  this.root.appendChild(e)
+};
+mxXmlCanvas2D.prototype.roundrect = function(a, b, c, d, e, f) {
+  var g = this.createElement("roundrect");
+  g.setAttribute("x", this.format(a));
+  g.setAttribute("y", this.format(b));
+  g.setAttribute("w", this.format(c));
+  g.setAttribute("h", this.format(d));
+  g.setAttribute("dx", this.format(e));
+  g.setAttribute("dy", this.format(f));
+  this.root.appendChild(g)
+};
+mxXmlCanvas2D.prototype.ellipse = function(a, b, c, d) {
+  var e = this.createElement("ellipse");
+  e.setAttribute("x", this.format(a));
+  e.setAttribute("y", this.format(b));
+  e.setAttribute("w", this.format(c));
+  e.setAttribute("h", this.format(d));
+  this.root.appendChild(e)
+};
+mxXmlCanvas2D.prototype.image = function(a, b, c, d, e, f, g, h) {
+  e = this.converter.convert(e);
+  var k = this.createElement("image");
+  k.setAttribute("x", this.format(a));
+  k.setAttribute("y", this.format(b));
+  k.setAttribute("w", this.format(c));
+  k.setAttribute("h", this.format(d));
+  k.setAttribute("src", e);
+  k.setAttribute("aspect", f ? "1" : "0");
+  k.setAttribute("flipH", g ? "1" : "0");
+  k.setAttribute("flipV", h ? "1" : "0");
+  this.root.appendChild(k)
+};
+mxXmlCanvas2D.prototype.begin = function() {
+  this.root.appendChild(this.createElement("begin"));
+  this.lastY = this.lastX = 0
+};
+mxXmlCanvas2D.prototype.moveTo = function(a, b) {
+  var c = this.createElement("move");
+  c.setAttribute("x", this.format(a));
+  c.setAttribute("y", this.format(b));
+  this.root.appendChild(c);
+  this.lastX = a;
+  this.lastY = b
+};
+mxXmlCanvas2D.prototype.lineTo = function(a, b) {
+  var c = this.createElement("line");
+  c.setAttribute("x", this.format(a));
+  c.setAttribute("y", this.format(b));
+  this.root.appendChild(c);
+  this.lastX = a;
+  this.lastY = b
+};
+mxXmlCanvas2D.prototype.quadTo = function(a, b, c, d) {
+  var e = this.createElement("quad");
+  e.setAttribute("x1", this.format(a));
+  e.setAttribute("y1", this.format(b));
+  e.setAttribute("x2", this.format(c));
+  e.setAttribute("y2", this.format(d));
+  this.root.appendChild(e);
+  this.lastX = c;
+  this.lastY = d
+};
+mxXmlCanvas2D.prototype.curveTo = function(a, b, c, d, e, f) {
+  var g = this.createElement("curve");
+  g.setAttribute("x1", this.format(a));
+  g.setAttribute("y1", this.format(b));
+  g.setAttribute("x2", this.format(c));
+  g.setAttribute("y2", this.format(d));
+  g.setAttribute("x3", this.format(e));
+  g.setAttribute("y3", this.format(f));
+  this.root.appendChild(g);
+  this.lastX = e;
+  this.lastY = f
+};
+mxXmlCanvas2D.prototype.close = function() {
+  this.root.appendChild(this.createElement("close"))
+};
+mxXmlCanvas2D.prototype.text = function(a, b, c, d, e, f, g, h, k, l, m, n) {
+  if (this.textEnabled && null != e) {
+    mxUtils.isNode(e) && (e = mxUtils.getOuterHtml(e));
+    var p = this.createElement("text");
+    p.setAttribute("x", this.format(a));
+    p.setAttribute("y", this.format(b));
+    p.setAttribute("w", this.format(c));
+    p.setAttribute("h", this.format(d));
+    p.setAttribute("str", e);
+    null != f && p.setAttribute("align", f);
+    null != g && p.setAttribute("valign", g);
+    p.setAttribute("wrap", h ? "1" : "0");
+    null == k && (k = "");
+    p.setAttribute("format", k);
+    null != l && p.setAttribute("fill",
+      l ? "1" : "0");
+    null != m && p.setAttribute("clip", m ? "1" : "0");
+    null != n && p.setAttribute("rotation", n);
+    this.root.appendChild(p)
+  }
+};
+mxXmlCanvas2D.prototype.stroke = function() {
+  this.root.appendChild(this.createElement("stroke"))
+};
+mxXmlCanvas2D.prototype.fill = function() {
+  this.root.appendChild(this.createElement("fill"))
+};
+mxXmlCanvas2D.prototype.fillAndStroke = function() {
+  this.root.appendChild(this.createElement("fillstroke"))
+};
+
+function mxSvgCanvas2D(a, b) {
+  mxAbstractCanvas2D.call(this);
+  this.root = a;
+  this.gradients = [];
+  this.defs = null;
+  this.styleEnabled = null != b ? b : !1;
+  var c = null;
+  if (a.ownerDocument != document)
+    for (c = a; null != c && "svg" != c.nodeName;) c = c.parentNode;
+  null != c && (0 < c.getElementsByTagName("defs").length && (this.defs = c.getElementsByTagName("defs")[0]), null == this.defs && (this.defs = this.createElement("defs"), null != c.firstChild ? c.insertBefore(
+    this.defs, c.firstChild) : c.appendChild(this.defs)), this.styleEnabled && this.defs.appendChild(this.createStyle()))
+}
+mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);
+mxSvgCanvas2D.prototype.node = null;
+mxSvgCanvas2D.prototype.matchHtmlAlignment = !0;
+mxSvgCanvas2D.prototype.textEnabled = !0;
+mxSvgCanvas2D.prototype.foEnabled = !0;
+mxSvgCanvas2D.prototype.strokeTolerance = 0;
+mxSvgCanvas2D.prototype.refCount = 0;
+mxSvgCanvas2D.prototype.blockImagePointerEvents = !1;
+mxSvgCanvas2D.prototype.reset = function() {
+  mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
+  this.gradients = []
+};
+mxSvgCanvas2D.prototype.createStyle = function(a) {
+  a = this.createElement("style");
+  a.setAttribute("type", "text/css");
+  mxUtils.write(a, "svg{font-family:" + mxConstants.DEFAULT_FONTFAMILY + ";font-size:" + mxConstants.DEFAULT_FONTSIZE + ";fill:none;stroke-miterlimit:10}");
+  return a
+};
+mxSvgCanvas2D.prototype.createElement = function(a, b) {
+  if (null != this.root.ownerDocument.createElementNS) return this.root.ownerDocument.createElementNS(b || mxConstants.NS_SVG, a);
+  var c = this.root.ownerDocument.createElement(a);
+  null != b && c.setAttribute("xmlns", b);
+  return c
+};
+mxSvgCanvas2D.prototype.createGradientId = function(a, b, c, d, e) {
+  "#" == a.charAt(0) && (a = a.substring(1));
+  "#" == b.charAt(0) && (b = b.substring(1));
+  a = a.toLowerCase() + "-" + c;
+  b = b.toLowerCase() + "-" + d;
+  c = null;
+  null == e || e == mxConstants.DIRECTION_SOUTH ? c = "s" : e == mxConstants.DIRECTION_EAST ? c = "e" : (d = a, a = b, b = d, e == mxConstants.DIRECTION_NORTH ? c = "s" : e == mxConstants.DIRECTION_WEST &&
+    (c = "e"));
+  return "mx-gradient-" + a + "-" + b + "-" + c
+};
+mxSvgCanvas2D.prototype.getSvgGradient = function(a, b, c, d, e) {
+  var f = this.createGradientId(a, b, c, d, e),
+    g = this.gradients[f];
+  if (null == g) {
+    var h = this.root.ownerSVGElement,
+      k = 0,
+      l = f + "-" + k;
+    if (null != h)
+      for (g = h.ownerDocument.getElementById(l); null != g && g.ownerSVGElement != h;) l = f + "-" + k++, g = h.ownerDocument.getElementById(l);
+    else l = "id" + ++this.refCount;
+    null == g && (g = this.createSvgGradient(a, b, c, d, e), g.setAttribute("id", l), null != this.defs ? this.defs.appendChild(g) : h.appendChild(g));
+    this.gradients[f] = g
+  }
+  return g.getAttribute("id")
+};
+mxSvgCanvas2D.prototype.createSvgGradient = function(a, b, c, d, e) {
+  var f = this.createElement("linearGradient");
+  f.setAttribute("x1", "0%");
+  f.setAttribute("y1", "0%");
+  f.setAttribute("x2", "0%");
+  f.setAttribute("y2", "0%");
+  null == e || e == mxConstants.DIRECTION_SOUTH ? f.setAttribute("y2", "100%") : e == mxConstants.DIRECTION_EAST ? f.setAttribute("x2", "100%") : e == mxConstants.DIRECTION_NORTH ? f.setAttribute(
+    "y1", "100%") : e == mxConstants.DIRECTION_WEST && f.setAttribute("x1", "100%");
+  c = 1 > c ? ";stop-opacity:" + c : "";
+  e = this.createElement("stop");
+  e.setAttribute("offset", "0%");
+  e.setAttribute("style", "stop-color:" + a + c);
+  f.appendChild(e);
+  c = 1 > d ? ";stop-opacity:" + d : "";
+  e = this.createElement("stop");
+  e.setAttribute("offset", "100%");
+  e.setAttribute("style", "stop-color:" + b + c);
+  f.appendChild(e);
+  return f
+};
+mxSvgCanvas2D.prototype.addNode = function(a, b) {
+  var c = this.node,
+    d = this.state;
+  if (null != c) {
+    if ("path" == c.nodeName)
+      if (null != this.path && 0 < this.path.length) c.setAttribute("d", this.path.join(" "));
+      else return;
+    a && null != d.fillColor ? this.updateFill() : this.styleEnabled || ("ellipse" == c.nodeName && mxClient.IS_NS && !mxClient.IS_GC && !mxClient.IS_SF ? c.setAttribute("fill", "transparent") : c.setAttribute(
+      "fill", "none"), a = !1);
+    b && null != d.strokeColor ? this.updateStroke() : this.styleEnabled || c.setAttribute("stroke", "none");
+    null !=
+      d.transform && 0 < d.transform.length && c.setAttribute("transform", d.transform);
+    d.shadow && this.root.appendChild(this.createShadow(c));
+    0 < this.strokeTolerance && !a && this.root.appendChild(this.createTolerance(c));
+    this.pointerEvents && ("path" != c.nodeName || this.path[this.path.length - 1] == this.closeOp) && c.setAttribute("pointer-events", "all");
+    this.root.appendChild(c)
+  }
+};
+mxSvgCanvas2D.prototype.updateFill = function() {
+  var a = this.state;
+  1 > a.alpha && this.node.setAttribute("fill-opacity", a.alpha);
+  null != a.fillColor && (null != a.gradientColor ? (a = this.getSvgGradient(a.fillColor, a.gradientColor, a.fillAlpha, a.gradientAlpha, a.gradientDirection), this.node.setAttribute("fill", "url(#" +
+    a + ")")) : this.node.setAttribute("fill", a.fillColor.toLowerCase()))
+};
+mxSvgCanvas2D.prototype.updateStroke = function() {
+  var a = this.state;
+  this.node.setAttribute("stroke", a.strokeColor.toLowerCase());
+  1 > a.alpha && this.node.setAttribute("stroke-opacity", a.alpha);
+  var b = Math.max(1, this.format(a.strokeWidth * a.scale));
+  1 != b && this.node.setAttribute("stroke-width", b);
+  "path" == this.node.nodeName && this.updateStrokeAttributes();
+  a.dashed && this.node.setAttribute("stroke-dasharray", this.createDashPattern(b))
+};
+mxSvgCanvas2D.prototype.updateStrokeAttributes = function() {
+  var a = this.state;
+  null != a.lineJoin && "miter" != a.lineJoin && this.node.setAttribute("stroke-linejoin", a.lineJoin);
+  if (null != a.lineCap) {
+    var b = a.lineCap;
+    "flat" == b && (b = "butt");
+    "butt" != b && this.node.setAttribute("stroke-linecap", b)
+  }
+  null != a.miterLimit && (!this.styleEnabled || 10 != a.miterLimit) && this.node.setAttribute("stroke-miterlimit", a.miterLimit)
+};
+mxSvgCanvas2D.prototype.createDashPattern = function(a) {
+  var b = this.state.dashPattern.split(" "),
+    c = [];
+  if (0 < b.length)
+    for (var d = 0; d < b.length; d++) c[d] = Number(b[d]) * a;
+  return c.join(" ")
+};
+mxSvgCanvas2D.prototype.createTolerance = function(a) {
+  a = a.cloneNode(!0);
+  var b = parseFloat(a.getAttribute("stroke-width") || 1) + this.strokeTolerance;
+  a.setAttribute("pointer-events", "stroke");
+  a.setAttribute("visibility", "hidden");
+  a.removeAttribute("stroke-dasharray");
+  a.setAttribute("stroke-width", b);
+  a.setAttribute("fill", "none");
+  a.setAttribute("stroke", mxClient.IS_OP ? "none" : "white");
+  return a
+};
+mxSvgCanvas2D.prototype.createShadow = function(a) {
+  a = a.cloneNode(!0);
+  var b = this.state;
+  "none" != a.getAttribute("fill") && a.setAttribute("fill", b.shadowColor);
+  "none" != a.getAttribute("stroke") && a.setAttribute("stroke", b.shadowColor);
+  a.setAttribute("transform", "translate(" + this.format(b.shadowDx * b.scale) + "," + this.format(b.shadowDy * b.scale) + ")" + (b.transform || ""));
+  a.setAttribute("opacity", b.shadowAlpha);
+  return a
+};
+mxSvgCanvas2D.prototype.rotate = function(a, b, c, d, e) {
+  if (0 != a || b || c) {
+    var f = this.state;
+    d += f.dx;
+    e += f.dy;
+    d *= f.scale;
+    e *= f.scale;
+    f.transform = f.transform || "";
+    if (b && c) a += 180;
+    else if (b ^ c) {
+      var g = b ? d : 0,
+        h = b ? -1 : 1,
+        k = c ? e : 0,
+        l = c ? -1 : 1;
+      f.transform += "translate(" + this.format(g) + "," + this.format(k) + ")scale(" + this.format(h) + "," + this.format(l) + ")translate(" + this.format(-g) + "," + this.format(-k) + ")"
+    }
+    if (b ? !c : c) a *= -1;
+    0 != a && (f.transform += "rotate(" + this.format(a) + "," + this.format(d) + "," + this.format(e) + ")");
+    f.rotation += a;
+    f.rotationCx =
+      d;
+    f.rotationCy = e
+  }
+};
+mxSvgCanvas2D.prototype.begin = function() {
+  mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+  this.node = this.createElement("path")
+};
+mxSvgCanvas2D.prototype.rect = function(a, b, c, d) {
+  var e = this.state,
+    f = this.createElement("rect");
+  f.setAttribute("x", this.format((a + e.dx) * e.scale));
+  f.setAttribute("y", this.format((b + e.dy) * e.scale));
+  f.setAttribute("width", this.format(c * e.scale));
+  f.setAttribute("height", this.format(d * e.scale));
+  this.node = f
+};
+mxSvgCanvas2D.prototype.roundrect = function(a, b, c, d, e, f) {
+  this.rect(a, b, c, d);
+  0 < e && this.node.setAttribute("rx", this.format(e * this.state.scale));
+  0 < f && this.node.setAttribute("ry", this.format(f * this.state.scale))
+};
+mxSvgCanvas2D.prototype.ellipse = function(a, b, c, d) {
+  var e = this.state,
+    f = this.createElement("ellipse");
+  f.setAttribute("cx", Math.round((a + c / 2 + e.dx) * e.scale));
+  f.setAttribute("cy", Math.round((b + d / 2 + e.dy) * e.scale));
+  f.setAttribute("rx", c / 2 * e.scale);
+  f.setAttribute("ry", d / 2 * e.scale);
+  this.node = f
+};
+mxSvgCanvas2D.prototype.image = function(a, b, c, d, e, f, g, h) {
+  e = this.converter.convert(e);
+  f = null != f ? f : !0;
+  g = null != g ? g : !1;
+  h = null != h ? h : !1;
+  var k = this.state;
+  a += k.dx;
+  b += k.dy;
+  var l = this.createElement("image");
+  l.setAttribute("x", this.format(a * k.scale));
+  l.setAttribute("y", this.format(b * k.scale));
+  l.setAttribute("width", this.format(c * k.scale));
+  l.setAttribute("height", this.format(d * k.scale));
+  null == l.setAttributeNS ? l.setAttribute("xlink:href", e) : l.setAttributeNS(mxConstants.NS_XLINK, "href", e);
+  f || l.setAttribute("preserveAspectRatio",
+    "none");
+  1 > k.alpha && l.setAttribute("opacity", k.alpha);
+  e = this.state.transform || "";
+  if (g || h) {
+    var m = f = 1,
+      n = 0,
+      p = 0;
+    g && (f = -1, n = -c - 2 * a);
+    h && (m = -1, p = -d - 2 * b);
+    e += "scale(" + f + "," + m + ")translate(" + n + "," + p + ")"
+  }
+  0 < e.length && l.setAttribute("transform", e);
+  this.root.appendChild(l);
+  this.blockImagePointerEvents && (l.setAttribute("style", "pointer-events:none"), l = this.createElement("rect"), l.setAttribute("visibility", "hidden"), l.setAttribute("pointer-events", "fill"),
+    l.setAttribute("x", this.format(a * k.scale)), l.setAttribute("y",
+      this.format(b * k.scale)), l.setAttribute("width", this.format(c * k.scale)), l.setAttribute("height", this.format(d * k.scale)), this.root.appendChild(l))
+};
+mxSvgCanvas2D.prototype.createDiv = function(a, b, c, d, e) {
+  c = this.state;
+  d = "display:inline-block;font-size:" + Math.round(c.fontSize) + "px;font-family:" + c.fontFamily + ";color:" + c.fontColor + ";line-height:" + Math.round(c.fontSize * mxConstants.LINE_HEIGHT) +
+    "px;" + d;
+  (c.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD && (d += "font-weight:bold;");
+  (c.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC && (d += "font-style:italic;");
+  (c.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE && (d += "font-decoration:underline;");
+  b == mxConstants.ALIGN_CENTER ? d += "text-align:center;" : b == mxConstants.ALIGN_RIGHT && (d += "text-align:right;");
+  b = "";
+  null != c.fontBackgroundColor && (b += "background-color:" + c.fontBackgroundColor + ";");
+  null != c.fontBorderColor && (b += "border:1px solid " + c.fontBorderColor + ";");
+  mxUtils.isNode(a) || (c = document.createElement("textarea"), c.innerHTML = a.replace(/&lt;/g, "\x26amp;lt;").replace(/&gt;/g, "\x26amp;gt;").replace(/</g, "\x26lt;").replace(/>/g, "\x26gt;"), a =
+    c.value, e ? d += b : 0 < b.length && (a = '\x3cdiv xmlns\x3d"http://www.w3.org/1999/xhtml" style\x3d"display:inline-block;' +
+      b + '"\x3e' + a + "\x3c/div\x3e"));
+  if (!mxClient.IS_IE && document.createElementNS) return e = document.createElementNS("http://www.w3.org/1999/xhtml", "div"), e.setAttribute("style", d), mxUtils.isNode(a) ? this.root.ownerDocument !=
+    document ? e.appendChild(a.cloneNode(!0)) : e.appendChild(a) : e.innerHTML = a, e;
+  mxUtils.isNode(a) && this.root.ownerDocument != document && (a = a.outerHTML);
+  a = a.replace(/<br>/g, "\x3cbr /\x3e").replace(/<hr>/g, "\x3chr /\x3e");
+  return mxUtils.parseXml('\x3cdiv xmlns\x3d"http://www.w3.org/1999/xhtml" style\x3d"' +
+    d + '"\x3e' + a + "\x3c/div\x3e").documentElement
+};
+mxSvgCanvas2D.prototype.text = function(a, b, c, d, e, f, g, h, k, l, m, n) {
+  if (this.textEnabled && null != e) {
+    n = null != n ? n : 0;
+    var p = this.state;
+    a += p.dx;
+    b += p.dy;
+    if (this.foEnabled && "html" == k) {
+      var q = "vertical-align:top;";
+      m ? (q += "overflow:hidden;", 0 < d && (q += "max-height:" + Math.round(d) + "px;"), 0 < c && (q += "width:" + Math.round(c) + "px;")) : l && (q += "width:" + Math.round(c) + "px;", 0 < d &&
+        (q += "max-height:" + Math.round(d) + "px;"));
+      h ? (!m && 0 < c && (q += "width:" + Math.round(c) + "px;"), q += "white-space:normal;") : q += "white-space:nowrap;";
+      h = this.createElement("g");
+      1 > p.alpha && h.setAttribute("opacity", p.alpha);
+      k = this.createElement("foreignObject");
+      k.setAttribute("pointer-events", "all");
+      q = this.createDiv(e, f, g, q, l);
+      if (null != q) {
+        h.appendChild(k);
+        this.root.appendChild(h);
+        var s = 0;
+        if (mxClient.IS_IE && !mxClient.IS_SVG) {
+          var r = document.createElement("div");
+          r.style.cssText = q.getAttribute("style");
+          r.style.display = mxClient.IS_QUIRKS ? "inline" : "inline-block";
+          r.style.visibility = "hidden";
+          r.innerHTML = mxUtils.isNode(e) ? e.outerHTML : e;
+          document.body.appendChild(r);
+          s = r.offsetWidth;
+          d = mxClient.IS_QUIRKS && 0 < d && m ? Math.min(d, r.offsetHeight + 2) : r.offsetHeight;
+          r.parentNode.removeChild(r);
+          k.appendChild(q)
+        } else this.root.ownerDocument != document ? (q.style.visibility = "hidden", document.body.appendChild(q), s = q.offsetWidth, d = q.offsetHeight, k.appendChild(q), q.style.visibility = "") :
+          (k.appendChild(q), s = q.offsetWidth, d = q.offsetHeight);
+        c = l ? Math.max(c, s) : s;
+        1 > p.alpha && h.setAttribute("opacity", p.alpha);
+        l = e = 0;
+        f == mxConstants.ALIGN_CENTER ? e -= c / 2 : f == mxConstants.ALIGN_RIGHT && (e -= c);
+        a += e;
+        g == mxConstants.ALIGN_MIDDLE ?
+          l -= d / 2 : g == mxConstants.ALIGN_BOTTOM && (l -= d);
+        b += l;
+        f = 1 != p.scale ? "scale(" + p.scale + ")" : "";
+        0 != p.rotation && this.rotateHtml ? (f += "rotate(" + p.rotation + "," + c / 2 + "," + d / 2 + ")", b = this.rotatePoint((a + c / 2) * p.scale, (b + d / 2) * p.scale, p.rotation, p.rotationCx,
+          p.rotationCy), a = b.x - c * p.scale / 2, b = b.y - d * p.scale / 2) : (a *= p.scale, b *= p.scale);
+        0 != n && (f += "rotate(" + n + "," + -e + "," + -l + ")");
+        h.setAttribute("transform", "translate(" + Math.round(a) + "," + Math.round(b) + ")" + f);
+        k.setAttribute("width", Math.round(Math.max(1, c)));
+        k.setAttribute("height",
+          Math.round(Math.max(1, d)))
+      }
+    } else this.plainText(a, b, c, d, e, f, g, h, l, m, n)
+  }
+};
+mxSvgCanvas2D.prototype.createClip = function(a, b, c, d) {
+  a = Math.round(a);
+  b = Math.round(b);
+  c = Math.round(c);
+  d = Math.round(d);
+  for (var e = "mx-clip-" + a + "-" + b + "-" + c + "-" + d, f = 0, g = e + "-" + f; null != document.getElementById(g);) g = e + "-" + ++f;
+  clip = this.createElement("clipPath");
+  clip.setAttribute("id", g);
+  e = this.createElement("rect");
+  e.setAttribute("x", a);
+  e.setAttribute("y", b);
+  e.setAttribute("width", c);
+  e.setAttribute("height", d);
+  clip.appendChild(e);
+  return clip
+};
+mxSvgCanvas2D.prototype.plainText = function(a, b, c, d, e, f, g, h, k, l, m) {
+  m = null != m ? m : 0;
+  h = this.state;
+  var n = Math.round(h.fontSize),
+    p = this.createElement("g"),
+    q = h.transform || "";
+  0 != m && (q += "rotate(" + m + "," + this.format(a * h.scale) + "," + this.format(b * h.scale) + ")");
+  if (l && 0 < c && 0 < d) {
+    var s = a;
+    m = b;
+    f == mxConstants.ALIGN_CENTER ? s -= c / 2 : f == mxConstants.ALIGN_RIGHT && (s -= c);
+    g == mxConstants.ALIGN_MIDDLE ? m -= d / 2 : g == mxConstants.ALIGN_BOTTOM && (m -= d);
+    m = this.createClip(s * h.scale - 2, m * h.scale - 2, c * h.scale + 4, d * h.scale + 4);
+    null != this.defs ? this.defs.appendChild(m) :
+      this.root.appendChild(m);
+    p.setAttribute("clip-path", "url(#" + m.getAttribute("id") + ")")
+  }
+  this.updateFont(p, f);
+  m = f == mxConstants.ALIGN_RIGHT ? "end" : f == mxConstants.ALIGN_CENTER ? "middle" : "start";
+  "start" != m && p.setAttribute("text-anchor", m);
+  (!this.styleEnabled || n != mxConstants.DEFAULT_FONTSIZE) && p.setAttribute("font-size", Math.round(n * h.scale) + "px");
+  0 < q.length && p.setAttribute("transform", q);
+  1 > h.alpha && p.setAttribute("opacity", h.alpha);
+  var q = e.split("\n"),
+    s = Math.round(n * mxConstants.LINE_HEIGHT),
+    r = n + (q.length -
+      1) * s;
+  m = b + n - 1;
+  g == mxConstants.ALIGN_MIDDLE ? (d = (this.matchHtmlAlignment && l && 0 < d ? Math.min(r, d) : r) / 2, m -= d + 1) : g == mxConstants.ALIGN_BOTTOM && (d = this.matchHtmlAlignment && l && 0 < d ?
+    Math.min(r, d) : r, m -= d + 2);
+  for (d = 0; d < q.length; d++) 0 < q[d].length && 0 < mxUtils.trim(q[d]).length && (l = this.createElement("text"), l.setAttribute("x", this.format(a * h.scale)), l.setAttribute("y", this.format(
+    m * h.scale)), mxUtils.write(l, q[d]), p.appendChild(l)), m += s;
+  this.root.appendChild(p);
+  this.addTextBackground(p, e, a, b, c, r, f, g, k)
+};
+mxSvgCanvas2D.prototype.updateFont = function(a) {
+  var b = this.state;
+  a.setAttribute("fill", b.fontColor);
+  (!this.styleEnabled || b.fontFamily != mxConstants.DEFAULT_FONTFAMILY) && a.setAttribute("font-family", b.fontFamily);
+  (b.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD && a.setAttribute("font-weight", "bold");
+  (b.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC && a.setAttribute("font-style", "italic");
+  (b.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE && a.setAttribute("text-decoration",
+    "underline")
+};
+mxSvgCanvas2D.prototype.addTextBackground = function(a, b, c, d, e, f, g, h, k) {
+  var l = this.state;
+  if (null != l.fontBackgroundColor || null != l.fontBorderColor) {
+    var m = null;
+    k ? (g == mxConstants.ALIGN_CENTER ? c -= e / 2 : g == mxConstants.ALIGN_RIGHT && (c -= e), h == mxConstants.ALIGN_MIDDLE ? d -= f / 2 : h == mxConstants.ALIGN_BOTTOM && (d -= f), m = new mxRectangle(
+      (c + 1) * l.scale, d * l.scale, (e - 2) * l.scale, (f + 2) * l.scale)) : null != a.getBBox && this.root.ownerDocument == document ? (m = a.getBBox(), b = mxClient.IS_IE && mxClient.IS_SVG,
+      m = new mxRectangle(m.x, m.y + (b ? 0 :
+        1), m.width, m.height + (b ? 1 : 0))) : (m = document.createElement("div"), m.style.lineHeight = Math.round(l.fontSize * mxConstants.LINE_HEIGHT) + "px", m.style.fontSize = Math.round(l.fontSize) +
+      "px", m.style.fontFamily = l.fontFamily, m.style.whiteSpace = "nowrap", m.style.position = "absolute", m.style.visibility = "hidden", m.style.display = mxClient.IS_QUIRKS ? "inline" :
+      "inline-block", m.style.zoom = "1", (l.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD && (m.style.fontWeight = "bold"), (l.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC &&
+      (m.style.fontStyle = "italic"), b = mxUtils.htmlEntities(b, !1), m.innerHTML = b.replace(/\n/g, "\x3cbr/\x3e"), document.body.appendChild(m), e = m.offsetWidth, f = m.offsetHeight, m.parentNode
+      .removeChild(m), g == mxConstants.ALIGN_CENTER ? c -= e / 2 : g == mxConstants.ALIGN_RIGHT && (c -= e), h == mxConstants.ALIGN_MIDDLE ? d -= f / 2 : h == mxConstants.ALIGN_BOTTOM && (d -= f),
+      m = new mxRectangle((c + 1) * l.scale, (d + 2) * l.scale, e * l.scale, (f + 1) * l.scale));
+    null != m && (b = this.createElement("rect"), b.setAttribute("fill", l.fontBackgroundColor || "none"), b.setAttribute("stroke",
+      l.fontBorderColor || "none"), b.setAttribute("x", Math.floor(m.x - 1)), b.setAttribute("y", Math.floor(m.y - 1)), b.setAttribute("width", Math.ceil(m.width + 2)), b.setAttribute("height",
+      Math.ceil(m.height)), l = null != l.fontBorderColor ? Math.max(1, this.format(l.scale)) : 0, b.setAttribute("stroke-width", l), this.root.ownerDocument == document && 1 == mxUtils.mod(l,
+      2) && b.setAttribute("transform", "translate(0.5, 0.5)"), a.insertBefore(b, a.firstChild))
+  }
+};
+mxSvgCanvas2D.prototype.stroke = function() {
+  this.addNode(!1, !0)
+};
+mxSvgCanvas2D.prototype.fill = function() {
+  this.addNode(!0, !1)
+};
+mxSvgCanvas2D.prototype.fillAndStroke = function() {
+  this.addNode(!0, !0)
+};
+var mxVmlCanvas2D = function(a) {
+  mxAbstractCanvas2D.call(this);
+  this.root = a
+};
+mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);
+mxVmlCanvas2D.prototype.node = null;
+mxVmlCanvas2D.prototype.textEnabled = !0;
+mxVmlCanvas2D.prototype.moveOp = "m";
+mxVmlCanvas2D.prototype.lineOp = "l";
+mxVmlCanvas2D.prototype.curveOp = "c";
+mxVmlCanvas2D.prototype.closeOp = "x";
+mxVmlCanvas2D.prototype.rotatedHtmlBackground = "";
+mxVmlCanvas2D.prototype.vmlScale = 1;
+mxVmlCanvas2D.prototype.addNode = function(a, b) {
+  var c = this.node,
+    d = this.state;
+  if (null != c) {
+    if ("shape" == c.nodeName)
+      if (null != this.path && 0 < this.path.length) c.path = this.path.join(" ") + " e", c.style.width = this.root.style.width, c.style.height = this.root.style.height, c.coordsize = parseInt(c.style
+        .width) + " " + parseInt(c.style.height);
+      else return;
+    c.strokeweight = this.format(Math.max(1, d.strokeWidth * d.scale / this.vmlScale)) + "px";
+    d.shadow && this.root.appendChild(this.createShadow(c, a && null != d.fillColor, b && null != d.strokeColor));
+    b && null != d.strokeColor ? (c.stroked = "true", c.strokecolor = d.strokeColor) : c.stroked = "false";
+    c.appendChild(this.createStroke());
+    a && null != d.fillColor ? c.appendChild(this.createFill()) : this.pointerEvents && ("shape" != c.nodeName || this.path[this.path.length - 1] == this.closeOp) ? c.appendChild(this.createTransparentFill()) :
+      c.filled = "false";
+    this.root.appendChild(c)
+  }
+};
+mxVmlCanvas2D.prototype.createTransparentFill = function() {
+  var a = document.createElement(mxClient.VML_PREFIX + ":fill");
+  a.src = mxClient.imageBasePath + "/transparent.gif";
+  a.type = "tile";
+  return a
+};
+mxVmlCanvas2D.prototype.createFill = function() {
+  var a = this.state,
+    b = document.createElement(mxClient.VML_PREFIX + ":fill");
+  b.color = a.fillColor;
+  if (null != a.gradientColor) {
+    b.type = "gradient";
+    b.method = "none";
+    b.color2 = a.gradientColor;
+    var c = 180 - a.rotation,
+      c = a.gradientDirection == mxConstants.DIRECTION_WEST ? c - (90 + ("x" == this.root.style.flip ? 180 : 0)) : a.gradientDirection == mxConstants.DIRECTION_EAST ? c + (90 + ("x" == this.root.style
+        .flip ? 180 : 0)) : a.gradientDirection == mxConstants.DIRECTION_NORTH ? c - (180 + ("y" == this.root.style.flip ?
+        -180 : 0)) : c + ("y" == this.root.style.flip ? -180 : 0);
+    if ("x" == this.root.style.flip || "y" == this.root.style.flip) c *= -1;
+    b.angle = mxUtils.mod(c, 360);
+    b.opacity = 100 * a.alpha * a.fillAlpha + "%";
+    b.setAttribute(mxClient.OFFICE_PREFIX + ":opacity2", 100 * a.alpha * a.gradientAlpha + "%")
+  } else 1 > a.alpha && (b.opacity = 100 * a.alpha + "%");
+  return b
+};
+mxVmlCanvas2D.prototype.createStroke = function() {
+  var a = this.state,
+    b = document.createElement(mxClient.VML_PREFIX + ":stroke");
+  b.endcap = a.lineCap || "flat";
+  b.joinstyle = a.lineJoin || "miter";
+  b.miterlimit = a.miterLimit || "10";
+  1 > a.alpha && (b.opacity = 100 * a.alpha + "%");
+  a.dashed && (b.dashstyle = this.getVmlDashStyle());
+  return b
+};
+mxVmlCanvas2D.prototype.getVmlDashStyle = function() {
+  var a = "dash";
+  if (null != this.state.dashPattern) {
+    var b = this.state.dashPattern.split(" ");
+    0 < b.length && 1 == b[0] && (a = "0 2")
+  }
+  return a
+};
+mxVmlCanvas2D.prototype.createShadow = function(a, b, c) {
+  var d = this.state,
+    e = -d.rotation * (Math.PI / 180),
+    f = Math.cos(e),
+    e = Math.sin(e),
+    g = d.shadowDx * d.scale,
+    h = d.shadowDy * d.scale;
+  "x" == this.root.style.flip ? g *= -1 : "y" == this.root.style.flip && (h *= -1);
+  var k = a.cloneNode(!0);
+  k.style.marginLeft = Math.round(g * f - h * e) + "px";
+  k.style.marginTop = Math.round(g * e + h * f) + "px";
+  8 == document.documentMode && (k.strokeweight = a.strokeweight, "shape" == a.nodeName && (k.path = this.path.join(" ") + " e", k.style.width = this.root.style.width, k.style.height =
+    this.root.style.height, k.coordsize = parseInt(a.style.width) + " " + parseInt(a.style.height)));
+  c ? (k.strokecolor = d.shadowColor, k.appendChild(this.createShadowStroke())) : k.stroked = "false";
+  b ? k.appendChild(this.createShadowFill()) : k.filled = "false";
+  return k
+};
+mxVmlCanvas2D.prototype.createShadowFill = function() {
+  var a = document.createElement(mxClient.VML_PREFIX + ":fill");
+  a.color = this.state.shadowColor;
+  a.opacity = 100 * this.state.alpha * this.state.shadowAlpha + "%";
+  return a
+};
+mxVmlCanvas2D.prototype.createShadowStroke = function() {
+  var a = this.createStroke();
+  a.opacity = 100 * this.state.alpha * this.state.shadowAlpha + "%";
+  return a
+};
+mxVmlCanvas2D.prototype.rotate = function(a, b, c, d, e) {
+  b && c ? a += 180 : b ? this.root.style.flip = "x" : c && (this.root.style.flip = "y");
+  if (b ? !c : c) a *= -1;
+  this.root.style.rotation = a;
+  this.state.rotation += a;
+  this.state.rotationCx = d;
+  this.state.rotationCy = e
+};
+mxVmlCanvas2D.prototype.begin = function() {
+  mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+  this.node = document.createElement(mxClient.VML_PREFIX + ":shape");
+  this.node.style.position = "absolute"
+};
+mxVmlCanvas2D.prototype.quadTo = function(a, b, c, d) {
+  var e = this.state,
+    f = (this.lastX + e.dx) * e.scale,
+    g = (this.lastY + e.dy) * e.scale;
+  a = (a + e.dx) * e.scale;
+  b = (b + e.dy) * e.scale;
+  c = (c + e.dx) * e.scale;
+  d = (d + e.dy) * e.scale;
+  var g = g + 2 / 3 * (b - g),
+    h = c + 2 / 3 * (a - c);
+  b = d + 2 / 3 * (b - d);
+  this.path.push("c " + this.format(f + 2 / 3 * (a - f)) + " " + this.format(g) + " " + this.format(h) + " " + this.format(b) + " " + this.format(c) + " " + this.format(d));
+  this.lastX = c / e.scale - e.dx;
+  this.lastY = d / e.scale - e.dy
+};
+mxVmlCanvas2D.prototype.createRect = function(a, b, c, d, e) {
+  var f = this.state;
+  a = document.createElement(a);
+  a.style.position = "absolute";
+  a.style.left = this.format((b + f.dx) * f.scale) + "px";
+  a.style.top = this.format((c + f.dy) * f.scale) + "px";
+  a.style.width = this.format(d * f.scale) + "px";
+  a.style.height = this.format(e * f.scale) + "px";
+  return a
+};
+mxVmlCanvas2D.prototype.rect = function(a, b, c, d) {
+  this.node = this.createRect(mxClient.VML_PREFIX + ":rect", a, b, c, d)
+};
+mxVmlCanvas2D.prototype.roundrect = function(a, b, c, d, e, f) {
+  this.node = this.createRect(mxClient.VML_PREFIX + ":roundrect", a, b, c, d);
+  this.node.setAttribute("arcsize", Math.max(100 * e / c, 100 * f / d) + "%")
+};
+mxVmlCanvas2D.prototype.ellipse = function(a, b, c, d) {
+  this.node = this.createRect(mxClient.VML_PREFIX + ":oval", a, b, c, d)
+};
+mxVmlCanvas2D.prototype.image = function(a, b, c, d, e, f, g, h) {
+  var k = null;
+  f ? (k = this.createRect(mxClient.VML_PREFIX + ":rect", a, b, c, d), k.stroked = "false", a = document.createElement(mxClient.VML_PREFIX + ":fill"), a.aspect = f ? "atmost" : "ignore", a.rotate =
+    "true", a.type = "frame", a.src = e, k.appendChild(a)) : (k = this.createRect(mxClient.VML_PREFIX + ":image", a, b, c, d), k.src = e);
+  g && h ? k.style.rotation = "180" : g ? k.style.flip = "x" : h && (k.style.flip = "y");
+  1 > this.state.alpha && (k.style.filter += "alpha(opacity\x3d" + 100 * this.state.alpha + ")");
+  this.root.appendChild(k)
+};
+mxVmlCanvas2D.prototype.createDiv = function(a, b, c, d) {
+  c = document.createElement("div");
+  var e = this.state,
+    f = "";
+  null != e.fontBackgroundColor && (f += "background-color:" + e.fontBackgroundColor + ";");
+  null != e.fontBorderColor && (f += "border:1px solid " + e.fontBorderColor + ";");
+  mxUtils.isNode(a) ? c.appendChild(a) : 0 < f.length && !d ? (d = document.createElement("div"), d.style.cssText = f, d.style.display = mxClient.IS_QUIRKS ? "inline" : "inline-block", d.style.zoom =
+    "1", d.innerHTML = a, c.appendChild(d)) : (c.style.cssText = f, c.innerHTML = a);
+  a = c.style;
+  a.fontSize = Math.round(e.fontSize / this.vmlScale) + "px";
+  a.fontFamily = e.fontFamily;
+  a.color = e.fontColor;
+  a.verticalAlign = "top";
+  a.textAlign = b || "left";
+  a.lineHeight = Math.round(e.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + "px";
+  (e.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD && (a.fontWeight = "bold");
+  (e.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC && (a.fontStyle = "italic");
+  (e.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE && (a.fontDecoration = "underline");
+  return c
+};
+mxVmlCanvas2D.prototype.text = function(a, b, c, d, e, f, g, h, k, l, m, n) {
+  if (this.textEnabled && null != e) {
+    var p = this.state;
+    if ("html" == k) {
+      null != p.rotation && (b = this.rotatePoint(a, b, p.rotation, p.rotationCx, p.rotationCy), a = b.x, b = b.y);
+      8 == document.documentMode ? (a += p.dx, b += p.dy) : (a *= p.scale, b *= p.scale);
+      k = 8 == document.documentMode ? document.createElement(mxClient.VML_PREFIX + ":group") : document.createElement("div");
+      k.style.position = "absolute";
+      k.style.display = "inline";
+      k.style.left = this.format(a) + "px";
+      k.style.top = this.format(b) +
+        "px";
+      k.style.zoom = p.scale;
+      var q = document.createElement("div");
+      q.style.position = "relative";
+      q.style.display = "inline";
+      var s = mxUtils.getAlignmentAsPoint(f, g),
+        r = s.x,
+        s = s.y;
+      e = this.createDiv(e, f, g, l);
+      f = document.createElement("div");
+      h ? (!m && 0 < c && (e.style.width = Math.round(c) + "px"), e.style.whiteSpace = "normal") : e.style.whiteSpace = "nowrap";
+      h = p.rotation + (n || 0);
+      this.rotateHtml && 0 != h ? (f.style.display = "inline", f.style.zoom = "1", f.appendChild(e), 8 == document.documentMode && "DIV" != this.root.nodeName ? (q.appendChild(f), k.appendChild(q)) :
+        k.appendChild(f)) : 8 == document.documentMode ? (q.appendChild(e), k.appendChild(q)) : (e.style.display = "inline", k.appendChild(e));
+      "DIV" != this.root.nodeName ? (n = document.createElement(mxClient.VML_PREFIX + ":rect"), n.stroked = "false", n.filled = "false", n.appendChild(k), this.root.appendChild(n)) : this.root.appendChild(
+        k);
+      m ? (e.style.overflow = "hidden", 0 < c && (e.style.width = Math.round(c) + "px"), 0 < d && 8 == document.documentMode && (e.style.maxHeight = Math.round(d) + "px")) : l && (e.style.width = c +
+        "px", 0 < d && (e.style.maxHeight = Math.round(d) +
+          "px"));
+      if (this.rotateHtml && 0 != h) {
+        c = h * (Math.PI / 180);
+        h = parseFloat(parseFloat(Math.cos(c)).toFixed(8));
+        n = parseFloat(parseFloat(Math.sin(-c)).toFixed(8));
+        c %= 2 * Math.PI;
+        0 > c && (c += 2 * Math.PI);
+        c %= Math.PI;
+        c > Math.PI / 2 && (c = Math.PI - c);
+        g = Math.cos(c);
+        var t = Math.sin(c);
+        8 == document.documentMode && (e.style.display = "inline-block", f.style.display = "inline-block", q.style.display = "inline-block");
+        e.style.visibility = "hidden";
+        document.body.appendChild(e);
+        c = e.offsetWidth;
+        q = e.offsetHeight;
+        if (mxClient.IS_QUIRKS && (m || l) && q > d) q =
+          d, e.style.height = q + "px";
+        d = q;
+        m = (d - d * g + c * -t) / 2 - n * c * (r + 0.5) + h * d * (s + 0.5);
+        l = (c - c * g + d * -t) / 2 + h * c * (r + 0.5) + n * d * (s + 0.5);
+        "group" == k.nodeName && "DIV" == this.root.nodeName ? (r = document.createElement("div"), r.style.display = "inline-block", r.style.position = "absolute", r.style.left = this.format(a + (l -
+          c / 2) * p.scale) + "px", r.style.top = this.format(b + (m - d / 2) * p.scale) + "px", k.parentNode.appendChild(r), r.appendChild(k)) : (p = 8 == document.documentMode ? 1 : p.scale, k.style
+          .left = this.format(a + (l - c / 2) * p) + "px", k.style.top = this.format(b + (m -
+            d / 2) * p) + "px");
+        f.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11\x3d" + h + ", M12\x3d" + n + ", M21\x3d" + -n + ", M22\x3d" + h + ", sizingMethod\x3d'auto expand')";
+        f.style.backgroundColor = this.rotatedHtmlBackground;
+        1 > this.state.alpha && (f.style.filter += "alpha(opacity\x3d" + 100 * this.state.alpha + ")");
+        e.style.visibility = "";
+        f.appendChild(e)
+      } else 8 != document.documentMode ? (e.style.verticalAlign = "top", 1 > this.state.alpha && (k.style.filter = "alpha(opacity\x3d" + 100 * this.state.alpha + ")"), p = e.parentNode, e.style.visibility =
+        "hidden", document.body.appendChild(e), c = e.offsetWidth, q = e.offsetHeight, mxClient.IS_QUIRKS && (m && q > d) && (q = d, e.style.height = q + "px"), d = q, e.style.visibility = "", p.appendChild(
+          e), k.style.left = this.format(a + c * r * this.state.scale) + "px", k.style.top = this.format(b + d * s * this.state.scale) + "px") : (1 > this.state.alpha && (e.style.filter =
+        "alpha(opacity\x3d" + 100 * this.state.alpha + ")"), q.style.left = 100 * r + "%", q.style.top = 100 * s + "%")
+    } else this.plainText(a, b, c, d, mxUtils.htmlEntities(e, !1), f, g, h, k, l, m, n)
+  }
+};
+mxVmlCanvas2D.prototype.plainText = function(a, b, c, d, e, f, g, h, k, l, m, n) {
+  d = this.state;
+  a = (a + d.dx) * d.scale;
+  b = (b + d.dy) * d.scale;
+  c = document.createElement(mxClient.VML_PREFIX + ":shape");
+  c.style.width = "1px";
+  c.style.height = "1px";
+  c.stroked = "false";
+  l = document.createElement(mxClient.VML_PREFIX + ":fill");
+  l.color = d.fontColor;
+  l.opacity = 100 * d.alpha + "%";
+  c.appendChild(l);
+  l = document.createElement(mxClient.VML_PREFIX + ":path");
+  l.textpathok = "true";
+  l.v = "m " + this.format(0) + " " + this.format(0) + " l " + this.format(1) + " " + this.format(0);
+  c.appendChild(l);
+  l = document.createElement(mxClient.VML_PREFIX + ":textpath");
+  l.style.cssText = "v-text-align:" + f;
+  l.style.align = f;
+  l.style.fontFamily = d.fontFamily;
+  l.string = e;
+  l.on = "true";
+  f = Math.round(d.fontSize * d.scale / this.vmlScale);
+  l.style.fontSize = f + "px";
+  (d.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD && (l.style.fontWeight = "bold");
+  (d.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC && (l.style.fontStyle = "italic");
+  (d.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE &&
+    (l.style.textDecoration = "underline");
+  e = e.split("\n");
+  d = f + (e.length - 1) * f * mxConstants.LINE_HEIGHT;
+  f = e = 0;
+  g == mxConstants.ALIGN_BOTTOM ? f = -d / 2 : g != mxConstants.ALIGN_MIDDLE && (f = d / 2);
+  null != n && (c.style.rotation = n, g = n * (Math.PI / 180), e = Math.sin(g) * f, f *= Math.cos(g));
+  c.appendChild(l);
+  c.style.left = this.format(a - e) + "px";
+  c.style.top = this.format(b + f) + "px";
+  this.root.appendChild(c)
+};
+mxVmlCanvas2D.prototype.stroke = function() {
+  this.addNode(!1, !0)
+};
+mxVmlCanvas2D.prototype.fill = function() {
+  this.addNode(!0, !1)
+};
+mxVmlCanvas2D.prototype.fillAndStroke = function() {
+  this.addNode(!0, !0)
+};
+
+function mxGuide(a, b) {
+  this.graph = a;
+  this.setStates(b)
+}
+mxGuide.prototype.graph = null;
+mxGuide.prototype.states = null;
+mxGuide.prototype.horizontal = !0;
+mxGuide.prototype.vertical = !0;
+mxGuide.prototype.guideX = null;
+mxGuide.prototype.guideY = null;
+mxGuide.prototype.setStates = function(a) {
+  this.states = a
+};
+mxGuide.prototype.isEnabledForEvent = function(a) {
+  return !0
+};
+mxGuide.prototype.getGuideTolerance = function() {
+  return this.graph.gridSize * this.graph.view.scale / 2
+};
+mxGuide.prototype.createGuideShape = function(a) {
+  a = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
+  a.isDashed = !0;
+  return a
+};
+mxGuide.prototype.move = function(a, b, c) {
+  if (null != this.states && (this.horizontal || this.vertical) && null != a && null != b) {
+    var d = this.graph.getView().translate,
+      e = this.graph.getView().scale,
+      f = b.x,
+      g = b.y,
+      h = !1,
+      k = !1,
+      l = this.getGuideTolerance(),
+      m = l,
+      n = l,
+      l = a.clone();
+    l.x += b.x;
+    l.y += b.y;
+    var p = l.x,
+      q = l.x + l.width,
+      s = l.getCenterX(),
+      r = l.y,
+      t = l.y + l.height,
+      v = l.getCenterY();
+    b = function(b) {
+      b += this.graph.panDx;
+      var c = !1;
+      Math.abs(b - s) < m ? (f = b - a.getCenterX(), m = Math.abs(b - s), c = !0) : Math.abs(b - p) < m ? (f = b - a.x, m = Math.abs(b - p), c = !0) : Math.abs(b -
+        q) < m && (f = b - a.x - a.width, m = Math.abs(b - q), c = !0);
+      if (c) {
+        null == this.guideX && (this.guideX = this.createGuideShape(!0), this.guideX.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG,
+          this.guideX.pointerEvents = !1, this.guideX.init(this.graph.getView().getOverlayPane()));
+        var d = this.graph.container;
+        b -= this.graph.panDx;
+        this.guideX.points = [new mxPoint(b, -this.graph.panDy), new mxPoint(b, d.scrollHeight - 3 - this.graph.panDy)]
+      }
+      h = h || c
+    };
+    for (var l = function(b) {
+        b += this.graph.panDy;
+        var c = !1;
+        Math.abs(b - v) < n ? (g = b - a.getCenterY(), n = Math.abs(b - v), c = !0) : Math.abs(b - r) < n ? (g = b - a.y, n = Math.abs(b - r), c = !0) : Math.abs(b - t) < n && (g = b - a.y - a.height,
+          n = Math.abs(b - t), c = !0);
+        if (c) {
+          null == this.guideY && (this.guideY = this.createGuideShape(!1), this.guideY.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG,
+            this.guideY.pointerEvents = !1, this.guideY.init(this.graph.getView().getOverlayPane()));
+          var d = this.graph.container;
+          b -= this.graph.panDy;
+          this.guideY.points = [new mxPoint(-this.graph.panDx,
+            b), new mxPoint(d.scrollWidth - 3 - this.graph.panDx, b)]
+        }
+        k = k || c
+      }, w = 0; w < this.states.length; w++) {
+      var u = this.states[w];
+      null != u && (this.horizontal && (b.call(this, u.getCenterX()), b.call(this, u.x), b.call(this, u.x + u.width)), this.vertical && (l.call(this, u.getCenterY()), l.call(this, u.y), l.call(this,
+        u.y + u.height)))
+    }!h && null != this.guideX ? this.guideX.node.style.visibility = "hidden" : null != this.guideX && (this.guideX.node.style.visibility = "visible", this.guideX.redraw());
+    !k && null != this.guideY ? this.guideY.node.style.visibility =
+      "hidden" : null != this.guideY && (this.guideY.node.style.visibility = "visible", this.guideY.redraw());
+    c && (h || (c = a.x - (this.graph.snap(a.x / e - d.x) + d.x) * e, f = this.graph.snap(f / e) * e - c), k || (d = a.y - (this.graph.snap(a.y / e - d.y) + d.y) * e, g = this.graph.snap(g / e) * e -
+      d));
+    b = new mxPoint(f, g)
+  }
+  return b
+};
+mxGuide.prototype.hide = function() {
+  null != this.guideX && (this.guideX.node.style.visibility = "hidden");
+  null != this.guideY && (this.guideY.node.style.visibility = "hidden")
+};
+mxGuide.prototype.destroy = function() {
+  null != this.guideX && (this.guideX.destroy(), this.guideX = null);
+  null != this.guideY && (this.guideY.destroy(), this.guideY = null)
+};
+
+function mxStencil(a) {
+  this.desc = a;
+  this.parseDescription();
+  this.parseConstraints()
+}
+mxStencil.defaultLocalized = !1;
+mxStencil.prototype.desc = null;
+mxStencil.prototype.constraints = null;
+mxStencil.prototype.aspect = null;
+mxStencil.prototype.w0 = null;
+mxStencil.prototype.h0 = null;
+mxStencil.prototype.bgNode = null;
+mxStencil.prototype.fgNode = null;
+mxStencil.prototype.strokewidth = null;
+mxStencil.prototype.parseDescription = function() {
+  this.fgNode = this.desc.getElementsByTagName("foreground")[0];
+  this.bgNode = this.desc.getElementsByTagName("background")[0];
+  this.w0 = Number(this.desc.getAttribute("w") || 100);
+  this.h0 = Number(this.desc.getAttribute("h") || 100);
+  var a = this.desc.getAttribute("aspect");
+  this.aspect = null != a ? a : "variable";
+  a = this.desc.getAttribute("strokewidth");
+  this.strokewidth = null != a ? a : "1"
+};
+mxStencil.prototype.parseConstraints = function() {
+  var a = this.desc.getElementsByTagName("connections")[0];
+  if (null != a && (a = mxUtils.getChildNodes(a), null != a && 0 < a.length)) {
+    this.constraints = [];
+    for (var b = 0; b < a.length; b++) this.constraints.push(this.parseConstraint(a[b]))
+  }
+};
+mxStencil.prototype.parseConstraint = function(a) {
+  var b = Number(a.getAttribute("x")),
+    c = Number(a.getAttribute("y"));
+  a = "1" == a.getAttribute("perimeter");
+  return new mxConnectionConstraint(new mxPoint(b, c), a)
+};
+mxStencil.prototype.evaluateTextAttribute = function(a, b, c) {
+  b = this.evaluateAttribute(a, b, c);
+  a = a.getAttribute("localized");
+  if (mxStencil.defaultLocalized && null == a || "1" == a) b = mxResources.get(b);
+  return b
+};
+mxStencil.prototype.evaluateAttribute = function(a, b, c) {
+  b = a.getAttribute(b);
+  null == b && (a = mxUtils.getTextContent(a), null != a && (a = mxUtils.eval(a), "function" == typeof a && (b = a(c))));
+  return b
+};
+mxStencil.prototype.drawShape = function(a, b, c, d, e, f) {
+  this.drawChildren(a, b, c, d, e, f, this.bgNode, !1);
+  this.drawChildren(a, b, c, d, e, f, this.fgNode, !0)
+};
+mxStencil.prototype.drawChildren = function(a, b, c, d, e, f, g, h) {
+  if (null != g) {
+    var k = mxUtils.getValue(b.style, mxConstants.STYLE_DIRECTION, null);
+    c = this.computeAspect(b.style, c, d, e, f, k);
+    d = Math.min(c.width, c.height);
+    d = "inherit" == this.strokewidth ? Number(mxUtils.getNumber(b.style, mxConstants.STYLE_STROKEWIDTH, 1)) : Number(this.strokewidth) * d;
+    a.setStrokeWidth(d);
+    for (g = g.firstChild; null != g;) g.nodeType == mxConstants.NODETYPE_ELEMENT && this.drawNode(a, b, g, c, h), g = g.nextSibling
+  }
+};
+mxStencil.prototype.computeAspect = function(a, b, c, d, e, f) {
+  a = b;
+  b = d / this.w0;
+  var g = e / this.h0;
+  if (f = "north" == f || "south" == f) {
+    g = d / this.h0;
+    b = e / this.w0;
+    var h = (d - e) / 2;
+    a += h;
+    c -= h
+  }
+  "fixed" == this.aspect && (b = g = Math.min(b, g), f ? (a += (e - this.w0 * b) / 2, c += (d - this.h0 * g) / 2) : (a += (d - this.w0 * b) / 2, c += (e - this.h0 * g) / 2));
+  return new mxRectangle(a, c, b, g)
+};
+mxStencil.prototype.drawNode = function(a, b, c, d, e) {
+  var f = c.nodeName,
+    g = d.x,
+    h = d.y,
+    k = d.width,
+    l = d.height,
+    m = Math.min(k, l);
+  if ("save" == f) a.save();
+  else if ("restore" == f) a.restore();
+  else if ("path" == f) {
+    a.begin();
+    for (c = c.firstChild; null != c;) c.nodeType == mxConstants.NODETYPE_ELEMENT && this.drawNode(a, b, c, d, e), c = c.nextSibling
+  } else if ("close" == f) a.close();
+  else if ("move" == f) a.moveTo(g + Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) * l);
+  else if ("line" == f) a.lineTo(g + Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) *
+    l);
+  else if ("quad" == f) a.quadTo(g + Number(c.getAttribute("x1")) * k, h + Number(c.getAttribute("y1")) * l, g + Number(c.getAttribute("x2")) * k, h + Number(c.getAttribute("y2")) * l);
+  else if ("curve" == f) a.curveTo(g + Number(c.getAttribute("x1")) * k, h + Number(c.getAttribute("y1")) * l, g + Number(c.getAttribute("x2")) * k, h + Number(c.getAttribute("y2")) * l, g + Number(
+    c.getAttribute("x3")) * k, h + Number(c.getAttribute("y3")) * l);
+  else if ("arc" == f) a.arcTo(Number(c.getAttribute("rx")) * k, Number(c.getAttribute("ry")) * l, Number(c.getAttribute("x-axis-rotation")),
+    Number(c.getAttribute("large-arc-flag")), Number(c.getAttribute("sweep-flag")), g + Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) * l);
+  else if ("rect" == f) a.rect(g + Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) * l, Number(c.getAttribute("w")) * k, Number(c.getAttribute("h")) * l);
+  else if ("roundrect" == f) b = c.getAttribute("arcsize"), 0 == b && (b = 100 * mxConstants.RECTANGLE_ROUNDING_FACTOR), m = Number(c.getAttribute("w")) * k, d = Number(c.getAttribute("h")) * l, b =
+    Number(b) / 100, b = Math.min(m * b, d * b), a.roundrect(g +
+      Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) * l, m, d, b, b);
+  else if ("ellipse" == f) a.ellipse(g + Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) * l, Number(c.getAttribute("w")) * k, Number(c.getAttribute("h")) * l);
+  else if ("image" == f) b = this.evaluateAttribute(c, "src", b), a.image(g + Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) * l, Number(c.getAttribute("w")) * k, Number(c.getAttribute(
+    "h")) * l, b, !1, "1" == c.getAttribute("flipH"), "1" == c.getAttribute("flipV"));
+  else if ("text" == f) {
+    m = this.evaluateTextAttribute(c,
+      "str", b);
+    d = "1" == c.getAttribute("vertical") ? -90 : 0;
+    if ("0" == c.getAttribute("align-shape")) {
+      var n = b.rotation,
+        p = 1 == mxUtils.getValue(b.style, mxConstants.STYLE_FLIPH, 0);
+      b = 1 == mxUtils.getValue(b.style, mxConstants.STYLE_FLIPV, 0);
+      d = p && b ? d - n : p || b ? d + n : d - n
+    }
+    d -= c.getAttribute("rotation");
+    a.text(g + Number(c.getAttribute("x")) * k, h + Number(c.getAttribute("y")) * l, 0, 0, m, c.getAttribute("align") || "left", c.getAttribute("valign") || "top", !1, "", !1, !1, d)
+  } else if ("include-shape" == f) n = mxStencilRegistry.getStencil(c.getAttribute("name")),
+    null != n && (g += Number(c.getAttribute("x")) * k, h += Number(c.getAttribute("y")) * l, m = Number(c.getAttribute("w")) * k, d = Number(c.getAttribute("h")) * l, n.drawShape(a, b, g, h, m, d));
+  else if ("fillstroke" == f) a.fillAndStroke();
+  else if ("fill" == f) a.fill();
+  else if ("stroke" == f) a.stroke();
+  else if ("strokewidth" == f) a.setStrokeWidth(Number(c.getAttribute("width")) * m);
+  else if ("dashed" == f) a.setDashed("1" == c.getAttribute("dashed"));
+  else if ("dashpattern" == f) {
+    if (c = c.getAttribute("pattern"), null != c) {
+      c = c.split(" ");
+      k = [];
+      for (l = 0; l <
+        c.length; l++) 0 < c[l].length && k.push(Number(c[l]) * m);
+      c = k.join(" ");
+      a.setDashPattern(c)
+    }
+  } else "strokecolor" == f ? a.setStrokeColor(c.getAttribute("color")) : "linecap" == f ? a.setLineCap(c.getAttribute("cap")) : "linejoin" == f ? a.setLineJoin(c.getAttribute("join")) :
+    "miterlimit" == f ? a.setMiterLimit(Number(c.getAttribute("limit"))) : "fillcolor" == f ? a.setFillColor(c.getAttribute("color")) : "alpha" == f ? a.setAlpha(c.getAttribute("alpha")) :
+    "fontcolor" == f ? a.setFontColor(c.getAttribute("color")) : "fontstyle" == f ? a.setFontStyle(c.getAttribute("style")) :
+    "fontfamily" == f ? a.setFontFamily(c.getAttribute("family")) : "fontsize" == f && a.setFontSize(Number(c.getAttribute("size")) * m);
+  e && ("fillstroke" == f || "fill" == f || "stroke" == f) && a.setShadow(!1)
+};
+
+function mxShape(a) {
+  this.stencil = a;
+  this.strokewidth = 1;
+  this.rotation = 0;
+  this.opacity = 100;
+  this.flipV = this.flipH = !1
+}
+mxShape.prototype.dialect = null;
+mxShape.prototype.scale = 1;
+mxShape.prototype.bounds = null;
+mxShape.prototype.points = null;
+mxShape.prototype.node = null;
+mxShape.prototype.style = null;
+mxShape.prototype.style = null;
+mxShape.prototype.boundingBox = null;
+mxShape.prototype.stencil = null;
+mxShape.prototype.svgStrokeTolerance = 6;
+mxShape.prototype.pointerEvents = !0;
+mxShape.prototype.stencilPointerEvents = !1;
+mxShape.prototype.vmlScale = 1;
+mxShape.prototype.init = function(a) {
+  null == this.node && (this.node = this.create(a), null != a && a.appendChild(this.node))
+};
+mxShape.prototype.isParseVml = function() {
+  return !0
+};
+mxShape.prototype.isHtmlAllowed = function() {
+  return !1
+};
+mxShape.prototype.getSvgScreenOffset = function() {
+  return 1 == mxUtils.mod(Math.max(1, Math.round(this.strokewidth * this.scale)), 2) ? 0.5 : 0
+};
+mxShape.prototype.create = function(a) {
+  var b = null;
+  return b = null != a.ownerSVGElement ? this.createSvg(a) : 8 == document.documentMode || this.dialect == mxConstants.DIALECT_SVG || this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed() ?
+    this.createHtml(a) : this.createVml(a)
+};
+mxShape.prototype.createSvg = function() {
+  return document.createElementNS(mxConstants.NS_SVG, "g")
+};
+mxShape.prototype.createVml = function() {
+  var a = document.createElement(mxClient.VML_PREFIX + ":group");
+  a.style.position = "absolute";
+  return a
+};
+mxShape.prototype.createHtml = function() {
+  var a = document.createElement("div");
+  a.style.position = "absolute";
+  return a
+};
+mxShape.prototype.reconfigure = function() {
+  this.redraw()
+};
+mxShape.prototype.redraw = function() {
+  this.updateBoundsFromPoints();
+  this.checkBounds() ? (this.node.style.visibility = "", "DIV" == this.node.nodeName && this.isHtmlAllowed() ? this.redrawHtmlShape() : this.redrawShape(), this.updateBoundingBox()) : (this.node.style
+    .visibility = "hidden", this.boundingBox = null)
+};
+mxShape.prototype.updateBoundsFromPoints = function() {
+  var a = this.points;
+  if (null != a && 0 < a.length && null != a[0]) {
+    this.bounds = new mxRectangle(Number(a[0].x), Number(a[0].y), 1, 1);
+    for (var b = 1; b < this.points.length; b++) null != a[b] && this.bounds.add(new mxRectangle(Number(a[b].x), Number(a[b].y), 1, 1))
+  }
+};
+mxShape.prototype.checkBounds = function() {
+  return null != this.bounds && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) && !isNaN(this.bounds.width) && !isNaN(this.bounds.height) && 0 < this.bounds.width && 0 < this.bounds.height
+};
+mxShape.prototype.createVmlGroup = function() {
+  var a = document.createElement(mxClient.VML_PREFIX + ":group");
+  a.style.position = "absolute";
+  a.style.width = this.node.style.width;
+  a.style.height = this.node.style.height;
+  return a
+};
+mxShape.prototype.redrawShape = function() {
+  var a = this.createCanvas();
+  for (a.pointerEvents = this.pointerEvents; this.node.hasChildNodes();) this.node.removeChild(this.node.lastChild);
+  this.paint(a);
+  this.node != a.root && this.node.insertAdjacentHTML("beforeend", a.root.outerHTML);
+  "DIV" == this.node.nodeName && 8 == document.documentMode && (this.node.style.filter = "", (null == this.stencil || !this.stencilPointerEvents) && mxUtils.addTransparentBackgroundFilter(this.node));
+  this.destroyCanvas(a)
+};
+mxShape.prototype.createCanvas = function() {
+  var a = null,
+    b = null;
+  if (null != this.node.ownerSVGElement) a = new mxSvgCanvas2D(this.node, !1), a.strokeTolerance = this.svgStrokeTolerance, a.blockImagePointerEvents = mxClient.IS_NS && !mxClient.IS_SF && !
+    mxClient.IS_GC, b = this.getSvgScreenOffset(), 0 != b ? this.node.setAttribute("transform", "translate(" + b + "," + b + ")") : this.node.removeAttribute("transform");
+  else {
+    this.updateVmlContainer();
+    var c = Math.max(1, Math.round(this.bounds.width)),
+      d = Math.max(1, Math.round(this.bounds.height)),
+      b = 8 == document.documentMode && this.isParseVml() ? this.createVmlGroup() : this.node,
+      a = new mxVmlCanvas2D(b, !1);
+    "" != b.tagUrn && (b.coordsize = c * this.vmlScale + "," + d * this.vmlScale, a.scale(this.vmlScale), a.vmlScale = this.vmlScale);
+    b = this.scale;
+    a.translate(-Math.round(this.bounds.x / b), -Math.round(this.bounds.y / b))
+  }
+  return a
+};
+mxShape.prototype.updateVmlContainer = function() {
+  this.node.style.left = Math.round(this.bounds.x) + "px";
+  this.node.style.top = Math.round(this.bounds.y) + "px";
+  var a = Math.max(1, Math.round(this.bounds.width)),
+    b = Math.max(1, Math.round(this.bounds.height));
+  this.node.style.width = a + "px";
+  this.node.style.height = b + "px";
+  this.node.style.overflow = "visible"
+};
+mxShape.prototype.destroyCanvas = function(a) {
+  if (a instanceof mxSvgCanvas2D) {
+    for (var b in a.gradients) {
+      var c = a.gradients[b];
+      c.mxRefCount = (c.mxRefCount || 0) + 1
+    }
+    this.releaseSvgGradients(this.oldGradients);
+    this.oldGradients = a.gradients
+  }
+};
+mxShape.prototype.paint = function(a) {
+  var b = this.scale,
+    c = this.bounds.x / b,
+    d = this.bounds.y / b,
+    e = this.bounds.width / b,
+    f = this.bounds.height / b;
+  if (this.isPaintBoundsInverted()) var g = (e - f) / 2,
+    c = c + g,
+    d = d - g,
+    g = e,
+    e = f,
+    f = g;
+  this.updateTransform(a, c, d, e, f);
+  this.configureCanvas(a, c, d, e, f);
+  if (null != this.stencil) this.paintStencilShape(a, c, d, e, f);
+  else if (a.setStrokeWidth(this.strokewidth), null != this.points) {
+    c = [];
+    for (d = 0; d < this.points.length; d++) c.push(new mxPoint(this.points[d].x / b, this.points[d].y / b));
+    this.paintEdgeShape(a,
+      c)
+  } else this.paintVertexShape(a, c, d, e, f)
+};
+mxShape.prototype.configureCanvas = function(a, b, c, d, e) {
+  var f = null;
+  null != this.style && (f = this.style.dashPattern);
+  a.setAlpha(this.opacity / 100);
+  null != this.isShadow && a.setShadow(this.isShadow);
+  null != this.isDashed && a.setDashed(this.isDashed);
+  null != f && a.setDashPattern(f);
+  null != this.gradient ? (b = this.getGradientBounds(a, b, c, d, e), a.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection)) : a.setFillColor(this.fill);
+  a.setStrokeColor(this.stroke)
+};
+mxShape.prototype.getGradientBounds = function(a, b, c, d, e) {
+  return new mxRectangle(b, c, d, e)
+};
+mxShape.prototype.updateTransform = function(a, b, c, d, e) {
+  a.scale(this.scale);
+  a.rotate(this.getShapeRotation(), this.flipH, this.flipV, b + d / 2, c + e / 2)
+};
+mxShape.prototype.paintStencilShape = function(a, b, c, d, e) {
+  this.stencilPointerEvents && (this.dialect == mxConstants.DIALECT_SVG ? this.addTransparentBackgroundRectangle(this.node, b, c, d, e) : 8 != document.documentMode && this.setTransparentBackgroundImage(
+    this.node));
+  this.stencil.drawShape(a, this, b, c, d, e)
+};
+mxShape.prototype.paintVertexShape = function(a, b, c, d, e) {
+  this.paintBackground(a, b, c, d, e);
+  a.setShadow(!1);
+  this.paintForeground(a, b, c, d, e)
+};
+mxShape.prototype.paintBackground = function(a, b, c, d, e) {};
+mxShape.prototype.paintForeground = function(a, b, c, d, e) {};
+mxShape.prototype.paintEdgeShape = function(a, b) {};
+mxShape.prototype.getArcSize = function(a, b) {
+  var c = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, 100 * mxConstants.RECTANGLE_ROUNDING_FACTOR) / 100;
+  return Math.min(a * c, b * c)
+};
+mxShape.prototype.paintGlassEffect = function(a, b, c, d, e, f) {
+  var g = Math.ceil(this.strokewidth / 2);
+  a.setGradient("#ffffff", "#ffffff", b, c, d, 0.6 * e, "south", 0.9, 0.1);
+  a.begin();
+  f += 2 * g;
+  this.isRounded ? (a.moveTo(b - g + f, c - g), a.quadTo(b - g, c - g, b - g, c - g + f), a.lineTo(b - g, c + 0.4 * e), a.quadTo(b + 0.5 * d, c + 0.7 * e, b + d + g, c + 0.4 * e), a.lineTo(b + d +
+    g, c - g + f), a.quadTo(b + d + g, c - g, b + d + g - f, c - g)) : (a.moveTo(b - g, c - g), a.lineTo(b - g, c + 0.4 * e), a.quadTo(b + 0.5 * d, c + 0.7 * e, b + d + g, c + 0.4 * e), a.lineTo(
+    b + d + g, c - g));
+  a.close();
+  a.fill()
+};
+mxShape.prototype.apply = function(a) {
+  this.state = a;
+  this.style = a.style;
+  if (null != this.style) {
+    this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_FILLCOLOR, this.fill);
+    this.gradient = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);
+    this.gradientDirection = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);
+    this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, this.opacity);
+    this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_STROKECOLOR,
+      this.stroke);
+    this.strokewidth = mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);
+    this.spacing = mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing);
+    this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, this.startSize);
+    this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, this.endSize);
+    this.startArrow = mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, this.startArrow);
+    this.endArrow = mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW,
+      this.endArrow);
+    this.rotation = mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, this.rotation);
+    this.direction = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, this.direction);
+    this.flipH = 1 == mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0);
+    this.flipV = 1 == mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0);
+    null != this.stencil && (this.flipH = 1 == mxUtils.getValue(this.style, "stencilFlipH", 0) || this.flipH, this.flipV = 1 == mxUtils.getValue(this.style, "stencilFlipV", 0) || this.flipV);
+    if ("north" ==
+      this.direction || "south" == this.direction) a = this.flipH, this.flipH = this.flipV, this.flipV = a;
+    this.isShadow = 1 == mxUtils.getValue(this.style, mxConstants.STYLE_SHADOW, this.isShadow);
+    this.isDashed = 1 == mxUtils.getValue(this.style, mxConstants.STYLE_DASHED, this.isDashed);
+    this.isRounded = 1 == mxUtils.getValue(this.style, mxConstants.STYLE_ROUNDED, this.isRounded);
+    this.glass = 1 == mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, this.glass);
+    "none" == this.fill && (this.fill = null);
+    "none" == this.gradient && (this.gradient = null);
+    "none" == this.stroke && (this.stroke = null)
+  }
+};
+mxShape.prototype.setCursor = function(a) {
+  null == a && (a = "");
+  this.cursor = a;
+  null != this.node && (this.node.style.cursor = a)
+};
+mxShape.prototype.getCursor = function() {
+  return this.cursor
+};
+mxShape.prototype.updateBoundingBox = function() {
+  if (null != this.bounds) {
+    var a = this.createBoundingBox();
+    if (null != a) {
+      this.augmentBoundingBox(a);
+      var b = this.getShapeRotation();
+      0 != b && (a = mxUtils.getBoundingBox(a, b));
+      a.x = Math.floor(a.x);
+      a.y = Math.floor(a.y);
+      a.width = Math.ceil(a.width);
+      a.height = Math.ceil(a.height)
+    }
+    this.boundingBox = a
+  }
+};
+mxShape.prototype.createBoundingBox = function() {
+  var a = this.bounds.clone();
+  if (this.isPaintBoundsInverted()) {
+    var b = (a.width - a.height) / 2;
+    a.x += b;
+    a.y -= b;
+    b = a.width;
+    a.width = a.height;
+    a.height = b
+  }
+  return a
+};
+mxShape.prototype.augmentBoundingBox = function(a) {
+  this.isShadow && (a.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale), a.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale));
+  var b = Math.ceil(this.strokewidth * this.scale);
+  a.grow(Math.ceil(b / 2))
+};
+mxShape.prototype.isPaintBoundsInverted = function() {
+  return null == this.stencil && ("north" == this.direction || "south" == this.direction)
+};
+mxShape.prototype.getRotation = function() {
+  return null != this.rotation ? this.rotation : 0
+};
+mxShape.prototype.getTextRotation = function() {
+  var a = this.getRotation();
+  1 != mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) && (a += mxText.prototype.verticalTextRotation);
+  return a
+};
+mxShape.prototype.getShapeRotation = function() {
+  var a = this.getRotation();
+  null != this.direction && ("north" == this.direction ? a += 270 : "west" == this.direction ? a += 180 : "south" == this.direction && (a += 90));
+  return a
+};
+mxShape.prototype.addTransparentBackgroundRectangle = function(a, b, c, d, e) {
+  a = document.createElementNS(mxConstants.NS_SVG, "rect");
+  a.setAttribute("x", b);
+  a.setAttribute("y", c);
+  a.setAttribute("width", d);
+  a.setAttribute("height", e);
+  a.setAttribute("fill", "none");
+  a.setAttribute("stroke", "none");
+  a.setAttribute("pointer-events", "all");
+  this.node.appendChild(a)
+};
+mxShape.prototype.setTransparentBackgroundImage = function(a) {
+  a.style.backgroundImage = "url('" + mxClient.imageBasePath + "/transparent.gif')"
+};
+mxShape.prototype.releaseSvgGradients = function(a) {
+  if (null != a)
+    for (var b in a) {
+      var c = a[b];
+      c.mxRefCount = (c.mxRefCount || 0) - 1;
+      0 == c.mxRefCount && null != c.parentNode && c.parentNode.removeChild(c)
+    }
+};
+mxShape.prototype.destroy = function() {
+  null != this.node && (mxEvent.release(this.node), null != this.node.parentNode && this.node.parentNode.removeChild(this.node), this.node = null);
+  this.releaseSvgGradients(this.oldGradients);
+  this.oldGradients = null
+};
+var mxStencilRegistry = {
+    stencils: [],
+    addStencil: function(a, b) {
+      mxStencilRegistry.stencils[a] = b
+    },
+    getStencil: function(a) {
+      return mxStencilRegistry.stencils[a]
+    }
+  },
+  mxMarker = {
+    markers: [],
+    addMarker: function(a, b) {
+      mxMarker.markers[a] = b
+    },
+    createMarker: function(a, b, c, d, e, f, g, h, k, l) {
+      var m = mxMarker.markers[c];
+      return null != m ? m(a, b, c, d, e, f, g, h, k, l) : null
+    }
+  };
+(function() {
+  function a(a, b, e, f, g, h, k, l, m, n) {
+    b = 1.118 * g * m;
+    l = 1.118 * h * m;
+    g *= k + m;
+    h *= k + m;
+    var p = f.clone();
+    p.x -= b;
+    p.y -= l;
+    k = e != mxConstants.ARROW_CLASSIC ? 1 : 0.75;
+    f.x += -g * k - b;
+    f.y += -h * k - l;
+    return function() {
+      a.begin();
+      a.moveTo(p.x, p.y);
+      a.lineTo(p.x - g - h / 2, p.y - h + g / 2);
+      e == mxConstants.ARROW_CLASSIC && a.lineTo(p.x - 3 * g / 4, p.y - 3 * h / 4);
+      a.lineTo(p.x + h / 2 - g, p.y - h - g / 2);
+      a.close();
+      n ? a.fillAndStroke() : a.stroke()
+    }
+  }
+
+  function b(a, b, e, f, g, h, k, l, m, n) {
+    l = e == mxConstants.ARROW_DIAMOND ? 0.7071 : 0.9862;
+    b = g * m * l;
+    l *= h * m;
+    g *= k + m;
+    h *= k + m;
+    var p = f.clone();
+    p.x -= b;
+    p.y -= l;
+    f.x += -g - b;
+    f.y += -h - l;
+    var q = e == mxConstants.ARROW_DIAMOND ? 2 : 3.4;
+    return function() {
+      a.begin();
+      a.moveTo(p.x, p.y);
+      a.lineTo(p.x - g / 2 - h / q, p.y + g / q - h / 2);
+      a.lineTo(p.x - g, p.y - h);
+      a.lineTo(p.x - g / 2 + h / q, p.y - h / 2 - g / q);
+      a.close();
+      n ? a.fillAndStroke() : a.stroke()
+    }
+  }
+  mxMarker.addMarker("classic", a);
+  mxMarker.addMarker("block", a);
+  mxMarker.addMarker("open", function(a, b, e, f, g, h, k, l, m, n) {
+    b = 1.118 * g * m;
+    e = 1.118 * h * m;
+    g *= k + m;
+    h *= k + m;
+    var p = f.clone();
+    p.x -= b;
+    p.y -= e;
+    f.x += 2 * -b;
+    f.y += 2 * -e;
+    return function() {
+      a.begin();
+      a.moveTo(p.x - g -
+        h / 2, p.y - h + g / 2);
+      a.lineTo(p.x, p.y);
+      a.lineTo(p.x + h / 2 - g, p.y - h - g / 2);
+      a.stroke()
+    }
+  });
+  mxMarker.addMarker("oval", function(a, b, e, f, g, h, k, l, m, n) {
+    var p = k / 2,
+      q = f.clone();
+    f.x -= g * p;
+    f.y -= h * p;
+    return function() {
+      a.ellipse(q.x - p, q.y - p, k, k);
+      n ? a.fillAndStroke() : a.stroke()
+    }
+  });
+  mxMarker.addMarker("diamond", b);
+  mxMarker.addMarker("diamondThin", b)
+})();
+
+function mxActor(a, b, c, d) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxActor, mxShape);
+mxActor.prototype.paintVertexShape = function(a, b, c, d, e) {
+  a.translate(b, c);
+  a.begin();
+  this.redrawPath(a, b, c, d, e);
+  a.fillAndStroke()
+};
+mxActor.prototype.redrawPath = function(a, b, c, d, e) {
+  b = d / 3;
+  a.moveTo(0, e);
+  a.curveTo(0, 3 * e / 5, 0, 2 * e / 5, d / 2, 2 * e / 5);
+  a.curveTo(d / 2 - b, 2 * e / 5, d / 2 - b, 0, d / 2, 0);
+  a.curveTo(d / 2 + b, 0, d / 2 + b, 2 * e / 5, d / 2, 2 * e / 5);
+  a.curveTo(d, 2 * e / 5, d, 3 * e / 5, d, e);
+  a.close()
+};
+
+function mxCloud(a, b, c, d) {
+  mxActor.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxCloud, mxActor);
+mxCloud.prototype.redrawPath = function(a, b, c, d, e) {
+  a.moveTo(0.25 * d, 0.25 * e);
+  a.curveTo(0.05 * d, 0.25 * e, 0, 0.5 * e, 0.16 * d, 0.55 * e);
+  a.curveTo(0, 0.66 * e, 0.18 * d, 0.9 * e, 0.31 * d, 0.8 * e);
+  a.curveTo(0.4 * d, e, 0.7 * d, e, 0.8 * d, 0.8 * e);
+  a.curveTo(d, 0.8 * e, d, 0.6 * e, 0.875 * d, 0.5 * e);
+  a.curveTo(d, 0.3 * e, 0.8 * d, 0.1 * e, 0.625 * d, 0.2 * e);
+  a.curveTo(0.5 * d, 0.05 * e, 0.3 * d, 0.05 * e, 0.25 * d, 0.25 * e);
+  a.close()
+};
+
+function mxRectangleShape(a, b, c, d) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxRectangleShape, mxShape);
+mxRectangleShape.prototype.isHtmlAllowed = function() {
+  return !this.isRounded && !this.glass && 0 == this.rotation
+};
+mxRectangleShape.prototype.paintBackground = function(a, b, c, d, e) {
+  if (this.isRounded) {
+    var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, 100 * mxConstants.RECTANGLE_ROUNDING_FACTOR) / 100,
+      f = Math.min(d * f, e * f);
+    a.roundrect(b, c, d, e, f, f)
+  } else a.rect(b, c, d, e);
+  a.fillAndStroke()
+};
+mxRectangleShape.prototype.paintForeground = function(a, b, c, d, e) {
+  this.glass && this.paintGlassEffect(a, b, c, d, e, this.getArcSize(d + this.strokewidth, e + this.strokewidth))
+};
+mxRectangleShape.prototype.redrawHtmlShape = function() {
+  this.updateHtmlBounds(this.node);
+  this.updateHtmlFilters(this.node);
+  this.updateHtmlColors(this.node)
+};
+mxRectangleShape.prototype.updateHtmlBounds = function(a) {
+  var b = 9 <= document.documentMode ? 0 : Math.ceil(this.strokewidth * this.scale);
+  a.style.borderWidth = Math.max(1, b) + "px";
+  a.style.overflow = "hidden";
+  a.style.left = Math.round(this.bounds.x - b / 2) + "px";
+  a.style.top = Math.round(this.bounds.y - b / 2) + "px";
+  "CSS1Compat" == document.compatMode && (b = -b);
+  a.style.width = Math.round(Math.max(0, this.bounds.width + b)) + "px";
+  a.style.height = Math.round(Math.max(0, this.bounds.height + b)) + "px"
+};
+mxRectangleShape.prototype.updateHtmlColors = function(a) {
+  var b = this.stroke;
+  null != b && b != mxConstants.NONE ? (a.style.borderColor = b, this.isDashed ? a.style.borderStyle = "dashed" : 0 < this.strokewidth && (a.style.borderStyle = "solid"), a.style.borderWidth = Math
+    .max(1, Math.ceil(this.strokewidth * this.scale)) + "px") : a.style.borderWidth = "0px";
+  b = this.fill;
+  null != b && b != mxConstants.NONE ? (a.style.backgroundColor = b, a.style.backgroundImage = "none") : this.pointerEvents ? a.style.backgroundColor = "transparent" : 8 == document.documentMode ?
+    mxUtils.addTransparentBackgroundFilter(a) : this.setTransparentBackgroundImage(a)
+};
+mxRectangleShape.prototype.updateHtmlFilters = function(a) {
+  var b = "";
+  100 > this.opacity && (b += "alpha(opacity\x3d" + this.opacity + ")");
+  this.isShadow && (b += "progid:DXImageTransform.Microsoft.dropShadow (OffX\x3d'" + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + "', OffY\x3d'" + Math.round(mxConstants.SHADOW_OFFSET_Y *
+    this.scale) + "', Color\x3d'" + mxConstants.SHADOWCOLOR + "')");
+  if (this.gradient) {
+    var c = this.fill,
+      d = this.gradient,
+      e = "0",
+      f = {
+        east: 0,
+        south: 1,
+        west: 2,
+        north: 3
+      },
+      g = null != this.direction ? f[this.direction] : 0;
+    null !=
+      this.gradientDirection && (g = mxUtils.mod(g + f[this.gradientDirection] - 1, 4));
+    1 == g ? (e = "1", f = c, c = d, d = f) : 2 == g ? (f = c, c = d, d = f) : 3 == g && (e = "1");
+    b += "progid:DXImageTransform.Microsoft.gradient(startColorStr\x3d'" + c + "', endColorStr\x3d'" + d + "', gradientType\x3d'" + e + "')"
+  }
+  a.style.filter = b
+};
+
+function mxEllipse(a, b, c, d) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxEllipse, mxShape);
+mxEllipse.prototype.paintVertexShape = function(a, b, c, d, e) {
+  a.ellipse(b, c, d, e);
+  a.fillAndStroke()
+};
+
+function mxDoubleEllipse(a, b, c, d) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxDoubleEllipse, mxShape);
+mxDoubleEllipse.prototype.vmlScale = 10;
+mxDoubleEllipse.prototype.paintBackground = function(a, b, c, d, e) {
+  a.ellipse(b, c, d, e);
+  a.fillAndStroke()
+};
+mxDoubleEllipse.prototype.paintForeground = function(a, b, c, d, e) {
+  var f = Math.min(4, Math.min(d / 5, e / 5));
+  d -= 2 * f;
+  e -= 2 * f;
+  0 < d && 0 < e && a.ellipse(b + f, c + f, d, e);
+  a.stroke()
+};
+
+function mxRhombus(a, b, c, d) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxRhombus, mxShape);
+mxRhombus.prototype.paintVertexShape = function(a, b, c, d, e) {
+  var f = d / 2,
+    g = e / 2;
+  a.begin();
+  a.moveTo(b + f, c);
+  a.lineTo(b + d, c + g);
+  a.lineTo(b + f, c + e);
+  a.lineTo(b, c + g);
+  a.close();
+  a.fillAndStroke()
+};
+
+function mxPolyline(a, b, c) {
+  mxShape.call(this);
+  this.points = a;
+  this.stroke = b;
+  this.strokewidth = null != c ? c : 1
+}
+mxUtils.extend(mxPolyline, mxShape);
+mxPolyline.prototype.getRotation = function() {
+  return 0
+};
+mxPolyline.prototype.paintEdgeShape = function(a, b) {
+  this.paintLine(a, b, this.isRounded)
+};
+mxPolyline.prototype.paintLine = function(a, b, c) {
+  var d = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2,
+    e = b[0],
+    f = b[b.length - 1];
+  a.begin();
+  a.moveTo(e.x, e.y);
+  for (var g = 1; g < b.length - 1; g++) {
+    var h = b[g],
+      k = e.x - h.x,
+      e = e.y - h.y;
+    if (c && g < b.length - 1 && (0 != k || 0 != e)) {
+      var l = Math.sqrt(k * k + e * e),
+        k = k * Math.min(d, l / 2) / l,
+        e = e * Math.min(d, l / 2) / l;
+      a.lineTo(h.x + k, h.y + e);
+      e = b[g + 1];
+      k = e.x - h.x;
+      e = e.y - h.y;
+      l = Math.max(1, Math.sqrt(k * k + e * e));
+      k = k * Math.min(d, l / 2) / l;
+      e = e * Math.min(d, l / 2) / l;
+      k = h.x + k;
+      e = h.y + e;
+      a.quadTo(h.x,
+        h.y, k, e);
+      h = new mxPoint(k, e)
+    } else a.lineTo(h.x, h.y);
+    e = h
+  }
+  a.lineTo(f.x, f.y);
+  a.stroke()
+};
+
+function mxArrow(a, b, c, d, e, f, g) {
+  mxShape.call(this);
+  this.points = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1;
+  this.arrowWidth = null != e ? e : mxConstants.ARROW_WIDTH;
+  this.spacing = null != f ? f : mxConstants.ARROW_SPACING;
+  this.endSize = null != g ? g : mxConstants.ARROW_SIZE
+}
+mxUtils.extend(mxArrow, mxShape);
+mxArrow.prototype.paintEdgeShape = function(a, b) {
+  var c = mxConstants.ARROW_SPACING,
+    d = mxConstants.ARROW_WIDTH,
+    e = mxConstants.ARROW_SIZE,
+    f = b[0],
+    g = b[b.length - 1],
+    h = g.x - f.x,
+    k = g.y - f.y,
+    l = Math.sqrt(h * h + k * k),
+    m = l - 2 * c - e,
+    h = h / l,
+    k = k / l,
+    l = d * k / 3,
+    d = -d * h / 3,
+    e = f.x - l / 2 + c * h,
+    f = f.y - d / 2 + c * k,
+    n = e + l,
+    p = f + d,
+    q = n + m * h,
+    m = p + m * k,
+    s = q + l,
+    r = m + d,
+    t = s - 3 * l,
+    v = r - 3 * d;
+  a.begin();
+  a.moveTo(e, f);
+  a.lineTo(n, p);
+  a.lineTo(q, m);
+  a.lineTo(s, r);
+  a.lineTo(g.x - c * h, g.y - c * k);
+  a.lineTo(t, v);
+  a.lineTo(t + l, v + d);
+  a.close();
+  a.fillAndStroke()
+};
+
+function mxText(a, b, c, d, e, f, g, h, k, l, m, n, p, q, s, r, t, v, w, u) {
+  mxShape.call(this);
+  this.value = a;
+  this.bounds = b;
+  this.color = null != e ? e : "black";
+  this.align = null != c ? c : "";
+  this.valign = null != d ? d : "";
+  this.family = null != f ? f : mxConstants.DEFAULT_FONTFAMILY;
+  this.size = null != g ? g : mxConstants.DEFAULT_FONTSIZE;
+  this.fontStyle = null != h ? h : mxConstants.DEFAULT_FONTSTYLE;
+  this.spacing = parseInt(k || 2);
+  this.spacingTop = this.spacing + parseInt(l || 0);
+  this.spacingRight = this.spacing + parseInt(m || 0);
+  this.spacingBottom = this.spacing + parseInt(n || 0);
+  this.spacingLeft = this.spacing + parseInt(p || 0);
+  this.horizontal = null != q ? q : !0;
+  this.background = s;
+  this.border = r;
+  this.wrap = null != t ? t : !1;
+  this.clipped = null != v ? v : !1;
+  this.overflow = null != w ? w : "visible";
+  this.labelPadding = null != u ? u : 0;
+  this.rotation = 0
+}
+mxUtils.extend(mxText, mxShape);
+mxText.prototype.baseSpacingTop = 0;
+mxText.prototype.baseSpacingBottom = 0;
+mxText.prototype.baseSpacingLeft = 0;
+mxText.prototype.baseSpacingRight = 0;
+mxText.prototype.replaceLinefeeds = !0;
+mxText.prototype.verticalTextRotation = -90;
+mxText.prototype.ignoreClippedStringSize = !0;
+mxText.prototype.ignoreStringSize = !1;
+mxText.prototype.isParseVml = function() {
+  return !1
+};
+mxText.prototype.isHtmlAllowed = function() {
+  return 8 != document.documentMode
+};
+mxText.prototype.getSvgScreenOffset = function() {
+  return 0
+};
+mxText.prototype.checkBounds = function() {
+  return null != this.bounds && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) && !isNaN(this.bounds.width) && !isNaN(this.bounds.height)
+};
+mxText.prototype.updateBoundingBox = function() {
+  var a = this.node;
+  8 == document.documentMode && null != a.firstChild && (a = a.firstChild, null != a.firstChild && (a = a.firstChild));
+  this.boundingBox = this.bounds.clone();
+  var b = this.getTextRotation();
+  if (!this.ignoreStringSize && null != a && "fill" != this.overflow && (!this.clipped || !this.ignoreClippedStringSize)) {
+    var c = null,
+      d = null;
+    if (null != a.ownerSVGElement)
+      if (null != a.firstChild && null != a.firstChild.firstChild && "foreignObject" == a.firstChild.firstChild.nodeName) a = a.firstChild.firstChild,
+        c = this.wrap ? this.bounds.width : parseInt(a.getAttribute("width")) * this.scale, d = parseInt(a.getAttribute("height")) * this.scale;
+      else {
+        b = a.getBBox();
+        if (0 == b.width && 0 == b.height) return;
+        this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
+        b = 0
+      } else d = this.state.view.textDiv, null != this.offsetWidth && null != this.offsetHeight ? (c = this.wrap ? this.bounds.width : this.offsetWidth * this.scale, d = this.offsetHeight * this.scale) :
+      null != d ? (this.updateFont(d), this.updateSize(d), mxUtils.isNode(this.value) ? d.innerHTML = this.value.outerHTML :
+        (c = this.replaceLinefeeds ? this.value.replace(/\n/g, "\x3cbr/\x3e") : this.value, d.innerHTML = c), c = this.wrap ? this.bounds.width : d.offsetWidth * this.scale, d = d.offsetHeight *
+        this.scale) : (c = this.wrap ? this.bounds.width : a.offsetWidth * this.scale, d = a.offsetHeight * this.scale);
+    null != c && null != d && (this.boundingBox = new mxRectangle(this.bounds.x + this.margin.x * c, this.bounds.y + this.margin.y * d, c, d))
+  } else this.boundingBox.x += this.margin.x * this.boundingBox.width, this.boundingBox.y += this.margin.y * this.boundingBox.height;
+  null !=
+    this.boundingBox && (0 != b && (b = mxUtils.getBoundingBox(this.boundingBox, b), this.boundingBox.x = b.x, this.boundingBox.y = b.y, mxClient.IS_QUIRKS || (this.boundingBox.width = b.width,
+      this.boundingBox.height = b.height)), this.boundingBox.x = Math.floor(this.boundingBox.x), this.boundingBox.y = Math.floor(this.boundingBox.y), this.boundingBox.width = Math.ceil(this.boundingBox
+      .width), this.boundingBox.height = Math.ceil(this.boundingBox.height))
+};
+mxText.prototype.getShapeRotation = function() {
+  return 0
+};
+mxText.prototype.getTextRotation = function() {
+  return null != this.state && null != this.state.shape ? this.state.shape.getTextRotation() : 0
+};
+mxText.prototype.isPaintBoundsInverted = function() {
+  return !this.horizontal && null != this.state && this.state.view.graph.model.isVertex(this.state.cell)
+};
+mxText.prototype.configureCanvas = function(a, b, c, d, e) {
+  mxShape.prototype.configureCanvas.apply(this, arguments);
+  a.setFontColor(this.color);
+  a.setFontBackgroundColor(this.background);
+  a.setFontBorderColor(this.border);
+  a.setFontFamily(this.family);
+  a.setFontSize(this.size);
+  a.setFontStyle(this.fontStyle)
+};
+mxText.prototype.updateVmlContainer = function() {
+  this.node.style.left = Math.round(this.bounds.x) + "px";
+  this.node.style.top = Math.round(this.bounds.y) + "px";
+  this.node.style.width = "1px";
+  this.node.style.height = "1px";
+  this.node.style.overflow = "visible"
+};
+mxText.prototype.paint = function(a) {
+  var b = this.scale,
+    c = this.bounds.x / b,
+    d = this.bounds.y / b,
+    e = this.bounds.width / b,
+    b = this.bounds.height / b;
+  this.updateTransform(a, c, d, e, b);
+  this.configureCanvas(a, c, d, e, b);
+  var f = mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML,
+    g = f || a instanceof mxVmlCanvas2D ? "html" : "",
+    h = this.value;
+  !f && "html" == g && (h = mxUtils.htmlEntities(h, !1));
+  h = !mxUtils.isNode(this.value) && this.replaceLinefeeds && "html" == g ? h.replace(/\n/g, "\x3cbr/\x3e") : h;
+  a.text(c, d, e, b, h, this.align,
+    this.valign, this.wrap, g, "fill" == this.overflow, this.clipped, this.getTextRotation())
+};
+mxText.prototype.redrawHtmlShape = function() {
+  var a = this.node.style;
+  a.opacity = 1 > this.opacity ? this.opacity : "";
+  a.overflow = "";
+  a.width = "";
+  a.height = "";
+  this.updateFont(this.node);
+  this.updateSize(this.node);
+  this.updateValue();
+  this.offsetHeight = this.offsetWidth = null;
+  null != mxClient.CSS_PREFIX ? this.updateHtmlTransform() : this.updateHtmlFilter()
+};
+mxText.prototype.updateHtmlTransform = function() {
+  var a = this.getTextRotation(),
+    b = this.node.style,
+    c = this.margin.x,
+    d = this.margin.y;
+  0 != a ? (b[mxClient.CSS_PREFIX + "TransformOrigin"] = 100 * -c + "% " + 100 * -d + "%", b[mxClient.CSS_PREFIX + "Transform"] = "translate(" + 100 * c + "%," + 100 * d + "%)scale(" + this.scale +
+    ") rotate(" + a + "deg)") : (b[mxClient.CSS_PREFIX + "TransformOrigin"] = "0% 0%", b[mxClient.CSS_PREFIX + "Transform"] = "scale(" + this.scale + ")translate(" + 100 * c + "%," + 100 * d +
+    "%)");
+  b.left = Math.round(this.bounds.x) + "px";
+  b.top = Math.round(this.bounds.y) +
+    "px"
+};
+mxText.prototype.updateHtmlFilter = function() {
+  var a = this.node.style,
+    b = this.margin.x,
+    c = this.margin.y,
+    d = this.scale;
+  a.filter = "";
+  var e = 0,
+    f = 0,
+    g = null != this.state ? this.state.view.textDiv : null;
+  if (null != g) {
+    g.style.overflow = "";
+    g.style.height = "";
+    g.style.width = "";
+    this.updateFont(g);
+    this.updateSize(g);
+    if (mxUtils.isNode(this.value)) g.innerHTML = this.value.outerHTML;
+    else {
+      var h = this.value;
+      this.dialect != mxConstants.DIALECT_STRICTHTML && (h = mxUtils.htmlEntities(h, !1));
+      h = this.replaceLinefeeds ? h.replace(/\n/g, "\x3cbr/\x3e") : h;
+      g.innerHTML = h
+    }
+    e = g.offsetWidth + 2;
+    f = g.offsetHeight + 2
+  } else e = this.node.offsetWidth, f = this.node.offsetHeight + 1;
+  this.offsetWidth = e;
+  this.offsetHeight = f;
+  g = this.bounds.width / d;
+  h = this.bounds.height / d;
+  mxClient.IS_QUIRKS && (this.clipped || "fill" == this.overflow) && 0 < h ? (h = Math.min(h, f), a.height = Math.round(h + 1) + "px") : h = f;
+  "fill" != this.overflow && (mxClient.IS_QUIRKS && (this.clipped || this.wrap) && 0 < g ? (g = Math.min(g, e), a.width = Math.round(g) + "px") : g = e);
+  var h = h * d,
+    g = g * d,
+    e = this.getTextRotation() * (Math.PI / 180),
+    f = parseFloat(parseFloat(Math.cos(e)).toFixed(8)),
+    k = parseFloat(parseFloat(Math.sin(-e)).toFixed(8)),
+    e = e % (2 * Math.PI);
+  0 > e && (e += 2 * Math.PI);
+  e %= Math.PI;
+  e > Math.PI / 2 && (e = Math.PI - e);
+  var l = Math.cos(e),
+    m = Math.sin(-e),
+    b = g * -(b + 0.5),
+    c = h * -(c + 0.5),
+    n = (h - h * l + g * m) / 2 + k * b - f * c;
+  0 != e && (a.filter = "progid:DXImageTransform.Microsoft.Matrix(M11\x3d" + f + ", M12\x3d" + k + ", M21\x3d" + -k + ", M22\x3d" + f + ", sizingMethod\x3d'auto expand')");
+  a.zoom = d;
+  a.left = Math.round(this.bounds.x + ((g - g * l + h * m) / 2 - f * b - k * c) - g / 2) + "px";
+  a.top = Math.round(this.bounds.y + n - h / 2) + "px"
+};
+mxText.prototype.updateValue = function() {
+  if (mxUtils.isNode(this.value)) this.node.innerHTML = "", this.node.appendChild(this.value);
+  else {
+    var a = this.value;
+    this.dialect != mxConstants.DIALECT_STRICTHTML && (a = mxUtils.htmlEntities(a, !1));
+    var a = this.replaceLinefeeds ? a.replace(/\n/g, "\x3cbr/\x3e") : a,
+      b = null != this.background && this.background != mxConstants.NONE ? this.background : null,
+      c = null != this.border && this.border != mxConstants.NONE ? this.border : null;
+    if (null != b || null != c)
+      if ("fill" == this.overflow) null != b && (this.node.style.backgroundColor =
+        b), null != c && (this.node.style.border = "1px solid " + c);
+      else {
+        var d = "";
+        null != b && (d += "background-color:" + b + ";");
+        null != c && (d += "border:1px solid " + c + ";");
+        a = '\x3cdiv style\x3d"zoom:1;' + d + "display:inline-block;_display:inline;padding-bottom:1px;padding-right:1px;line-height:" + this.node.style.lineHeight + '"\x3e' + a + "\x3c/div\x3e";
+        this.node.style.lineHeight = ""
+      }
+    this.node.innerHTML = a
+  }
+};
+mxText.prototype.updateFont = function(a) {
+  a = a.style;
+  a.lineHeight = Math.round(this.size * mxConstants.LINE_HEIGHT) + "px";
+  a.fontSize = Math.round(this.size) + "px";
+  a.fontFamily = this.family;
+  a.verticalAlign = "top";
+  a.color = this.color;
+  a.fontWeight = (this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD ? "bold" : "";
+  a.fontStyle = (this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC ? "italic" : "";
+  a.fontDecoration = (this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE ? "underline" : "";
+  a.textAlign =
+    this.align == mxConstants.ALIGN_CENTER ? "center" : this.align == mxConstants.ALIGN_RIGHT ? "right" : "left"
+};
+mxText.prototype.updateSize = function(a) {
+  var b = Math.round(this.bounds.width / this.scale),
+    c = Math.round(this.bounds.height / this.scale);
+  a = a.style;
+  this.clipped ? (a.overflow = "hidden", 0 < c && (a.maxHeight = c + "px"), 0 < b && (a.width = b + "px")) : "fill" == this.overflow && (a.width = b + "px", 0 < c && (a.maxHeight = c + "px"));
+  this.wrap ? (!this.clipped && 0 < b && (a.width = b + "px"), a.whiteSpace = "normal") : a.whiteSpace = "nowrap"
+};
+mxText.prototype.updateMargin = function() {
+  this.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign)
+};
+mxText.prototype.getSpacing = function() {
+  var a = 0,
+    b = 0,
+    a = this.align == mxConstants.ALIGN_CENTER ? (this.spacingLeft - this.spacingRight) / 2 : this.align == mxConstants.ALIGN_RIGHT ? -this.spacingRight - this.baseSpacingRight : this.spacingLeft +
+    this.baseSpacingLeft,
+    b = this.valign == mxConstants.ALIGN_MIDDLE ? (this.spacingTop - this.spacingBottom) / 2 : this.valign == mxConstants.ALIGN_BOTTOM ? -this.spacingBottom - this.baseSpacingBottom : this.spacingTop +
+    this.baseSpacingTop;
+  return new mxPoint(a, b)
+};
+
+function mxTriangle() {
+  mxActor.call(this)
+}
+mxUtils.extend(mxTriangle, mxActor);
+mxTriangle.prototype.redrawPath = function(a, b, c, d, e) {
+  a.moveTo(0, 0);
+  a.lineTo(d, 0.5 * e);
+  a.lineTo(0, e);
+  a.close()
+};
+
+function mxHexagon() {
+  mxActor.call(this)
+}
+mxUtils.extend(mxHexagon, mxActor);
+mxHexagon.prototype.redrawPath = function(a, b, c, d, e) {
+  a.moveTo(0.25 * d, 0);
+  a.lineTo(0.75 * d, 0);
+  a.lineTo(d, 0.5 * e);
+  a.lineTo(0.75 * d, e);
+  a.lineTo(0.25 * d, e);
+  a.lineTo(0, 0.5 * e);
+  a.close()
+};
+
+function mxLine(a, b, c) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.stroke = b;
+  this.strokewidth = null != c ? c : 1
+}
+mxUtils.extend(mxLine, mxShape);
+mxLine.prototype.paintVertexShape = function(a, b, c, d, e) {
+  c += e / 2;
+  a.begin();
+  a.moveTo(b, c);
+  a.lineTo(b + d, c);
+  a.stroke()
+};
+
+function mxImageShape(a, b, c, d, e) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.image = b;
+  this.fill = c;
+  this.stroke = d;
+  this.strokewidth = null != e ? e : 1;
+  this.shadow = !1
+}
+mxUtils.extend(mxImageShape, mxRectangleShape);
+mxImageShape.prototype.preserveImageAspect = !0;
+mxImageShape.prototype.getSvgScreenOffset = function() {
+  return !mxClient.IS_IE ? 0.5 : 0
+};
+mxImageShape.prototype.apply = function(a) {
+  mxShape.prototype.apply.apply(this, arguments);
+  this.gradient = this.stroke = this.fill = null;
+  null != this.style && (this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND), this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER), this.preserveImageAspect =
+    1 == mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1), this.flipH = this.flipH || 1 == mxUtils.getValue(this.style, "imageFlipH", 0), this.flipV = this.flipV || 1 == mxUtils.getValue(
+      this.style,
+      "imageFlipV", 0))
+};
+mxImageShape.prototype.isHtmlAllowed = function() {
+  return !this.preserveImageAspect
+};
+mxImageShape.prototype.createHtml = function() {
+  var a = document.createElement("div");
+  a.style.position = "absolute";
+  return a
+};
+mxImageShape.prototype.paintVertexShape = function(a, b, c, d, e) {
+  if (null != this.image) {
+    var f = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, null),
+      g = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);
+    if (null != f || null != g) a.setFillColor(f), a.setStrokeColor(g), a.rect(b, c, d, e), a.fillAndStroke();
+    a.image(b, c, d, e, this.image, this.preserveImageAspect, !1, !1)
+  } else mxRectangleShape.prototype.paintBackground.apply(this, arguments)
+};
+mxImageShape.prototype.redrawHtmlShape = function() {
+  this.node.style.left = Math.round(this.bounds.x) + "px";
+  this.node.style.top = Math.round(this.bounds.y) + "px";
+  this.node.style.width = Math.max(0, Math.round(this.bounds.width)) + "px";
+  this.node.style.height = Math.max(0, Math.round(this.bounds.height)) + "px";
+  this.node.style.backgroundColor = this.fill || "";
+  this.node.style.borderColor = this.stroke || "";
+  this.node.innerHTML = "";
+  if (null != this.image) {
+    var a = document.createElement(mxClient.IS_IE6 || null == mxClient.CSS_PREFIX && 0 !=
+      this.rotation ? mxClient.VML_PREFIX + ":image" : "img");
+    a.style.position = "absolute";
+    a.src = this.image;
+    var b = 100 > this.opacity ? "alpha(opacity\x3d" + this.opacity + ")" : "";
+    this.node.style.filter = b;
+    this.flipH && this.flipV ? b += "progid:DXImageTransform.Microsoft.BasicImage(rotation\x3d2)" : this.flipH ? b += "progid:DXImageTransform.Microsoft.BasicImage(mirror\x3d1)" : this.flipV && (b +=
+      "progid:DXImageTransform.Microsoft.BasicImage(rotation\x3d2, mirror\x3d1)");
+    a.style.filter != b && (a.style.filter = b);
+    "image" == a.nodeName ? a.style.rotation =
+      this.rotation : a.style[mxClient.CSS_PREFIX + "Transform"] = "rotate(" + this.rotation + "deg)";
+    a.style.width = this.node.style.width;
+    a.style.height = this.node.style.height;
+    this.node.style.backgroundImage = "";
+    this.node.appendChild(a)
+  } else this.setTransparentBackgroundImage(this.node)
+};
+
+function mxLabel(a, b, c, d) {
+  mxRectangleShape.call(this, a, b, c, d)
+}
+mxUtils.extend(mxLabel, mxRectangleShape);
+mxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;
+mxLabel.prototype.spacing = 2;
+mxLabel.prototype.indicatorSize = 10;
+mxLabel.prototype.indicatorSpacing = 2;
+mxLabel.prototype.init = function(a) {
+  mxShape.prototype.init.apply(this, arguments);
+  null != this.indicatorColor && null != this.indicatorShape && (this.indicator = new this.indicatorShape, this.indicator.dialect = this.dialect, this.indicator.bounds = this.bounds, this.indicator
+    .fill = this.indicatorColor, this.indicator.stroke = this.indicatorColor, this.indicator.gradient = this.indicatorGradientColor, this.indicator.direction = this.indicatorDirection, this.indicator
+    .init(this.node))
+};
+mxLabel.prototype.redraw = function() {
+  null != this.indicator && (this.indicator.fill = this.indicatorColor, this.indicator.stroke = this.indicatorColor, this.indicator.gradient = this.indicatorGradientColor, this.indicator.direction =
+    this.indicatorDirection);
+  mxShape.prototype.redraw.apply(this, arguments)
+};
+mxLabel.prototype.isHtmlAllowed = function() {
+  return mxRectangleShape.prototype.isHtmlAllowed.apply(this, arguments) && null == this.indicatorColor && null == this.indicatorShape
+};
+mxLabel.prototype.paintForeground = function(a, b, c, d, e) {
+  this.paintImage(a, b, c, d, e);
+  this.paintIndicator(a, b, c, d, e);
+  mxRectangleShape.prototype.paintForeground.apply(this, arguments)
+};
+mxLabel.prototype.paintImage = function(a, b, c, d, e) {
+  null != this.image && (b = this.getImageBounds(b, c, d, e), a.image(b.x, b.y, b.width, b.height, this.image, !1, !1, !1))
+};
+mxLabel.prototype.getImageBounds = function(a, b, c, d) {
+  var e = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT),
+    f = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE),
+    g = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE),
+    h = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE),
+    k = mxUtils.getNumber(this.style, mxConstants.STYLE_SPACING, this.spacing) + 5;
+  a = e == mxConstants.ALIGN_CENTER ?
+    a + (c - g) / 2 : e == mxConstants.ALIGN_RIGHT ? a + (c - g - k) : a + k;
+  b = f == mxConstants.ALIGN_TOP ? b + k : f == mxConstants.ALIGN_BOTTOM ? b + (d - h - k) : b + (d - h) / 2;
+  return new mxRectangle(a, b, g, h)
+};
+mxLabel.prototype.paintIndicator = function(a, b, c, d, e) {
+  null != this.indicator ? (this.indicator.bounds = this.getIndicatorBounds(b, c, d, e), this.indicator.paint(a)) : null != this.indicatorImage && (b = this.getIndicatorBounds(b, c, d, e), a.image(
+    b.x, b.y, b.width, b.height, this.indicatorImage, !1, !1, !1))
+};
+mxLabel.prototype.getIndicatorBounds = function(a, b, c, d) {
+  var e = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT),
+    f = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE),
+    g = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_WIDTH, this.indicatorSize),
+    h = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_HEIGHT, this.indicatorSize),
+    k = this.spacing + 5;
+  a = e == mxConstants.ALIGN_RIGHT ? a + (c - g - k) : e == mxConstants.ALIGN_CENTER ? a + (c - g) /
+    2 : a + k;
+  b = f == mxConstants.ALIGN_BOTTOM ? b + (d - h - k) : f == mxConstants.ALIGN_TOP ? b + k : b + (d - h) / 2;
+  return new mxRectangle(a, b, g, h)
+};
+mxLabel.prototype.redrawHtmlShape = function() {
+  for (mxRectangleShape.prototype.redrawHtmlShape.apply(this, arguments); this.node.hasChildNodes();) this.node.removeChild(this.node.lastChild);
+  if (null != this.image) {
+    var a = document.createElement("img");
+    a.style.position = "relative";
+    a.setAttribute("border", "0");
+    var b = this.getImageBounds(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);
+    b.x -= this.bounds.x;
+    b.y -= this.bounds.y;
+    a.style.left = Math.round(b.x) + "px";
+    a.style.top = Math.round(b.y) + "px";
+    a.style.width =
+      Math.round(b.width) + "px";
+    a.style.height = Math.round(b.height) + "px";
+    a.src = this.image;
+    this.node.appendChild(a)
+  }
+};
+
+function mxCylinder(a, b, c, d) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxCylinder, mxShape);
+mxCylinder.prototype.maxHeight = 40;
+mxCylinder.prototype.svgStrokeTolerance = 0;
+mxCylinder.prototype.paintVertexShape = function(a, b, c, d, e) {
+  a.translate(b, c);
+  a.begin();
+  this.redrawPath(a, b, c, d, e, !1);
+  a.fillAndStroke();
+  a.setShadow(!1);
+  a.begin();
+  this.redrawPath(a, b, c, d, e, !0);
+  a.stroke()
+};
+mxCylinder.prototype.redrawPath = function(a, b, c, d, e, f) {
+  b = Math.min(this.maxHeight, Math.round(e / 5));
+  if (f && null != this.fill || !f && null == this.fill) a.moveTo(0, b), a.curveTo(0, 2 * b, d, 2 * b, d, b), f || (a.stroke(), a.begin());
+  f || (a.moveTo(0, b), a.curveTo(0, -b / 3, d, -b / 3, d, b), a.lineTo(d, e - b), a.curveTo(d, e + b / 3, 0, e + b / 3, 0, e - b), a.close())
+};
+
+function mxConnector(a, b, c) {
+  mxPolyline.call(this, a, b, c)
+}
+mxUtils.extend(mxConnector, mxPolyline);
+mxConnector.prototype.paintEdgeShape = function(a, b) {
+  var c = this.createMarker(a, b, !0),
+    d = this.createMarker(a, b, !1);
+  null == this.style || 1 != this.style[mxConstants.STYLE_CURVED] ? this.paintLine(a, b, this.isRounded) : this.paintCurvedLine(a, b);
+  a.setFillColor(this.stroke);
+  a.setShadow(!1);
+  a.setDashed(!1);
+  null != c && c();
+  null != d && d()
+};
+mxConnector.prototype.paintCurvedLine = function(a, b) {
+  a.begin();
+  var c = b[0],
+    d = b.length;
+  a.moveTo(c.x, c.y);
+  for (c = 1; c < d - 2; c++) {
+    var e = b[c],
+      f = b[c + 1];
+    a.quadTo(e.x, e.y, (e.x + f.x) / 2, (e.y + f.y) / 2)
+  }
+  e = b[d - 2];
+  f = b[d - 1];
+  a.quadTo(e.x, e.y, f.x, f.y);
+  a.stroke()
+};
+mxConnector.prototype.createMarker = function(a, b, c) {
+  var d = b.length,
+    e = c ? b[1] : b[d - 2];
+  b = c ? b[0] : b[d - 1];
+  var d = b.x - e.x,
+    f = b.y - e.y,
+    g = Math.max(1, Math.sqrt(d * d + f * f)),
+    e = d / g,
+    d = f / g,
+    f = mxUtils.getNumber(this.style, c ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE),
+    g = mxUtils.getValue(this.style, c ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW);
+  return mxMarker.createMarker(a, this, g, b, e, d, f, c, this.strokewidth, 0 != this.style[c ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL])
+};
+mxConnector.prototype.augmentBoundingBox = function(a) {
+  mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+  var b = 0;
+  mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE && (b = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE) +
+    1);
+  mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE && (b = Math.max(b, mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)) +
+    1);
+  a.grow(Math.ceil(b * this.scale))
+};
+
+function mxSwimlane(a, b, c, d) {
+  mxShape.call(this);
+  this.bounds = a;
+  this.fill = b;
+  this.stroke = c;
+  this.strokewidth = null != d ? d : 1
+}
+mxUtils.extend(mxSwimlane, mxShape);
+mxSwimlane.prototype.imageSize = 16;
+mxSwimlane.prototype.getGradientBounds = function(a, b, c, d, e) {
+  a = Math.min(e, mxUtils.getValue(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+  return new mxRectangle(b, c, d, a)
+};
+mxSwimlane.prototype.getRotation = function() {
+  var a = mxShape.prototype.getRotation.apply(this, arguments);
+  1 != mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) && (a += mxText.prototype.verticalTextRotation);
+  return a
+};
+mxSwimlane.prototype.getTextRotation = function() {
+  return this.getRotation()
+};
+mxSwimlane.prototype.isPaintBoundsInverted = function() {
+  return mxShape.prototype.isPaintBoundsInverted.apply(this, arguments) || 1 != mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1)
+};
+mxSwimlane.prototype.getArcSize = function(a, b, c) {
+  a = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, 100 * mxConstants.RECTANGLE_ROUNDING_FACTOR) / 100;
+  return 3 * c * a
+};
+mxSwimlane.prototype.paintVertexShape = function(a, b, c, d, e) {
+  var f = Math.min(e, mxUtils.getValue(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE)),
+    g = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE),
+    h = 1 == mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_LINE, 1),
+    k = 0;
+  a.translate(b, c);
+  this.isRounded ? (k = this.getArcSize(d, e, f), this.paintRoundedSwimlane(a, b, c, d, e, f, k, g, h)) : this.paintSwimlane(a, b, c, d, e, f, g, h);
+  g = mxUtils.getValue(this.style, mxConstants.STYLE_SEPARATORCOLOR,
+    mxConstants.NONE);
+  this.paintSeparator(a, d, f, e, g);
+  null != this.image && (e = this.getImageBounds(b, f, d, e), a.image(e.x - b, e.y - c, e.width, e.height, this.image, !1, !1, !1));
+  this.glass && (a.setShadow(!1), this.paintGlassEffect(a, 0, 0, d, f, k))
+};
+mxSwimlane.prototype.paintSwimlane = function(a, b, c, d, e, f, g, h) {
+  g != mxConstants.NONE && (a.save(), a.setFillColor(g), a.rect(0, 0, d, e), a.fillAndStroke(), a.restore(), a.setShadow(!1));
+  a.begin();
+  a.moveTo(0, f);
+  a.lineTo(0, 0);
+  a.lineTo(d, 0);
+  a.lineTo(d, f);
+  h && a.close();
+  a.fillAndStroke();
+  f < e && g == mxConstants.NONE && (a.pointerEvents = !1, a.begin(), a.moveTo(0, f), a.lineTo(0, e), a.lineTo(d, e), a.lineTo(d, f), a.stroke())
+};
+mxSwimlane.prototype.paintRoundedSwimlane = function(a, b, c, d, e, f, g, h, k) {
+  h != mxConstants.NONE && (a.save(), a.setFillColor(h), a.roundrect(0, 0, d, e, g, g), a.fillAndStroke(), a.restore(), a.setShadow(!1));
+  a.begin();
+  a.moveTo(d, f);
+  a.lineTo(d, g);
+  a.quadTo(d, 0, d - Math.min(d / 2, g), 0);
+  a.lineTo(Math.min(d / 2, g), 0);
+  a.quadTo(0, 0, 0, g);
+  a.lineTo(0, f);
+  k && a.close();
+  a.fillAndStroke();
+  f < e && h == mxConstants.NONE && (a.pointerEvents = !1, a.begin(), a.moveTo(0, f), a.lineTo(0, e - g), a.quadTo(0, e, Math.min(d / 2, g), e), a.lineTo(d - Math.min(d / 2, g), e),
+    a.quadTo(d, e, d, e - g), a.lineTo(d, f), a.stroke())
+};
+mxSwimlane.prototype.paintSeparator = function(a, b, c, d, e) {
+  e != mxConstants.NONE && (a.setStrokeColor(e), a.setDashed(!0), a.begin(), a.moveTo(b, c), a.lineTo(b, d), a.stroke(), a.setDashed(!1))
+};
+mxSwimlane.prototype.getImageBounds = function(a, b, c, d) {
+  return new mxRectangle(a + c - this.imageSize, b, this.imageSize, this.imageSize)
+};
+
+function mxGraphLayout(a) {
+  this.graph = a
+}
+mxGraphLayout.prototype.graph = null;
+mxGraphLayout.prototype.useBoundingBox = !0;
+mxGraphLayout.prototype.parent = null;
+mxGraphLayout.prototype.moveCell = function(a, b, c) {};
+mxGraphLayout.prototype.execute = function(a) {};
+mxGraphLayout.prototype.getGraph = function() {
+  return this.graph
+};
+mxGraphLayout.prototype.getConstraint = function(a, b, c, d) {
+  c = this.graph.view.getState(b);
+  b = null != c ? c.style : this.graph.getCellStyle(b);
+  return null != b ? b[a] : null
+};
+mxGraphLayout.traverse = function(a, b, c, d, e) {
+  if (null != c && null != a) {
+    b = null != b ? b : !0;
+    e = e || [];
+    var f = mxCellPath.create(a);
+    if (null == e[f] && (e[f] = a, d = c(a, d), null == d || d))
+      if (d = this.graph.model.getEdgeCount(a), 0 < d)
+        for (f = 0; f < d; f++) {
+          var g = this.graph.model.getEdgeAt(a, f),
+            h = this.graph.model.getTerminal(g, !0) == a;
+          if (!b || h) h = this.graph.view.getVisibleTerminal(g, !h), this.traverse(h, b, c, g, e)
+        }
+  }
+};
+mxGraphLayout.prototype.isVertexMovable = function(a) {
+  return this.graph.isCellMovable(a)
+};
+mxGraphLayout.prototype.isVertexIgnored = function(a) {
+  return !this.graph.getModel().isVertex(a) || !this.graph.isCellVisible(a)
+};
+mxGraphLayout.prototype.isEdgeIgnored = function(a) {
+  var b = this.graph.getModel();
+  return !b.isEdge(a) || !this.graph.isCellVisible(a) || null == b.getTerminal(a, !0) || null == b.getTerminal(a, !1)
+};
+mxGraphLayout.prototype.setEdgeStyleEnabled = function(a, b) {
+  this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE, b ? "0" : "1", [a])
+};
+mxGraphLayout.prototype.setOrthogonalEdge = function(a, b) {
+  this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL, b ? "1" : "0", [a])
+};
+mxGraphLayout.prototype.getParentOffset = function(a) {
+  var b = new mxPoint;
+  if (null != a && a != this.parent) {
+    var c = this.graph.getModel();
+    if (c.isAncestor(this.parent, a))
+      for (var d = c.getGeometry(a); a != this.parent;) b.x += d.x, b.y += d.y, a = c.getParent(a), d = c.getGeometry(a)
+  }
+  return b
+};
+mxGraphLayout.prototype.setEdgePoints = function(a, b) {
+  if (null != a) {
+    var c = this.graph.model,
+      d = c.getGeometry(a);
+    null == d ? (d = new mxGeometry, d.setRelative(!0)) : d = d.clone();
+    if (null != this.parent && null != b)
+      for (var e = c.getParent(a), e = this.getParentOffset(e), f = 0; f < b.length; f++) b[f].x -= e.x, b[f].y -= e.y;
+    d.points = b;
+    c.setGeometry(a, d)
+  }
+};
+mxGraphLayout.prototype.setVertexLocation = function(a, b, c) {
+  var d = this.graph.getModel(),
+    e = d.getGeometry(a),
+    f = null;
+  if (null != e) {
+    f = new mxRectangle(b, c, e.width, e.height);
+    if (this.useBoundingBox) {
+      var g = this.graph.getView().getState(a);
+      if (null != g && null != g.text && null != g.text.boundingBox) {
+        var h = this.graph.getView().scale,
+          k = g.text.boundingBox;
+        g.text.boundingBox.x < g.x && (b += (g.x - k.x) / h, f.width = k.width);
+        g.text.boundingBox.y < g.y && (c += (g.y - k.y) / h, f.height = k.height)
+      }
+    }
+    null != this.parent && (g = d.getParent(a), null != g && g !=
+      this.parent && (g = this.getParentOffset(g), b -= g.x, c -= g.y));
+    if (e.x != b || e.y != c) e = e.clone(), e.x = b, e.y = c, d.setGeometry(a, e)
+  }
+  return f
+};
+mxGraphLayout.prototype.getVertexBounds = function(a) {
+  var b = this.graph.getModel().getGeometry(a);
+  if (this.useBoundingBox) {
+    var c = this.graph.getView().getState(a);
+    if (null != c && null != c.text && null != c.text.boundingBox) var d = this.graph.getView().scale,
+      e = c.text.boundingBox,
+      f = Math.max(c.x - e.x, 0) / d,
+      g = Math.max(c.y - e.y, 0) / d,
+      h = Math.max(e.x + e.width - (c.x + c.width), 0) / d,
+      c = Math.max(e.y + e.height - (c.y + c.height), 0) / d,
+      b = new mxRectangle(b.x - f, b.y - g, b.width + f + h, b.height + g + c)
+  }
+  null != this.parent && (a = this.graph.getModel().getParent(a),
+    b = b.clone(), null != a && a != this.parent && (a = this.getParentOffset(a), b.x += a.x, b.y += a.y));
+  return new mxRectangle(b.x, b.y, b.width, b.height)
+};
+mxGraphLayout.prototype.arrangeGroups = function(a, b) {
+  this.graph.getModel().beginUpdate();
+  try {
+    for (var c = a.length - 1; 0 <= c; c--) {
+      var d = a[c],
+        e = this.graph.getChildVertices(d),
+        f = this.graph.getBoundingBoxFromGeometry(e),
+        g = this.graph.getCellGeometry(d),
+        h = 0,
+        k = 0;
+      if (this.graph.isSwimlane(d)) var l = this.graph.getStartSize(d),
+        h = l.width,
+        k = l.height;
+      null != f && null != g && (g = g.clone(), g.x = g.x + f.x - b - h, g.y = g.y + f.y - b - k, g.width = f.width + 2 * b + h, g.height = f.height + 2 * b + k, this.graph.getModel().setGeometry(d,
+        g), this.graph.moveCells(e,
+        b + h - f.x, b + k - f.y))
+    }
+  } finally {
+    this.graph.getModel().endUpdate()
+  }
+};
+
+function mxStackLayout(a, b, c, d, e, f) {
+  mxGraphLayout.call(this, a);
+  this.horizontal = null != b ? b : !0;
+  this.spacing = null != c ? c : 0;
+  this.x0 = null != d ? d : 0;
+  this.y0 = null != e ? e : 0;
+  this.border = null != f ? f : 0
+}
+mxStackLayout.prototype = new mxGraphLayout;
+mxStackLayout.prototype.constructor = mxStackLayout;
+mxStackLayout.prototype.horizontal = null;
+mxStackLayout.prototype.spacing = null;
+mxStackLayout.prototype.x0 = null;
+mxStackLayout.prototype.y0 = null;
+mxStackLayout.prototype.border = 0;
+mxStackLayout.prototype.keepFirstLocation = !1;
+mxStackLayout.prototype.fill = !1;
+mxStackLayout.prototype.resizeParent = !1;
+mxStackLayout.prototype.resizeLast = !1;
+mxStackLayout.prototype.wrap = null;
+mxStackLayout.prototype.isHorizontal = function() {
+  return this.horizontal
+};
+mxStackLayout.prototype.moveCell = function(a, b, c) {
+  var d = this.graph.getModel(),
+    e = d.getParent(a),
+    f = this.isHorizontal();
+  if (null != a && null != e) {
+    var g = 0,
+      h = 0,
+      k = d.getChildCount(e);
+    b = f ? b : c;
+    g = this.graph.getView().getState(e);
+    null != g && (b -= f ? g.x : g.y);
+    for (g = 0; g < k; g++)
+      if (c = d.getChildAt(e, g), c != a && (c = d.getGeometry(c), null != c)) {
+        c = f ? c.x + c.width / 2 : c.y + c.height / 2;
+        if (h < b && c > b) break;
+        h = c
+      }
+    f = e.getIndex(a);
+    f = Math.max(0, g - (g > f ? 1 : 0));
+    d.add(e, a, f)
+  }
+};
+mxStackLayout.prototype.getParentSize = function(a) {
+  var b = this.graph.getModel(),
+    c = b.getGeometry(a);
+  if (null != this.graph.container && (null == c && b.isLayer(a) || a == this.graph.getView().currentRoot)) c = new mxRectangle(0, 0, this.graph.container.offsetWidth - 1, this.graph.container.offsetHeight -
+    1);
+  return c
+};
+mxStackLayout.prototype.execute = function(a) {
+  if (null != a) {
+    var b = this.isHorizontal(),
+      c = this.graph.getModel(),
+      d = this.getParentSize(a),
+      e = 0;
+    null != d && (e = b ? d.height : d.width);
+    var e = e - (2 * this.spacing + 2 * this.border),
+      f = this.x0 + this.border,
+      g = this.y0 + this.border;
+    if (this.graph.isSwimlane(a)) {
+      var h = this.graph.getCellStyle(a),
+        k = mxUtils.getValue(h, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE),
+        h = mxUtils.getValue(h, mxConstants.STYLE_HORIZONTAL, !0);
+      b == h && (e -= k);
+      b ? g += k : f += k
+    }
+    c.beginUpdate();
+    try {
+      for (var k = 0,
+          h = null, l = c.getChildCount(a), m = 0; m < l; m++) {
+        var n = c.getChildAt(a, m);
+        if (!this.isVertexIgnored(n) && this.isVertexMovable(n)) {
+          var p = c.getGeometry(n);
+          if (null != p) {
+            p = p.clone();
+            if (null != this.wrap && null != h && (b && h.x + h.width + p.width + 2 * this.spacing > this.wrap || !b && h.y + h.height + p.height + 2 * this.spacing > this.wrap)) h = null, b ? g +=
+              k + this.spacing : f += k + this.spacing, k = 0;
+            k = Math.max(k, b ? p.height : p.width);
+            null != h ? b ? p.x = h.x + h.width + this.spacing : p.y = h.y + h.height + this.spacing : this.keepFirstLocation || (b ? p.x = f : p.y = g);
+            b ? p.y = g : p.x = f;
+            this.fill && 0 < e && (b ? p.height = e : p.width = e);
+            c.setGeometry(n, p);
+            h = p
+          }
+        }
+      }
+      this.resizeParent && null != d && null != h && !this.graph.isCellCollapsed(a) ? (d = d.clone(), b ? d.width = h.x + h.width + this.spacing : d.height = h.y + h.height + this.spacing, c.setGeometry(
+        a, d)) : this.resizeLast && (null != d && null != h) && (b ? h.width = d.width - h.x - this.spacing : h.height = d.height - h.y - this.spacing)
+    } finally {
+      c.endUpdate()
+    }
+  }
+};
+
+function mxPartitionLayout(a, b, c, d) {
+  mxGraphLayout.call(this, a);
+  this.horizontal = null != b ? b : !0;
+  this.spacing = c || 0;
+  this.border = d || 0
+}
+mxPartitionLayout.prototype = new mxGraphLayout;
+mxPartitionLayout.prototype.constructor = mxPartitionLayout;
+mxPartitionLayout.prototype.horizontal = null;
+mxPartitionLayout.prototype.spacing = null;
+mxPartitionLayout.prototype.border = null;
+mxPartitionLayout.prototype.resizeVertices = !0;
+mxPartitionLayout.prototype.isHorizontal = function() {
+  return this.horizontal
+};
+mxPartitionLayout.prototype.moveCell = function(a, b, c) {
+  c = this.graph.getModel();
+  var d = c.getParent(a);
+  if (null != a && null != d) {
+    for (var e = 0, f = 0, g = c.getChildCount(d), e = 0; e < g; e++) {
+      var h = c.getChildAt(d, e),
+        h = this.getVertexBounds(h);
+      if (null != h) {
+        h = h.x + h.width / 2;
+        if (f < b && h > b) break;
+        f = h
+      }
+    }
+    b = d.getIndex(a);
+    b = Math.max(0, e - (e > b ? 1 : 0));
+    c.add(d, a, b)
+  }
+};
+mxPartitionLayout.prototype.execute = function(a) {
+  var b = this.isHorizontal(),
+    c = this.graph.getModel(),
+    d = c.getGeometry(a);
+  if (null != this.graph.container && (null == d && c.isLayer(a) || a == this.graph.getView().currentRoot)) d = new mxRectangle(0, 0, this.graph.container.offsetWidth - 1, this.graph.container.offsetHeight -
+    1);
+  if (null != d) {
+    for (var e = [], f = c.getChildCount(a), g = 0; g < f; g++) {
+      var h = c.getChildAt(a, g);
+      !this.isVertexIgnored(h) && this.isVertexMovable(h) && e.push(h)
+    }
+    f = e.length;
+    if (0 < f) {
+      var k = this.border,
+        l = this.border,
+        m = b ?
+        d.height : d.width,
+        m = m - 2 * this.border;
+      a = this.graph.isSwimlane(a) ? this.graph.getStartSize(a) : new mxRectangle;
+      m -= b ? a.height : a.width;
+      k += a.width;
+      l += a.height;
+      a = this.border + (f - 1) * this.spacing;
+      d = b ? (d.width - k - a) / f : (d.height - l - a) / f;
+      if (0 < d) {
+        c.beginUpdate();
+        try {
+          for (g = 0; g < f; g++) {
+            var h = e[g],
+              n = c.getGeometry(h);
+            null != n && (n = n.clone(), n.x = k, n.y = l, b ? (this.resizeVertices && (n.width = d, n.height = m), k += d + this.spacing) : (this.resizeVertices && (n.height = d, n.width = m), l +=
+              d + this.spacing), c.setGeometry(h, n))
+          }
+        } finally {
+          c.endUpdate()
+        }
+      }
+    }
+  }
+};
+
+function mxCompactTreeLayout(a, b, c) {
+  mxGraphLayout.call(this, a);
+  this.horizontal = null != b ? b : !0;
+  this.invert = null != c ? c : !1
+}
+mxCompactTreeLayout.prototype = new mxGraphLayout;
+mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;
+mxCompactTreeLayout.prototype.horizontal = null;
+mxCompactTreeLayout.prototype.invert = null;
+mxCompactTreeLayout.prototype.resizeParent = !0;
+mxCompactTreeLayout.prototype.groupPadding = 10;
+mxCompactTreeLayout.prototype.parentsChanged = null;
+mxCompactTreeLayout.prototype.moveTree = !1;
+mxCompactTreeLayout.prototype.levelDistance = 10;
+mxCompactTreeLayout.prototype.nodeDistance = 20;
+mxCompactTreeLayout.prototype.resetEdges = !0;
+mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;
+mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;
+mxCompactTreeLayout.prototype.minEdgeJetty = 8;
+mxCompactTreeLayout.prototype.channelBuffer = 4;
+mxCompactTreeLayout.prototype.edgeRouting = !0;
+mxCompactTreeLayout.prototype.isVertexIgnored = function(a) {
+  return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) || 0 == this.graph.getConnections(a).length
+};
+mxCompactTreeLayout.prototype.isHorizontal = function() {
+  return this.horizontal
+};
+mxCompactTreeLayout.prototype.execute = function(a, b) {
+  this.parent = a;
+  var c = this.graph.getModel();
+  if (null == b)
+    if (0 < this.graph.getEdges(a, c.getParent(a), this.invert, !this.invert, !1).length) b = a;
+    else {
+      var d = this.graph.findTreeRoots(a, !0, this.invert);
+      if (0 < d.length)
+        for (var e = 0; e < d.length; e++)
+          if (!this.isVertexIgnored(d[e]) && 0 < this.graph.getEdges(d[e], null, this.invert, !this.invert, !1).length) {
+            b = d[e];
+            break
+          }
+    }
+  if (null != b) {
+    this.parentsChanged = this.resizeParent ? {} : null;
+    c.beginUpdate();
+    try {
+      var f = this.dfs(b, a);
+      if (null !=
+        f) {
+        this.layout(f);
+        var g = this.graph.gridSize,
+          d = g;
+        if (!this.moveTree) {
+          var h = this.getVertexBounds(b);
+          null != h && (g = h.x, d = h.y)
+        }
+        h = null;
+        h = this.isHorizontal() ? this.horizontalLayout(f, g, d) : this.verticalLayout(f, null, g, d);
+        if (null != h) {
+          var k = e = 0;
+          0 > h.x && (e = Math.abs(g - h.x));
+          0 > h.y && (k = Math.abs(d - h.y));
+          (0 != e || 0 != k) && this.moveNode(f, e, k);
+          this.resizeParent && this.adjustParents();
+          this.edgeRouting && this.localEdgeProcessing(f)
+        }
+      }
+    } finally {
+      c.endUpdate()
+    }
+  }
+};
+mxCompactTreeLayout.prototype.moveNode = function(a, b, c) {
+  a.x += b;
+  a.y += c;
+  this.apply(a);
+  for (a = a.child; null != a;) this.moveNode(a, b, c), a = a.next
+};
+mxCompactTreeLayout.prototype.dfs = function(a, b, c) {
+  c = null != c ? c : [];
+  var d = mxCellPath.create(a),
+    e = null;
+  if (null != a && null == c[d] && !this.isVertexIgnored(a)) {
+    c[d] = a;
+    var e = this.createNode(a),
+      d = this.graph.getModel(),
+      f = null;
+    a = this.graph.getEdges(a, b, this.invert, !this.invert, !1, !0);
+    for (var g = this.graph.getView(), h = 0; h < a.length; h++) {
+      var k = a[h];
+      if (!this.isEdgeIgnored(k)) {
+        this.resetEdges && this.setEdgePoints(k, null);
+        this.edgeRouting && (this.setEdgeStyleEnabled(k, !1), this.setEdgePoints(k, null));
+        var l = g.getState(k),
+          k = null != l ? l.getVisibleTerminal(this.invert) : g.getVisibleTerminal(k, this.invert),
+          l = this.dfs(k, b, c);
+        null != l && null != d.getGeometry(k) && (null == f ? e.child = l : f.next = l, f = l)
+      }
+    }
+  }
+  return e
+};
+mxCompactTreeLayout.prototype.layout = function(a) {
+  if (null != a) {
+    for (var b = a.child; null != b;) this.layout(b), b = b.next;
+    null != a.child ? this.attachParent(a, this.join(a)) : this.layoutLeaf(a)
+  }
+};
+mxCompactTreeLayout.prototype.horizontalLayout = function(a, b, c, d) {
+  a.x += b + a.offsetX;
+  a.y += c + a.offsetY;
+  d = this.apply(a, d);
+  b = a.child;
+  if (null != b) {
+    d = this.horizontalLayout(b, a.x, a.y, d);
+    c = a.y + b.offsetY;
+    for (var e = b.next; null != e;) d = this.horizontalLayout(e, a.x + b.offsetX, c, d), c += e.offsetY, e = e.next
+  }
+  return d
+};
+mxCompactTreeLayout.prototype.verticalLayout = function(a, b, c, d, e) {
+  a.x += c + a.offsetY;
+  a.y += d + a.offsetX;
+  e = this.apply(a, e);
+  b = a.child;
+  if (null != b) {
+    e = this.verticalLayout(b, a, a.x, a.y, e);
+    c = a.x + b.offsetY;
+    for (d = b.next; null != d;) e = this.verticalLayout(d, a, c, a.y + b.offsetX, e), c += d.offsetY, d = d.next
+  }
+  return e
+};
+mxCompactTreeLayout.prototype.attachParent = function(a, b) {
+  var c = this.nodeDistance + this.levelDistance,
+    d = (b - a.width) / 2 - this.nodeDistance,
+    e = d + a.width + 2 * this.nodeDistance - b;
+  a.child.offsetX = c + a.height;
+  a.child.offsetY = e;
+  a.contour.upperHead = this.createLine(a.height, 0, this.createLine(c, e, a.contour.upperHead));
+  a.contour.lowerHead = this.createLine(a.height, 0, this.createLine(c, d, a.contour.lowerHead))
+};
+mxCompactTreeLayout.prototype.layoutLeaf = function(a) {
+  var b = 2 * this.nodeDistance;
+  a.contour.upperTail = this.createLine(a.height + b, 0);
+  a.contour.upperHead = a.contour.upperTail;
+  a.contour.lowerTail = this.createLine(0, -a.width - b);
+  a.contour.lowerHead = this.createLine(a.height + b, 0, a.contour.lowerTail)
+};
+mxCompactTreeLayout.prototype.join = function(a) {
+  var b = 2 * this.nodeDistance,
+    c = a.child;
+  a.contour = c.contour;
+  for (var d = c.width + b, e = d, c = c.next; null != c;) {
+    var f = this.merge(a.contour, c.contour);
+    c.offsetY = f + d;
+    c.offsetX = 0;
+    d = c.width + b;
+    e += f + d;
+    c = c.next
+  }
+  return e
+};
+mxCompactTreeLayout.prototype.merge = function(a, b) {
+  for (var c = 0, d = 0, e = 0, f = a.lowerHead, g = b.upperHead; null != g && null != f;) {
+    var h = this.offset(c, d, g.dx, g.dy, f.dx, f.dy),
+      d = d + h,
+      e = e + h;
+    c + g.dx <= f.dx ? (c += g.dx, d += g.dy, g = g.next) : (c -= f.dx, d -= f.dy, f = f.next)
+  }
+  null != g ? (c = this.bridge(a.upperTail, 0, 0, g, c, d), a.upperTail = null != c.next ? b.upperTail : c, a.lowerTail = b.lowerTail) : (c = this.bridge(b.lowerTail, c, d, f, 0, 0), null == c.next &&
+    (a.lowerTail = c));
+  a.lowerHead = b.lowerHead;
+  return e
+};
+mxCompactTreeLayout.prototype.offset = function(a, b, c, d, e, f) {
+  var g = 0;
+  if (e <= a || 0 >= a + c) return 0;
+  g = 0 < e * d - c * f ? 0 > a ? a * d / c - b : 0 < a ? a * f / e - b : -b : e < a + c ? f - (b + (e - a) * d / c) : e > a + c ? (c + a) * f / e - (b + d) : f - (b + d);
+  return 0 < g ? g : 0
+};
+mxCompactTreeLayout.prototype.bridge = function(a, b, c, d, e, f) {
+  b = e + d.dx - b;
+  e = e = 0;
+  0 == d.dx ? e = d.dy : (e = b * d.dy, e /= d.dx);
+  b = this.createLine(b, e, d.next);
+  a.next = this.createLine(0, f + d.dy - e - c, b);
+  return b
+};
+mxCompactTreeLayout.prototype.createNode = function(a) {
+  var b = {};
+  b.cell = a;
+  b.x = 0;
+  b.y = 0;
+  b.width = 0;
+  b.height = 0;
+  a = this.getVertexBounds(a);
+  null != a && (this.isHorizontal() ? (b.width = a.height, b.height = a.width) : (b.width = a.width, b.height = a.height));
+  b.offsetX = 0;
+  b.offsetY = 0;
+  b.contour = {};
+  return b
+};
+mxCompactTreeLayout.prototype.apply = function(a, b) {
+  var c = this.graph.getModel(),
+    d = a.cell,
+    e = c.getGeometry(d);
+  null != d && null != e && (this.isVertexMovable(d) && (e = this.setVertexLocation(d, a.x, a.y), this.resizeParent && (c = c.getParent(d), d = mxCellPath.create(c), null == this.parentsChanged[d] &&
+    (this.parentsChanged[d] = c))), b = null == b ? new mxRectangle(e.x, e.y, e.width, e.height) : new mxRectangle(Math.min(b.x, e.x), Math.min(b.y, e.y), Math.max(b.x + b.width, e.x + e.width),
+    Math.max(b.y + b.height, e.y + e.height)));
+  return b
+};
+mxCompactTreeLayout.prototype.createLine = function(a, b, c) {
+  var d = {};
+  d.dx = a;
+  d.dy = b;
+  d.next = c;
+  return d
+};
+mxCompactTreeLayout.prototype.adjustParents = function() {
+  var a = [],
+    b;
+  for (b in this.parentsChanged) a.push(this.parentsChanged[b]);
+  this.arrangeGroups(mxUtils.sortCells(a, !0), this.groupPadding)
+};
+mxCompactTreeLayout.prototype.localEdgeProcessing = function(a) {
+  this.processNodeOutgoing(a);
+  for (a = a.child; null != a;) this.localEdgeProcessing(a), a = a.next
+};
+mxCompactTreeLayout.prototype.processNodeOutgoing = function(a) {
+  for (var b = a.child, c = a.cell, d = 0, e = []; null != b;) {
+    d++;
+    var f = b.x;
+    this.horizontal && (f = b.y);
+    e.push(new WeightedCellSorter(b, f));
+    b = b.next
+  }
+  e.sort(WeightedCellSorter.prototype.compare);
+  var f = a.width,
+    g = (d + 1) * this.prefHozEdgeSep;
+  f > g + 2 * this.prefHozEdgeSep && (f -= 2 * this.prefHozEdgeSep);
+  a = f / d;
+  b = a / 2;
+  f > g + 2 * this.prefHozEdgeSep && (b += this.prefHozEdgeSep);
+  for (var f = this.minEdgeJetty - this.prefVertEdgeOff, g = 0, h = this.getVertexBounds(c), k = 0; k < e.length; k++) {
+    for (var l =
+        e[k].cell.cell, m = this.getVertexBounds(l), l = this.graph.getEdgesBetween(c, l, !1), n = [], p = 0, q = 0, s = 0; s < l.length; s++) this.horizontal ? (p = h.x + h.width, q = h.y + b, n.push(
+      new mxPoint(p, q)), p = h.x + h.width + f, n.push(new mxPoint(p, q)), q = m.y + m.height / 2) : (p = h.x + b, q = h.y + h.height, n.push(new mxPoint(p, q)), q = h.y + h.height + f, n.push(
+      new mxPoint(p, q)), p = m.x + m.width / 2), n.push(new mxPoint(p, q)), this.setEdgePoints(l[s], n);
+    k < d / 2 ? f += this.prefVertEdgeOff : k > d / 2 && (f -= this.prefVertEdgeOff);
+    b += a;
+    g = Math.max(g, f)
+  }
+};
+
+function WeightedCellSorter(a, b) {
+  this.cell = a;
+  this.weightedValue = b
+}
+WeightedCellSorter.prototype.weightedValue = 0;
+WeightedCellSorter.prototype.nudge = !1;
+WeightedCellSorter.prototype.visited = !1;
+WeightedCellSorter.prototype.rankIndex = null;
+WeightedCellSorter.prototype.cell = null;
+WeightedCellSorter.prototype.compare = function(a, b) {
+  return null != a && null != b ? b.weightedValue > a.weightedValue ? 1 : b.weightedValue < a.weightedValue ? -1 : b.nudge ? 1 : -1 : 0
+};
+
+function mxFastOrganicLayout(a) {
+  mxGraphLayout.call(this, a)
+}
+mxFastOrganicLayout.prototype = new mxGraphLayout;
+mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
+mxFastOrganicLayout.prototype.useInputOrigin = !0;
+mxFastOrganicLayout.prototype.resetEdges = !0;
+mxFastOrganicLayout.prototype.disableEdgeStyle = !0;
+mxFastOrganicLayout.prototype.forceConstant = 50;
+mxFastOrganicLayout.prototype.forceConstantSquared = 0;
+mxFastOrganicLayout.prototype.minDistanceLimit = 2;
+mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
+mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
+mxFastOrganicLayout.prototype.initialTemp = 200;
+mxFastOrganicLayout.prototype.temperature = 0;
+mxFastOrganicLayout.prototype.maxIterations = 0;
+mxFastOrganicLayout.prototype.iteration = 0;
+mxFastOrganicLayout.prototype.allowedToRun = !0;
+mxFastOrganicLayout.prototype.isVertexIgnored = function(a) {
+  return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) || 0 == this.graph.getConnections(a).length
+};
+mxFastOrganicLayout.prototype.execute = function(a) {
+  var b = this.graph.getModel();
+  this.vertexArray = [];
+  for (var c = this.graph.getChildVertices(a), d = 0; d < c.length; d++) this.isVertexIgnored(c[d]) || this.vertexArray.push(c[d]);
+  var e = this.useInputOrigin ? this.graph.view.getBounds(this.vertexArray) : null,
+    f = this.vertexArray.length;
+  this.indices = [];
+  this.dispX = [];
+  this.dispY = [];
+  this.cellLocation = [];
+  this.isMoveable = [];
+  this.neighbours = [];
+  this.radius = [];
+  this.radiusSquared = [];
+  0.0010 > this.forceConstant && (this.forceConstant =
+    0.0010);
+  this.forceConstantSquared = this.forceConstant * this.forceConstant;
+  for (d = 0; d < this.vertexArray.length; d++) {
+    var g = this.vertexArray[d];
+    this.cellLocation[d] = [];
+    var h = mxCellPath.create(g);
+    this.indices[h] = d;
+    var k = this.getVertexBounds(g),
+      l = k.width,
+      m = k.height,
+      n = k.x,
+      p = k.y;
+    this.cellLocation[d][0] = n + l / 2;
+    this.cellLocation[d][1] = p + m / 2;
+    this.radius[d] = Math.min(l, m);
+    this.radiusSquared[d] = this.radius[d] * this.radius[d]
+  }
+  b.beginUpdate();
+  try {
+    for (d = 0; d < f; d++) {
+      this.dispX[d] = 0;
+      this.dispY[d] = 0;
+      this.isMoveable[d] = this.isVertexMovable(this.vertexArray[d]);
+      var q = this.graph.getConnections(this.vertexArray[d], a),
+        c = this.graph.getOpposites(q, this.vertexArray[d]);
+      this.neighbours[d] = [];
+      for (l = 0; l < c.length; l++) {
+        this.resetEdges && this.graph.resetEdge(q[l]);
+        this.disableEdgeStyle && this.setEdgeStyleEnabled(q[l], !1);
+        var h = mxCellPath.create(c[l]),
+          s = this.indices[h];
+        this.neighbours[d][l] = null != s ? s : d
+      }
+    }
+    this.temperature = this.initialTemp;
+    0 == this.maxIterations && (this.maxIterations = 20 * Math.sqrt(f));
+    for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++) {
+      if (!this.allowedToRun) return;
+      this.calcRepulsion();
+      this.calcAttraction();
+      this.calcPositions();
+      this.reduceTemperature()
+    }
+    a = c = null;
+    for (d = 0; d < this.vertexArray.length; d++) g = this.vertexArray[d], this.isVertexMovable(g) && (k = this.getVertexBounds(g), null != k && (this.cellLocation[d][0] -= k.width / 2, this.cellLocation[
+        d][1] -= k.height / 2, n = this.graph.snap(this.cellLocation[d][0]), p = this.graph.snap(this.cellLocation[d][1]), this.setVertexLocation(g, n, p), c = null == c ? n : Math.min(c, n), a =
+      null == a ? p : Math.min(a, p)));
+    d = -(c || 0) + 1;
+    g = -(a || 0) + 1;
+    null != e && (d += e.x,
+      g += e.y);
+    this.graph.moveCells(this.vertexArray, d, g)
+  } finally {
+    b.endUpdate()
+  }
+};
+mxFastOrganicLayout.prototype.calcPositions = function() {
+  for (var a = 0; a < this.vertexArray.length; a++)
+    if (this.isMoveable[a]) {
+      var b = Math.sqrt(this.dispX[a] * this.dispX[a] + this.dispY[a] * this.dispY[a]);
+      0.0010 > b && (b = 0.0010);
+      var c = this.dispX[a] / b * Math.min(b, this.temperature),
+        b = this.dispY[a] / b * Math.min(b, this.temperature);
+      this.dispX[a] = 0;
+      this.dispY[a] = 0;
+      this.cellLocation[a][0] += c;
+      this.cellLocation[a][1] += b
+    }
+};
+mxFastOrganicLayout.prototype.calcAttraction = function() {
+  for (var a = 0; a < this.vertexArray.length; a++)
+    for (var b = 0; b < this.neighbours[a].length; b++) {
+      var c = this.neighbours[a][b];
+      if (a != c && this.isMoveable[a] && this.isMoveable[c]) {
+        var d = this.cellLocation[a][0] - this.cellLocation[c][0],
+          e = this.cellLocation[a][1] - this.cellLocation[c][1],
+          f = d * d + e * e - this.radiusSquared[a] - this.radiusSquared[c];
+        f < this.minDistanceLimitSquared && (f = this.minDistanceLimitSquared);
+        var g = Math.sqrt(f),
+          f = f / this.forceConstant,
+          d = d / g * f,
+          e = e / g * f;
+        this.dispX[a] -= d;
+        this.dispY[a] -= e;
+        this.dispX[c] += d;
+        this.dispY[c] += e
+      }
+    }
+};
+mxFastOrganicLayout.prototype.calcRepulsion = function() {
+  for (var a = this.vertexArray.length, b = 0; b < a; b++)
+    for (var c = b; c < a; c++) {
+      if (!this.allowedToRun) return;
+      if (c != b && this.isMoveable[b] && this.isMoveable[c]) {
+        var d = this.cellLocation[b][0] - this.cellLocation[c][0],
+          e = this.cellLocation[b][1] - this.cellLocation[c][1];
+        0 == d && (d = 0.01 + Math.random());
+        0 == e && (e = 0.01 + Math.random());
+        var f = Math.sqrt(d * d + e * e),
+          g = f - this.radius[b] - this.radius[c];
+        g > this.maxDistanceLimit || (g < this.minDistanceLimit && (g = this.minDistanceLimit), g = this.forceConstantSquared /
+          g, d = d / f * g, e = e / f * g, this.dispX[b] += d, this.dispY[b] += e, this.dispX[c] -= d, this.dispY[c] -= e)
+      }
+    }
+};
+mxFastOrganicLayout.prototype.reduceTemperature = function() {
+  this.temperature = this.initialTemp * (1 - this.iteration / this.maxIterations)
+};
+
+function mxCircleLayout(a, b) {
+  mxGraphLayout.call(this, a);
+  this.radius = null != b ? b : 100
+}
+mxCircleLayout.prototype = new mxGraphLayout;
+mxCircleLayout.prototype.constructor = mxCircleLayout;
+mxCircleLayout.prototype.radius = null;
+mxCircleLayout.prototype.moveCircle = !1;
+mxCircleLayout.prototype.x0 = 0;
+mxCircleLayout.prototype.y0 = 0;
+mxCircleLayout.prototype.resetEdges = !0;
+mxCircleLayout.prototype.disableEdgeStyle = !0;
+mxCircleLayout.prototype.execute = function(a) {
+  var b = this.graph.getModel();
+  b.beginUpdate();
+  try {
+    for (var c = 0, d = null, e = null, f = [], g = b.getChildCount(a), h = 0; h < g; h++) {
+      var k = b.getChildAt(a, h);
+      if (this.isVertexIgnored(k)) this.isEdgeIgnored(k) || (this.resetEdges && this.graph.resetEdge(k), this.disableEdgeStyle && this.setEdgeStyleEnabled(k, !1));
+      else {
+        f.push(k);
+        var l = this.getVertexBounds(k),
+          d = null == d ? l.y : Math.min(d, l.y),
+          e = null == e ? l.x : Math.min(e, l.x),
+          c = Math.max(c, Math.max(l.width, l.height))
+      }
+    }
+    var m = this.getRadius(f.length,
+      c);
+    this.moveCircle && (e = this.x0, d = this.y0);
+    this.circle(f, m, e, d)
+  } finally {
+    b.endUpdate()
+  }
+};
+mxCircleLayout.prototype.getRadius = function(a, b) {
+  return Math.max(a * b / Math.PI, this.radius)
+};
+mxCircleLayout.prototype.circle = function(a, b, c, d) {
+  for (var e = a.length, f = 2 * Math.PI / e, g = 0; g < e; g++) this.isVertexMovable(a[g]) && this.setVertexLocation(a[g], c + b + b * Math.sin(g * f), d + b + b * Math.cos(g * f))
+};
+
+function mxParallelEdgeLayout(a) {
+  mxGraphLayout.call(this, a)
+}
+mxParallelEdgeLayout.prototype = new mxGraphLayout;
+mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;
+mxParallelEdgeLayout.prototype.spacing = 20;
+mxParallelEdgeLayout.prototype.execute = function(a) {
+  a = this.findParallels(a);
+  this.graph.model.beginUpdate();
+  try {
+    for (var b in a) {
+      var c = a[b];
+      1 < c.length && this.layout(c)
+    }
+  } finally {
+    this.graph.model.endUpdate()
+  }
+};
+mxParallelEdgeLayout.prototype.findParallels = function(a) {
+  for (var b = this.graph.getModel(), c = [], d = b.getChildCount(a), e = 0; e < d; e++) {
+    var f = b.getChildAt(a, e);
+    if (!this.isEdgeIgnored(f)) {
+      var g = this.getEdgeId(f);
+      null != g && (null == c[g] && (c[g] = []), c[g].push(f))
+    }
+  }
+  return c
+};
+mxParallelEdgeLayout.prototype.getEdgeId = function(a) {
+  var b = this.graph.getView(),
+    c = b.getState(a),
+    d = null != c ? c.getVisibleTerminal(!0) : b.getVisibleTerminal(a, !0);
+  a = null != c ? c.getVisibleTerminal(!1) : b.getVisibleTerminal(a, !1);
+  return null != d && null != a ? (d = mxCellPath.create(d), a = mxCellPath.create(a), d > a ? a + "-" + d : d + "-" + a) : null
+};
+mxParallelEdgeLayout.prototype.layout = function(a) {
+  var b = a[0],
+    c = this.graph.getModel(),
+    d = c.getGeometry(c.getTerminal(b, !0)),
+    e = c.getGeometry(c.getTerminal(b, !1));
+  if (d == e)
+    for (var b = d.x + d.width + this.spacing, c = d.y + d.height / 2, f = 0; f < a.length; f++) this.route(a[f], b, c), b += this.spacing;
+  else if (null != d && null != e)
+    for (var b = d.x + d.width / 2, c = d.y + d.height / 2, f = e.x + e.width / 2 - b, g = e.y + e.height / 2 - c, e = Math.sqrt(f * f + g * g), d = g * this.spacing / e, e = f * this.spacing / e,
+        b = b + f / 2 + d * (a.length - 1) / 2, c = c + g / 2 - e * (a.length - 1) / 2, f = 0; f < a.length; f++) this.route(a[f],
+      b, c), b -= d, c += e
+};
+mxParallelEdgeLayout.prototype.route = function(a, b, c) {
+  this.graph.isCellMovable(a) && this.setEdgePoints(a, [new mxPoint(b, c)])
+};
+
+function mxCompositeLayout(a, b, c) {
+  mxGraphLayout.call(this, a);
+  this.layouts = b;
+  this.master = c
+}
+mxCompositeLayout.prototype = new mxGraphLayout;
+mxCompositeLayout.prototype.constructor = mxCompositeLayout;
+mxCompositeLayout.prototype.layouts = null;
+mxCompositeLayout.prototype.master = null;
+mxCompositeLayout.prototype.moveCell = function(a, b, c) {
+  null != this.master ? this.master.move.apply(this.master, arguments) : this.layouts[0].move.apply(this.layouts[0], arguments)
+};
+mxCompositeLayout.prototype.execute = function(a) {
+  var b = this.graph.getModel();
+  b.beginUpdate();
+  try {
+    for (var c = 0; c < this.layouts.length; c++) this.layouts[c].execute.apply(this.layouts[c], arguments)
+  } finally {
+    b.endUpdate()
+  }
+};
+
+function mxEdgeLabelLayout(a, b) {
+  mxGraphLayout.call(this, a)
+}
+mxEdgeLabelLayout.prototype = new mxGraphLayout;
+mxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;
+mxEdgeLabelLayout.prototype.execute = function(a) {
+  for (var b = this.graph.view, c = this.graph.getModel(), d = [], e = [], f = c.getChildCount(a), g = 0; g < f; g++) {
+    var h = c.getChildAt(a, g),
+      k = b.getState(h);
+    null != k && (this.isVertexIgnored(h) ? this.isEdgeIgnored(h) || d.push(k) : e.push(k))
+  }
+  this.placeLabels(e, d)
+};
+mxEdgeLabelLayout.prototype.placeLabels = function(a, b) {
+  var c = this.graph.getModel();
+  c.beginUpdate();
+  try {
+    for (var d = 0; d < b.length; d++) {
+      var e = b[d];
+      if (null != e && null != e.text && null != e.text.boundingBox)
+        for (var f = 0; f < a.length; f++) {
+          var g = a[f];
+          null != g && this.avoid(e, g)
+        }
+    }
+  } finally {
+    c.endUpdate()
+  }
+};
+mxEdgeLabelLayout.prototype.avoid = function(a, b) {
+  var c = this.graph.getModel(),
+    d = a.text.boundingBox;
+  if (mxUtils.intersects(d, b)) {
+    var e = -d.y - d.height + b.y,
+      f = -d.y + b.y + b.height,
+      e = Math.abs(e) < Math.abs(f) ? e : f,
+      f = -d.x - d.width + b.x,
+      d = -d.x + b.x + b.width,
+      d = Math.abs(f) < Math.abs(d) ? f : d;
+    Math.abs(d) < Math.abs(e) ? e = 0 : d = 0;
+    f = c.getGeometry(a.cell);
+    null != f && (f = f.clone(), null != f.offset ? (f.offset.x += d, f.offset.y += e) : f.offset = new mxPoint(d, e), c.setGeometry(a.cell, f))
+  }
+};
+
+function mxGraphAbstractHierarchyCell() {
+  this.x = [];
+  this.y = [];
+  this.temp = []
+}
+mxGraphAbstractHierarchyCell.prototype.maxRank = -1;
+mxGraphAbstractHierarchyCell.prototype.minRank = -1;
+mxGraphAbstractHierarchyCell.prototype.x = null;
+mxGraphAbstractHierarchyCell.prototype.y = null;
+mxGraphAbstractHierarchyCell.prototype.width = 0;
+mxGraphAbstractHierarchyCell.prototype.height = 0;
+mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;
+mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;
+mxGraphAbstractHierarchyCell.prototype.temp = null;
+mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function(a) {
+  return null
+};
+mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function(a) {
+  return null
+};
+mxGraphAbstractHierarchyCell.prototype.isEdge = function() {
+  return !1
+};
+mxGraphAbstractHierarchyCell.prototype.isVertex = function() {
+  return !1
+};
+mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function(a) {
+  return null
+};
+mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function(a, b) {
+  return null
+};
+mxGraphAbstractHierarchyCell.prototype.setX = function(a, b) {
+  this.isVertex() ? this.x[0] = b : this.isEdge() && (this.x[a - this.minRank - 1] = b)
+};
+mxGraphAbstractHierarchyCell.prototype.getX = function(a) {
+  return this.isVertex() ? this.x[0] : this.isEdge() ? this.x[a - this.minRank - 1] : 0
+};
+mxGraphAbstractHierarchyCell.prototype.setY = function(a, b) {
+  this.isVertex() ? this.y[0] = b : this.isEdge() && (this.y[a - this.minRank - 1] = b)
+};
+
+function mxGraphHierarchyNode(a) {
+  mxGraphAbstractHierarchyCell.apply(this, arguments);
+  this.cell = a
+}
+mxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell;
+mxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;
+mxGraphHierarchyNode.prototype.cell = null;
+mxGraphHierarchyNode.prototype.connectsAsTarget = [];
+mxGraphHierarchyNode.prototype.connectsAsSource = [];
+mxGraphHierarchyNode.prototype.hashCode = !1;
+mxGraphHierarchyNode.prototype.getRankValue = function(a) {
+  return this.maxRank
+};
+mxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function(a) {
+  if (null == this.nextLayerConnectedCells) {
+    this.nextLayerConnectedCells = [];
+    this.nextLayerConnectedCells[0] = [];
+    for (var b = 0; b < this.connectsAsTarget.length; b++) {
+      var c = this.connectsAsTarget[b]; - 1 == c.maxRank || c.maxRank == a + 1 ? this.nextLayerConnectedCells[0].push(c.source) : this.nextLayerConnectedCells[0].push(c)
+    }
+  }
+  return this.nextLayerConnectedCells[0]
+};
+mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function(a) {
+  if (null == this.previousLayerConnectedCells) {
+    this.previousLayerConnectedCells = [];
+    this.previousLayerConnectedCells[0] = [];
+    for (var b = 0; b < this.connectsAsSource.length; b++) {
+      var c = this.connectsAsSource[b]; - 1 == c.minRank || c.minRank == a - 1 ? this.previousLayerConnectedCells[0].push(c.target) : this.previousLayerConnectedCells[0].push(c)
+    }
+  }
+  return this.previousLayerConnectedCells[0]
+};
+mxGraphHierarchyNode.prototype.isVertex = function() {
+  return !0
+};
+mxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function(a) {
+  return this.temp[0]
+};
+mxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function(a, b) {
+  this.temp[0] = b
+};
+mxGraphHierarchyNode.prototype.isAncestor = function(a) {
+  if (null != a && null != this.hashCode && null != a.hashCode && this.hashCode.length < a.hashCode.length) {
+    if (this.hashCode == a.hashCode) return !0;
+    if (null == this.hashCode || null == this.hashCode) return !1;
+    for (var b = 0; b < this.hashCode.length; b++)
+      if (this.hashCode[b] != a.hashCode[b]) return !1;
+    return !0
+  }
+  return !1
+};
+mxGraphHierarchyNode.prototype.getCoreCell = function() {
+  return this.cell
+};
+
+function mxGraphHierarchyEdge(a) {
+  mxGraphAbstractHierarchyCell.apply(this, arguments);
+  this.edges = a
+}
+mxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell;
+mxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;
+mxGraphHierarchyEdge.prototype.edges = null;
+mxGraphHierarchyEdge.prototype.source = null;
+mxGraphHierarchyEdge.prototype.target = null;
+mxGraphHierarchyEdge.prototype.isReversed = !1;
+mxGraphHierarchyEdge.prototype.invert = function(a) {
+  a = this.source;
+  this.source = this.target;
+  this.target = a;
+  this.isReversed = !this.isReversed
+};
+mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function(a) {
+  if (null == this.nextLayerConnectedCells) {
+    this.nextLayerConnectedCells = [];
+    for (var b = 0; b < this.temp.length; b++) this.nextLayerConnectedCells[b] = [], b == this.temp.length - 1 ? this.nextLayerConnectedCells[b].push(this.source) : this.nextLayerConnectedCells[b].push(
+      this)
+  }
+  return this.nextLayerConnectedCells[a - this.minRank - 1]
+};
+mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function(a) {
+  if (null == this.previousLayerConnectedCells) {
+    this.previousLayerConnectedCells = [];
+    for (var b = 0; b < this.temp.length; b++) this.previousLayerConnectedCells[b] = [], 0 == b ? this.previousLayerConnectedCells[b].push(this.target) : this.previousLayerConnectedCells[b].push(
+      this)
+  }
+  return this.previousLayerConnectedCells[a - this.minRank - 1]
+};
+mxGraphHierarchyEdge.prototype.isEdge = function() {
+  return !0
+};
+mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function(a) {
+  return this.temp[a - this.minRank - 1]
+};
+mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function(a, b) {
+  this.temp[a - this.minRank - 1] = b
+};
+mxGraphHierarchyEdge.prototype.getCoreCell = function() {
+  return null != this.edges && 0 < this.edges.length ? this.edges[0] : null
+};
+
+function mxGraphHierarchyModel(a, b, c, d, e) {
+  a.getGraph();
+  this.tightenToSource = e;
+  this.roots = c;
+  this.parent = d;
+  this.vertexMapper = {};
+  this.edgeMapper = {};
+  this.maxRank = 0;
+  c = [];
+  null == b && (b = this.graph.getChildVertices(d));
+  this.maxRank = this.SOURCESCANSTARTRANK;
+  this.createInternalCells(a, b, c);
+  for (d = 0; d < b.length; d++) {
+    e = c[d].connectsAsSource;
+    for (var f = 0; f < e.length; f++) {
+      var g = e[f],
+        h = g.edges;
+      if (null != h && 0 < h.length) {
+        var h = h[0],
+          k = a.getVisibleTerminal(h, !1),
+          k = mxCellPath.create(k),
+          k = this.vertexMapper[k];
+        c[d] == k && (k = a.getVisibleTerminal(h, !0), k = mxCellPath.create(k), k = this.vertexMapper[k]);
+        null != k && c[d] != k && (g.target = k, 0 == k.connectsAsTarget.length && (k.connectsAsTarget = []), 0 > mxUtils.indexOf(k.connectsAsTarget, g) && k.connectsAsTarget.push(g))
+      }
+    }
+    c[d].temp[0] = 1
+  }
+}
+mxGraphHierarchyModel.prototype.maxRank = null;
+mxGraphHierarchyModel.prototype.vertexMapper = null;
+mxGraphHierarchyModel.prototype.edgeMapper = null;
+mxGraphHierarchyModel.prototype.ranks = null;
+mxGraphHierarchyModel.prototype.roots = null;
+mxGraphHierarchyModel.prototype.parent = null;
+mxGraphHierarchyModel.prototype.dfsCount = 0;
+mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 1E8;
+mxGraphHierarchyModel.prototype.tightenToSource = !1;
+mxGraphHierarchyModel.prototype.createInternalCells = function(a, b, c) {
+  for (var d = a.getGraph(), e = 0; e < b.length; e++) {
+    c[e] = new mxGraphHierarchyNode(b[e]);
+    var f = mxCellPath.create(b[e]);
+    this.vertexMapper[f] = c[e];
+    f = a.getEdges(b[e]);
+    c[e].connectsAsSource = [];
+    for (var g = 0; g < f.length; g++) {
+      var h = a.getVisibleTerminal(f[g], !1);
+      if (h != b[e] && h != b[e] && a.graph.model.isVertex(h) && !a.isVertexIgnored(h)) {
+        var k = a.getEdgesBetween(b[e], h, !1),
+          l = a.getEdgesBetween(b[e], h, !0),
+          h = mxCellPath.create(k[0]);
+        if (null != k && 0 < k.length && null ==
+          this.edgeMapper[h] && 2 * l.length >= k.length) {
+          for (var l = new mxGraphHierarchyEdge(k), m = 0; m < k.length; m++) {
+            var n = k[m],
+              h = mxCellPath.create(n);
+            this.edgeMapper[h] = l;
+            d.resetEdge(n);
+            a.disableEdgeStyle && (a.setEdgeStyleEnabled(n, !1), a.setOrthogonalEdge(n, !0))
+          }
+          l.source = c[e];
+          0 > mxUtils.indexOf(c[e].connectsAsSource, l) && c[e].connectsAsSource.push(l)
+        }
+      }
+    }
+    c[e].temp[0] = 0
+  }
+};
+mxGraphHierarchyModel.prototype.initialRank = function() {
+  var a = [];
+  if (null != this.roots)
+    for (var b = 0; b < this.roots.length; b++) {
+      var c = mxCellPath.create(this.roots[b]),
+        c = this.vertexMapper[c];
+      null != c && a.push(c)
+    }
+  for (var d in this.vertexMapper) c = this.vertexMapper[d], c.temp[0] = -1;
+  for (var e = a.slice(); 0 < a.length;) {
+    var c = a[0],
+      f, g;
+    f = c.connectsAsTarget;
+    g = c.connectsAsSource;
+    for (var h = !0, k = this.SOURCESCANSTARTRANK, b = 0; b < f.length; b++) {
+      var l = f[b];
+      if (5270620 == l.temp[0]) l = l.source, k = Math.min(k, l.temp[0] - 1);
+      else {
+        h = !1;
+        break
+      }
+    }
+    if (h) {
+      c.temp[0] =
+        k;
+      this.maxRank = Math.min(this.maxRank, k);
+      if (null != g)
+        for (b = 0; b < g.length; b++) l = g[b], l.temp[0] = 5270620, l = l.target, -1 == l.temp[0] && (a.push(l), l.temp[0] = -2);
+      a.shift()
+    } else if (b = a.shift(), a.push(c), b == c && 1 == a.length) break
+  }
+  for (d in this.vertexMapper) c = this.vertexMapper[d], c.temp[0] -= this.maxRank;
+  for (b = 0; b < e.length; b++) {
+    c = e[b];
+    a = 0;
+    f = c.connectsAsSource;
+    for (d = 0; d < f.length; d++) l = f[d], l = l.target, c.temp[0] = Math.max(a, l.temp[0] + 1), a = c.temp[0]
+  }
+  this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank
+};
+mxGraphHierarchyModel.prototype.fixRanks = function() {
+  var a = [];
+  this.ranks = [];
+  for (var b = 0; b < this.maxRank + 1; b++) a[b] = [], this.ranks[b] = a[b];
+  var c = null;
+  if (null != this.roots)
+    for (var d = this.roots, c = [], b = 0; b < d.length; b++) {
+      var e = mxCellPath.create(d[b]);
+      c[b] = this.vertexMapper[e]
+    }
+  this.visit(function(b, c, d, e, l) {
+    0 == l && (0 > c.maxRank && 0 > c.minRank) && (a[c.temp[0]].push(c), c.maxRank = c.temp[0], c.minRank = c.temp[0], c.temp[0] = a[c.maxRank].length - 1);
+    if (null != b && null != d && 1 < b.maxRank - c.maxRank) {
+      d.maxRank = b.maxRank;
+      d.minRank =
+        c.maxRank;
+      d.temp = [];
+      d.x = [];
+      d.y = [];
+      for (b = d.minRank + 1; b < d.maxRank; b++) a[b].push(d), d.setGeneralPurposeVariable(b, a[b].length - 1)
+    }
+  }, c, !1, null)
+};
+mxGraphHierarchyModel.prototype.visit = function(a, b, c, d) {
+  if (null != b) {
+    for (var e = 0; e < b.length; e++) {
+      var f = b[e];
+      null != f && (null == d && (d = {}), c ? (f.hashCode = [], f.hashCode[0] = this.dfsCount, f.hashCode[1] = e, this.extendedDfs(null, f, null, a, d, f.hashCode, e, 0)) : this.dfs(null, f, null,
+        a, d, 0))
+    }
+    this.dfsCount++
+  }
+};
+mxGraphHierarchyModel.prototype.dfs = function(a, b, c, d, e, f) {
+  if (null != b) {
+    var g = mxCellPath.create(b.cell);
+    if (null == e[g]) {
+      e[g] = b;
+      d(a, b, c, f, 0);
+      a = b.connectsAsSource.slice();
+      for (c = 0; c < a.length; c++) g = a[c], this.dfs(b, g.target, g, d, e, f + 1)
+    } else d(a, b, c, f, 1)
+  }
+};
+mxGraphHierarchyModel.prototype.extendedDfs = function(a, b, c, d, e, f, g, h) {
+  if (null != b) {
+    if (null != a && (null == b.hashCode || b.hashCode[0] != a.hashCode[0])) f = a.hashCode.length + 1, b.hashCode = a.hashCode.slice(), b.hashCode[f - 1] = g;
+    g = mxCellPath.create(b.cell);
+    if (null == e[g]) {
+      e[g] = b;
+      d(a, b, c, h, 0);
+      a = b.connectsAsSource.slice();
+      for (c = 0; c < a.length; c++) g = a[c], this.extendedDfs(b, g.target, g, d, e, b.hashCode, c, h + 1)
+    } else d(a, b, c, h, 1)
+  }
+};
+
+function mxHierarchicalLayoutStage() {}
+mxHierarchicalLayoutStage.prototype.execute = function(a) {};
+
+function mxMedianHybridCrossingReduction(a) {
+  this.layout = a
+}
+mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage;
+mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;
+mxMedianHybridCrossingReduction.prototype.layout = null;
+mxMedianHybridCrossingReduction.prototype.maxIterations = 24;
+mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;
+mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;
+mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;
+mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;
+mxMedianHybridCrossingReduction.prototype.execute = function(a) {
+  a = this.layout.getModel();
+  this.nestedBestRanks = [];
+  for (var b = 0; b < a.ranks.length; b++) this.nestedBestRanks[b] = a.ranks[b].slice();
+  for (var c = 0, d = this.calculateCrossings(a), b = 0; b < this.maxIterations && c < this.maxNoImprovementIterations; b++) {
+    this.weightedMedian(b, a);
+    this.transpose(b, a);
+    var e = this.calculateCrossings(a);
+    if (e < d) {
+      d = e;
+      for (e = c = 0; e < this.nestedBestRanks.length; e++)
+        for (var f = a.ranks[e], g = 0; g < f.length; g++) {
+          var h = f[g];
+          this.nestedBestRanks[e][h.getGeneralPurposeVariable(e)] =
+            h
+        }
+    } else {
+      c++;
+      for (e = 0; e < this.nestedBestRanks.length; e++) {
+        f = a.ranks[e];
+        for (g = 0; g < f.length; g++) h = f[g], h.setGeneralPurposeVariable(e, g)
+      }
+    }
+    if (0 == d) break
+  }
+  c = [];
+  d = [];
+  for (b = 0; b < a.maxRank + 1; b++) d[b] = [], c[b] = d[b];
+  for (b = 0; b < this.nestedBestRanks.length; b++)
+    for (e = 0; e < this.nestedBestRanks[b].length; e++) d[b].push(this.nestedBestRanks[b][e]);
+  a.ranks = c
+};
+mxMedianHybridCrossingReduction.prototype.calculateCrossings = function(a) {
+  for (var b = a.ranks.length, c = 0, d = 1; d < b; d++) c += this.calculateRankCrossing(d, a);
+  return c
+};
+mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(a, b) {
+  for (var c = 0, d = b.ranks[a], e = d.length, f = b.ranks[a - 1].length, g = [], h = 0; h < e; h++) g[h] = [];
+  for (h = 0; h < d.length; h++)
+    for (var k = d[h], l = k.getGeneralPurposeVariable(a), m = k.getPreviousLayerConnectedCells(a), k = 0; k < m.length; k++) {
+      var n = m[k].getGeneralPurposeVariable(a - 1);
+      g[l][n] = 201207
+    }
+  for (h = 0; h < e; h++)
+    for (k = 0; k < f; k++)
+      if (201207 == g[h][k]) {
+        for (d = h + 1; d < e; d++)
+          for (l = 0; l < k; l++) 201207 == g[d][l] && c++;
+        for (d = 0; d < h; d++)
+          for (l = k + 1; l < f; l++) 201207 == g[d][l] &&
+            c++
+      }
+  return c / 2
+};
+mxMedianHybridCrossingReduction.prototype.transpose = function(a, b) {
+  for (var c = !0, d = 0; c && 10 > d++;)
+    for (var e = 1 == a % 2 && 1 == d % 2, c = !1, f = 0; f < b.ranks.length; f++) {
+      for (var g = b.ranks[f], h = [], k = 0; k < g.length; k++) {
+        var l = g[k],
+          m = l.getGeneralPurposeVariable(f);
+        0 > m && (m = k);
+        h[m] = l
+      }
+      for (var n = m = l = null, p = null, q = null, s = null, r = null, t = null, v = null, w = null, k = 0; k < g.length - 1; k++) {
+        if (0 == k) {
+          for (var v = h[k], l = v.getNextLayerConnectedCells(f), m = v.getPreviousLayerConnectedCells(f), q = [], s = [], u = 0; u < l.length; u++) q[u] = l[u].getGeneralPurposeVariable(f + 1);
+          for (u = 0; u < m.length; u++) s[u] = m[u].getGeneralPurposeVariable(f - 1)
+        } else l = n, m = p, q = r, s = t, v = w;
+        w = h[k + 1];
+        n = w.getNextLayerConnectedCells(f);
+        p = w.getPreviousLayerConnectedCells(f);
+        r = [];
+        t = [];
+        for (u = 0; u < n.length; u++) r[u] = n[u].getGeneralPurposeVariable(f + 1);
+        for (u = 0; u < p.length; u++) t[u] = p[u].getGeneralPurposeVariable(f - 1);
+        for (var y = 0, z = 0, u = 0; u < q.length; u++)
+          for (var x = 0; x < r.length; x++) q[u] > r[x] && y++, q[u] < r[x] && z++;
+        for (u = 0; u < s.length; u++)
+          for (x = 0; x < t.length; x++) s[u] > t[x] && y++, s[u] < t[x] && z++;
+        if (z < y || z == y && e) n = v.getGeneralPurposeVariable(f),
+          v.setGeneralPurposeVariable(f, w.getGeneralPurposeVariable(f)), w.setGeneralPurposeVariable(f, n), n = l, p = m, r = q, t = s, w = v, e || (c = !0)
+      }
+    }
+};
+mxMedianHybridCrossingReduction.prototype.weightedMedian = function(a, b) {
+  var c = 0 == a % 2;
+  if (c)
+    for (var d = b.maxRank - 1; 0 <= d; d--) this.medianRank(d, c);
+  else
+    for (d = 1; d < b.maxRank; d++) this.medianRank(d, c)
+};
+mxMedianHybridCrossingReduction.prototype.medianRank = function(a, b) {
+  for (var c = this.nestedBestRanks[a].length, d = [], e = [], f = 0; f < c; f++) {
+    var g = this.nestedBestRanks[a][f],
+      h = new MedianCellSorter;
+    h.cell = g;
+    var k;
+    k = b ? g.getNextLayerConnectedCells(a) : g.getPreviousLayerConnectedCells(a);
+    var l;
+    l = b ? a + 1 : a - 1;
+    null != k && 0 != k.length ? (h.medianValue = this.medianValue(k, l), d.push(h)) : e[g.getGeneralPurposeVariable(a)] = !0
+  }
+  d.sort(MedianCellSorter.prototype.compare);
+  for (f = 0; f < c; f++) null == e[f] && (g = d.shift().cell, g.setGeneralPurposeVariable(a,
+    f))
+};
+mxMedianHybridCrossingReduction.prototype.medianValue = function(a, b) {
+  for (var c = [], d = 0, e = 0; e < a.length; e++) {
+    var f = a[e];
+    c[d++] = f.getGeneralPurposeVariable(b)
+  }
+  c.sort(function(a, b) {
+    return a - b
+  });
+  if (1 == d % 2) return c[Math.floor(d / 2)];
+  if (2 == d) return (c[0] + c[1]) / 2;
+  e = d / 2;
+  f = c[e - 1] - c[0];
+  d = c[d - 1] - c[e];
+  return (c[e - 1] * d + c[e] * f) / (f + d)
+};
+
+function MedianCellSorter() {}
+MedianCellSorter.prototype.medianValue = 0;
+MedianCellSorter.prototype.cell = !1;
+MedianCellSorter.prototype.compare = function(a, b) {
+  return null != a && null != b ? b.medianValue > a.medianValue ? -1 : b.medianValue < a.medianValue ? 1 : 0 : 0
+};
+
+function mxMinimumCycleRemover(a) {
+  this.layout = a
+}
+mxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage;
+mxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;
+mxMinimumCycleRemover.prototype.layout = null;
+mxMinimumCycleRemover.prototype.execute = function(a) {
+  var b = this.layout.getModel(),
+    c = {},
+    d = mxUtils.clone(b.vertexMapper, null, !0),
+    e = null;
+  if (null != b.roots) {
+    var f = b.roots,
+      e = [];
+    for (a = 0; a < f.length; a++) {
+      var g = mxCellPath.create(f[a]);
+      e[a] = b.vertexMapper[g]
+    }
+  }
+  b.visit(function(a, b, e, f, g) {
+    b.isAncestor(a) && (e.invert(), mxUtils.remove(e, a.connectsAsSource), a.connectsAsTarget.push(e), mxUtils.remove(e, b.connectsAsTarget), b.connectsAsSource.push(e));
+    a = mxCellPath.create(b.cell);
+    c[a] = b;
+    delete d[a]
+  }, e, !0, null);
+  e = null;
+  0 <
+    d.lenth && (e = mxUtils.clone(d, null, !0));
+  a = mxUtils.clone(c, null, !0);
+  b.visit(function(a, b, e, f, g) {
+    b.isAncestor(a) && (e.invert(), mxUtils.remove(e, a.connectsAsSource), b.connectsAsSource.push(e), a.connectsAsTarget.push(e), mxUtils.remove(e, b.connectsAsTarget));
+    a = mxCellPath.create(b.cell);
+    c[a] = b;
+    delete d[a]
+  }, d, !0, a);
+  f = this.layout.getGraph();
+  if (null != e && 0 < e.length) {
+    b = b.roots;
+    for (a = 0; a < e.length; a++) g = e[a].cell, 0 == f.getIncomingEdges(g).length && b.push(g)
+  }
+};
+
+function mxCoordinateAssignment(a, b, c, d, e, f) {
+  this.layout = a;
+  this.intraCellSpacing = b;
+  this.interRankCellSpacing = c;
+  this.orientation = d;
+  this.initialX = e;
+  this.parallelEdgeSpacing = f
+}
+var mxHierarchicalEdgeStyle = {
+  ORTHOGONAL: 1,
+  POLYLINE: 2,
+  STRAIGHT: 3,
+  CURVE: 4
+};
+mxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage;
+mxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;
+mxCoordinateAssignment.prototype.layout = null;
+mxCoordinateAssignment.prototype.intraCellSpacing = 30;
+mxCoordinateAssignment.prototype.interRankCellSpacing = 100;
+mxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;
+mxCoordinateAssignment.prototype.maxIterations = 8;
+mxCoordinateAssignment.prototype.prefHozEdgeSep = 5;
+mxCoordinateAssignment.prototype.prefVertEdgeOff = 2;
+mxCoordinateAssignment.prototype.minEdgeJetty = 12;
+mxCoordinateAssignment.prototype.channelBuffer = 4;
+mxCoordinateAssignment.prototype.jettyPositions = null;
+mxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;
+mxCoordinateAssignment.prototype.initialX = null;
+mxCoordinateAssignment.prototype.limitX = null;
+mxCoordinateAssignment.prototype.currentXDelta = null;
+mxCoordinateAssignment.prototype.widestRank = null;
+mxCoordinateAssignment.prototype.rankTopY = null;
+mxCoordinateAssignment.prototype.rankBottomY = null;
+mxCoordinateAssignment.prototype.widestRankValue = null;
+mxCoordinateAssignment.prototype.rankWidths = null;
+mxCoordinateAssignment.prototype.rankY = null;
+mxCoordinateAssignment.prototype.fineTuning = !0;
+mxCoordinateAssignment.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+mxCoordinateAssignment.prototype.nextLayerConnectedCache = null;
+mxCoordinateAssignment.prototype.previousLayerConnectedCache = null;
+mxCoordinateAssignment.prototype.groupPadding = 10;
+mxCoordinateAssignment.prototype.printStatus = function() {
+  var a = this.layout.getModel();
+  mxLog.show();
+  mxLog.writeln("\x3d\x3d\x3d\x3d\x3d\x3dCoord assignment debug\x3d\x3d\x3d\x3d\x3d\x3d\x3d");
+  for (var b = 0; b < a.ranks.length; b++) {
+    mxLog.write("Rank ", b, " : ");
+    for (var c = a.ranks[b], d = 0; d < c.length; d++) mxLog.write(c[d].getGeneralPurposeVariable(b), "  ");
+    mxLog.writeln()
+  }
+  mxLog.writeln("\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d")
+};
+mxCoordinateAssignment.prototype.execute = function(a) {
+  this.jettyPositions = [];
+  a = this.layout.getModel();
+  this.currentXDelta = 0;
+  this.initialCoords(this.layout.getGraph(), a);
+  this.fineTuning && this.minNode(a);
+  var b = 1E8;
+  if (this.fineTuning)
+    for (var c = 0; c < this.maxIterations; c++) {
+      0 != c && (this.medianPos(c, a), this.minNode(a));
+      if (this.currentXDelta < b) {
+        for (var d = 0; d < a.ranks.length; d++)
+          for (var e = a.ranks[d], f = 0; f < e.length; f++) {
+            var g = e[f];
+            g.setX(d, g.getGeneralPurposeVariable(d))
+          }
+        b = this.currentXDelta
+      } else
+        for (d = 0; d < a.ranks.length; d++) {
+          e =
+            a.ranks[d];
+          for (f = 0; f < e.length; f++) g = e[f], g.setGeneralPurposeVariable(d, g.getX(d))
+        }
+      this.minPath(this.layout.getGraph(), a);
+      this.currentXDelta = 0
+    }
+  this.setCellLocations(this.layout.getGraph(), a)
+};
+mxCoordinateAssignment.prototype.minNode = function(a) {
+  for (var b = [], c = [], d = [], e = 0; e <= a.maxRank; e++) {
+    d[e] = a.ranks[e];
+    for (var f = 0; f < d[e].length; f++) {
+      var g = d[e][f],
+        h = new WeightedCellSorter(g, e);
+      h.rankIndex = f;
+      h.visited = !0;
+      b.push(h);
+      g = mxCellPath.create(g.getCoreCell());
+      c[g] = h
+    }
+  }
+  a = 10 * b.length;
+  for (f = 0; 0 < b.length && f <= a;) {
+    var h = b.shift(),
+      e = h.cell,
+      k = h.weightedValue,
+      l = parseInt(h.rankIndex),
+      g = e.getNextLayerConnectedCells(k),
+      m = e.getPreviousLayerConnectedCells(k),
+      n = g.length,
+      p = m.length,
+      q = this.medianXValue(g, k + 1),
+      s =
+      this.medianXValue(m, k - 1),
+      r = n + p,
+      t = e.getGeneralPurposeVariable(k),
+      v = t;
+    0 < r && (v = (q * n + s * p) / r);
+    n = !1;
+    v < t - 1 ? 0 == l ? (e.setGeneralPurposeVariable(k, v), n = !0) : (l = d[k][l - 1], t = l.getGeneralPurposeVariable(k), t = t + l.width / 2 + this.intraCellSpacing + e.width / 2, t < v ? (e.setGeneralPurposeVariable(
+      k, v), n = !0) : t < e.getGeneralPurposeVariable(k) - 1 && (e.setGeneralPurposeVariable(k, t), n = !0)) : v > t + 1 && (l == d[k].length - 1 ? (e.setGeneralPurposeVariable(k, v), n = !0) :
+      (l = d[k][l + 1], t = l.getGeneralPurposeVariable(k), t = t - l.width / 2 - this.intraCellSpacing -
+        e.width / 2, t > v ? (e.setGeneralPurposeVariable(k, v), n = !0) : t > e.getGeneralPurposeVariable(k) + 1 && (e.setGeneralPurposeVariable(k, t), n = !0)));
+    if (n) {
+      for (e = 0; e < g.length; e++) k = g[e], k = mxCellPath.create(k.getCoreCell()), k = c[k], null != k && !1 == k.visited && (k.visited = !0, b.push(k));
+      for (e = 0; e < m.length; e++) k = m[e], k = mxCellPath.create(k.getCoreCell()), k = c[k], null != k && !1 == k.visited && (k.visited = !0, b.push(k))
+    }
+    h.visited = !1;
+    f++
+  }
+};
+mxCoordinateAssignment.prototype.medianPos = function(a, b) {
+  if (0 == a % 2)
+    for (var c = b.maxRank; 0 < c; c--) this.rankMedianPosition(c - 1, b, c);
+  else
+    for (c = 0; c < b.maxRank - 1; c++) this.rankMedianPosition(c + 1, b, c)
+};
+mxCoordinateAssignment.prototype.rankMedianPosition = function(a, b, c) {
+  b = b.ranks[a];
+  for (var d = [], e = [], f = 0; f < b.length; f++) {
+    var g = b[f];
+    d[f] = new WeightedCellSorter;
+    d[f].cell = g;
+    d[f].rankIndex = f;
+    var h = mxCellPath.create(g.getCoreCell());
+    e[h] = d[f];
+    var k = null,
+      k = c < a ? g.getPreviousLayerConnectedCells(a) : g.getNextLayerConnectedCells(a);
+    d[f].weightedValue = this.calculatedWeightedValue(g, k)
+  }
+  d.sort(WeightedCellSorter.prototype.compare);
+  for (f = 0; f < d.length; f++) {
+    h = 0;
+    g = d[f].cell;
+    h = 0;
+    k = c < a ? g.getPreviousLayerConnectedCells(a).slice() :
+      g.getNextLayerConnectedCells(a).slice();
+    null != k && (h = k.length, h = 0 < h ? this.medianXValue(k, c) : g.getGeneralPurposeVariable(a));
+    for (var l = 0, k = -1E8, m = d[f].rankIndex - 1; 0 <= m;) {
+      var n = mxCellPath.create(b[m].getCoreCell()),
+        n = e[n];
+      if (null != n) {
+        var p = n.cell;
+        n.visited ? (k = p.getGeneralPurposeVariable(a) + p.width / 2 + this.intraCellSpacing + l + g.width / 2, m = -1) : (l += p.width + this.intraCellSpacing, m--)
+      }
+    }
+    l = 0;
+    p = 1E8;
+    for (m = d[f].rankIndex + 1; m < d.length;)
+      if (n = mxCellPath.create(b[m].getCoreCell()), n = e[n], null != n) {
+        var q = n.cell;
+        n.visited ?
+          (p = q.getGeneralPurposeVariable(a) - q.width / 2 - this.intraCellSpacing - l - g.width / 2, m = d.length) : (l += q.width + this.intraCellSpacing, m++)
+      }
+    h >= k && h <= p ? g.setGeneralPurposeVariable(a, h) : h < k ? (g.setGeneralPurposeVariable(a, k), this.currentXDelta += k - h) : h > p && (g.setGeneralPurposeVariable(a, p), this.currentXDelta +=
+      h - p);
+    d[f].visited = !0
+  }
+};
+mxCoordinateAssignment.prototype.calculatedWeightedValue = function(a, b) {
+  for (var c = 0, d = 0; d < b.length; d++) {
+    var e = b[d];
+    a.isVertex() && e.isVertex() ? c++ : c = a.isEdge() && e.isEdge() ? c + 8 : c + 2
+  }
+  return c
+};
+mxCoordinateAssignment.prototype.medianXValue = function(a, b) {
+  if (0 == a.length) return 0;
+  for (var c = [], d = 0; d < a.length; d++) c[d] = a[d].getGeneralPurposeVariable(b);
+  c.sort(function(a, b) {
+    return a - b
+  });
+  if (1 == a.length % 2) return c[Math.floor(a.length / 2)];
+  d = a.length / 2;
+  return (c[d - 1] + c[d]) / 2
+};
+mxCoordinateAssignment.prototype.initialCoords = function(a, b) {
+  this.calculateWidestRank(a, b);
+  for (var c = this.widestRank; 0 <= c; c--) c < b.maxRank && this.rankCoordinates(c, a, b);
+  for (c = this.widestRank + 1; c <= b.maxRank; c++) 0 < c && this.rankCoordinates(c, a, b)
+};
+mxCoordinateAssignment.prototype.rankCoordinates = function(a, b, c) {
+  b = c.ranks[a];
+  c = 0;
+  for (var d = this.initialX + (this.widestRankValue - this.rankWidths[a]) / 2, e = !1, f = 0; f < b.length; f++) {
+    var g = b[f];
+    if (g.isVertex()) {
+      var h = this.layout.getVertexBounds(g.cell);
+      null != h ? this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH ? (g.width = h.width, g.height = h.height) : (g.width = h.height, g.height = h.width) :
+        e = !0;
+      c = Math.max(c, g.height)
+    } else g.isEdge() && (h = 1, null != g.edges ? h = g.edges.length : mxLog.warn("edge.edges is null"),
+      g.width = (h - 1) * this.parallelEdgeSpacing);
+    d += g.width / 2;
+    g.setX(a, d);
+    g.setGeneralPurposeVariable(a, d);
+    d += g.width / 2;
+    d += this.intraCellSpacing
+  }!0 == e && mxLog.warn("At least one cell has no bounds")
+};
+mxCoordinateAssignment.prototype.calculateWidestRank = function(a, b) {
+  var c = -this.interRankCellSpacing,
+    d = 0;
+  this.rankWidths = [];
+  this.rankY = [];
+  for (var e = b.maxRank; 0 <= e; e--) {
+    for (var f = 0, g = b.ranks[e], h = this.initialX, k = !1, l = 0; l < g.length; l++) {
+      var m = g[l];
+      if (m.isVertex()) {
+        var n = this.layout.getVertexBounds(m.cell);
+        null != n ? this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH ? (m.width = n.width, m.height = n.height) : (m.width = n.height, m.height = n
+          .width) : k = !0;
+        f = Math.max(f, m.height)
+      } else m.isEdge() &&
+        (n = 1, null != m.edges ? n = m.edges.length : mxLog.warn("edge.edges is null"), m.width = (n - 1) * this.parallelEdgeSpacing);
+      h += m.width / 2;
+      m.setX(e, h);
+      m.setGeneralPurposeVariable(e, h);
+      h += m.width / 2;
+      h += this.intraCellSpacing;
+      h > this.widestRankValue && (this.widestRankValue = h, this.widestRank = e);
+      this.rankWidths[e] = h
+    }!0 == k && mxLog.warn("At least one cell has no bounds");
+    this.rankY[e] = c;
+    h = f / 2 + d / 2 + this.interRankCellSpacing;
+    d = f;
+    c = this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_WEST ? c + h : c -
+      h;
+    for (l = 0; l < g.length; l++) g[l].setY(e, c)
+  }
+};
+mxCoordinateAssignment.prototype.minPath = function(a, b) {
+  var c = b.edgeMapper,
+    d;
+  for (d in c) {
+    var e = c[d];
+    if (!(1 > e.maxRank - e.minRank - 1)) {
+      for (var f = e.getGeneralPurposeVariable(e.minRank + 1), g = !0, h = 0, k = e.minRank + 2; k < e.maxRank; k++) {
+        var l = e.getGeneralPurposeVariable(k);
+        f != l ? (g = !1, f = l) : h++
+      }
+      if (!g) {
+        for (var g = f = 0, l = [], m = [], n = e.getGeneralPurposeVariable(e.minRank + 1), k = e.minRank + 1; k < e.maxRank - 1; k++) {
+          var p = e.getX(k + 1);
+          n == p ? (l[k - e.minRank - 1] = n, f++) : this.repositionValid(b, e, k + 1, n) ? (l[k - e.minRank - 1] = n, f++) : n = l[k - e.minRank -
+            1] = p
+        }
+        n = e.getX(k);
+        for (k = e.maxRank - 1; k > e.minRank + 1; k--) p = e.getX(k - 1), n == p ? (m[k - e.minRank - 2] = n, g++) : this.repositionValid(b, e, k - 1, n) ? (m[k - e.minRank - 2] = n, g++) : (m[k -
+          e.minRank - 2] = e.getX(k - 1), n = p);
+        if (g > h || f > h)
+          if (g >= f)
+            for (k = e.maxRank - 2; k > e.minRank; k--) e.setX(k, m[k - e.minRank - 1]);
+          else if (f > g)
+          for (k = e.minRank + 2; k < e.maxRank; k++) e.setX(k, l[k - e.minRank - 2])
+      }
+    }
+  }
+};
+mxCoordinateAssignment.prototype.repositionValid = function(a, b, c, d) {
+  a = a.ranks[c];
+  for (var e = -1, f = 0; f < a.length; f++)
+    if (b == a[f]) {
+      e = f;
+      break
+    }
+  if (0 > e) return !1;
+  f = b.getGeneralPurposeVariable(c);
+  if (d < f) {
+    if (0 == e) return !0;
+    a = a[e - 1];
+    c = a.getGeneralPurposeVariable(c);
+    c = c + a.width / 2 + this.intraCellSpacing + b.width / 2;
+    if (!(c <= d)) return !1
+  } else if (d > f) {
+    if (e == a.length - 1) return !0;
+    a = a[e + 1];
+    c = a.getGeneralPurposeVariable(c);
+    c = c - a.width / 2 - this.intraCellSpacing - b.width / 2;
+    if (!(c >= d)) return !1
+  }
+  return !0
+};
+mxCoordinateAssignment.prototype.setCellLocations = function(a, b) {
+  this.rankTopY = [];
+  this.rankBottomY = [];
+  for (var c = 0; c < b.ranks.length; c++) this.rankTopY[c] = Number.MAX_VALUE, this.rankBottomY[c] = 0;
+  c = null;
+  this.layout.resizeParent && (c = {});
+  var d = b.edgeMapper,
+    e = b.vertexMapper,
+    f;
+  for (f in e) {
+    var g = e[f];
+    this.setVertexLocation(g);
+    if (this.layout.resizeParent) {
+      var g = a.model.getParent(g.cell),
+        h = mxCellPath.create(g);
+      null == c[h] && (c[h] = g)
+    }
+  }
+  this.layout.resizeParent && null != c && this.adjustParents(c);
+  (this.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL ||
+    this.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE || this.edgeStyle == mxHierarchicalEdgeStyle.CURVE) && this.localEdgeProcessing(b);
+  for (f in d) this.setEdgePosition(d[f])
+};
+mxCoordinateAssignment.prototype.adjustParents = function(a) {
+  var b = [],
+    c;
+  for (c in a) b.push(a[c]);
+  this.layout.arrangeGroups(mxUtils.sortCells(b, !0), this.groupPadding)
+};
+mxCoordinateAssignment.prototype.localEdgeProcessing = function(a) {
+  for (var b = 0; b < a.ranks.length; b++)
+    for (var c = a.ranks[b], d = 0; d < c.length; d++) {
+      var e = c[d];
+      if (e.isVertex())
+        for (var f = e.getPreviousLayerConnectedCells(b), g = b - 1, h = 0; 2 > h; h++) {
+          if (-1 < g && g < a.ranks.length && null != f && 0 < f.length) {
+            for (var k = [], l = 0; l < f.length; l++) {
+              var m = new WeightedCellSorter(f[l], f[l].getX(g));
+              k.push(m)
+            }
+            k.sort(WeightedCellSorter.prototype.compare);
+            for (var m = e.x[0] - e.width / 2, n = m + e.width, p = f = 0, g = [], l = 0; l < k.length; l++) {
+              var q = k[l].cell,
+                s;
+              if (q.isVertex()) {
+                s = 0 == h ? e.connectsAsSource : e.connectsAsTarget;
+                for (var r = 0; r < s.length; r++)
+                  if (s[r].source == q || s[r].target == q) f += s[r].edges.length, p++, g.push(s[r])
+              } else f += q.edges.length, p++, g.push(q)
+            }
+            e.width > (f + 1) * this.prefHozEdgeSep + 2 * this.prefHozEdgeSep && (m += this.prefHozEdgeSep, n -= this.prefHozEdgeSep);
+            k = (n - m) / f;
+            m += k / 2;
+            n = this.minEdgeJetty - this.prefVertEdgeOff;
+            for (l = p = 0; l < g.length; l++) {
+              q = g[l].edges.length;
+              r = mxCellPath.create(g[l].edges[0]);
+              s = this.jettyPositions[r];
+              null == s && (s = [], this.jettyPositions[r] =
+                s);
+              l < f / 2 ? n += this.prefVertEdgeOff : l > f / 2 && (n -= this.prefVertEdgeOff);
+              for (r = 0; r < q; r++) s[4 * r + 2 * h] = m, m += k, s[4 * r + 2 * h + 1] = n;
+              p = Math.max(p, n)
+            }
+          }
+          f = e.getNextLayerConnectedCells(b);
+          g = b + 1
+        }
+    }
+};
+mxCoordinateAssignment.prototype.setEdgePosition = function(a) {
+  var b = 0;
+  if (101207 != a.temp[0]) {
+    var c = a.maxRank,
+      d = a.minRank;
+    c == d && (c = a.source.maxRank, d = a.target.minRank);
+    for (var e = 0, f = mxCellPath.create(a.edges[0]), f = this.jettyPositions[f], g = a.isReversed ? a.target.cell : a.source.cell, h = this.layout.graph, k = 0; k < a.edges.length; k++) {
+      var l = a.edges[k],
+        m = this.layout.getVisibleTerminal(l, !0),
+        n = h.model.getTerminal(l, !0),
+        p = [],
+        q = a.isReversed;
+      m != g && (q = !q);
+      if (null != f) {
+        var s = q ? 2 : 0,
+          r = q ? this.rankTopY[d] : this.rankBottomY[c],
+          t = f[4 * e + 1 + s];
+        q && (t = -t);
+        r += t;
+        s = f[4 * e + s];
+        n = h.model.getTerminal(l, !0);
+        this.layout.isPort(n) && h.model.getParent(n) == m && (s = h.view.getState(n), s = null != s ? s.x : m.geometry.x + a.source.width * n.geometry.x);
+        this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH ? (p.push(new mxPoint(s, r)), this.edgeStyle == mxHierarchicalEdgeStyle.CURVE && p.push(
+          new mxPoint(s, r + t))) : (p.push(new mxPoint(r, s)), this.edgeStyle == mxHierarchicalEdgeStyle.CURVE && p.push(new mxPoint(r + t, s)))
+      }
+      s = a.x.length -
+        1;
+      r = t = -1;
+      m = a.maxRank - 1;
+      q && (s = 0, t = a.x.length, r = 1, m = a.minRank + 1);
+      for (; a.maxRank != a.minRank && s != t; s += r) {
+        var n = a.x[s] + b,
+          v = (this.rankTopY[m] + this.rankBottomY[m + 1]) / 2,
+          w = (this.rankTopY[m - 1] + this.rankBottomY[m]) / 2;
+        if (q) var u = v,
+          v = w,
+          w = u;
+        this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH ? (p.push(new mxPoint(n, v)), p.push(new mxPoint(n, w))) : (p.push(new mxPoint(v, n)), p.push(
+          new mxPoint(w, n)));
+        this.limitX = Math.max(this.limitX, n);
+        m += r
+      }
+      null != f && (s = q ? 2 : 0, r = q ? this.rankBottomY[c] :
+        this.rankTopY[d], t = f[4 * e + 3 - s], q && (t = -t), r -= t, s = f[4 * e + 2 - s], q = h.model.getTerminal(l, !1), m = this.layout.getVisibleTerminal(l, !1), this.layout.isPort(q) && h.model
+        .getParent(q) == m && (s = h.view.getState(q), s = null != s ? s.x : m.geometry.x + a.target.width * q.geometry.x), this.orientation == mxConstants.DIRECTION_NORTH || this.orientation ==
+        mxConstants.DIRECTION_SOUTH ? (this.edgeStyle == mxHierarchicalEdgeStyle.CURVE && p.push(new mxPoint(s, r - t)), p.push(new mxPoint(s, r))) : (this.edgeStyle == mxHierarchicalEdgeStyle.CURVE &&
+          p.push(new mxPoint(r -
+            t, s)), p.push(new mxPoint(r, s))));
+      a.isReversed && this.processReversedEdge(a, l);
+      this.layout.setEdgePoints(l, p);
+      b = 0 == b ? this.parallelEdgeSpacing : 0 < b ? -b : -b + this.parallelEdgeSpacing;
+      e++
+    }
+    a.temp[0] = 101207
+  }
+};
+mxCoordinateAssignment.prototype.setVertexLocation = function(a) {
+  var b = a.cell,
+    c = a.x[0] - a.width / 2,
+    d = a.y[0] - a.height / 2;
+  this.rankTopY[a.minRank] = Math.min(this.rankTopY[a.minRank], d);
+  this.rankBottomY[a.minRank] = Math.max(this.rankBottomY[a.minRank], d + a.height);
+  this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH ? this.layout.setVertexLocation(b, c, d) : this.layout.setVertexLocation(b, d, c);
+  this.limitX = Math.max(this.limitX, c + a.width)
+};
+mxCoordinateAssignment.prototype.processReversedEdge = function(a, b) {};
+
+function WeightedCellSorter(a, b) {
+  this.cell = a;
+  this.weightedValue = b
+}
+WeightedCellSorter.prototype.weightedValue = 0;
+WeightedCellSorter.prototype.nudge = !1;
+WeightedCellSorter.prototype.visited = !1;
+WeightedCellSorter.prototype.rankIndex = null;
+WeightedCellSorter.prototype.cell = null;
+WeightedCellSorter.prototype.compare = function(a, b) {
+  return null != a && null != b ? b.weightedValue > a.weightedValue ? -1 : b.weightedValue < a.weightedValue ? 1 : b.nudge ? -1 : 1 : 0
+};
+
+function mxHierarchicalLayout(a, b, c) {
+  mxGraphLayout.call(this, a);
+  this.orientation = null != b ? b : mxConstants.DIRECTION_NORTH;
+  this.deterministic = null != c ? c : !0
+}
+mxHierarchicalLayout.prototype = new mxGraphLayout;
+mxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;
+mxHierarchicalLayout.prototype.roots = null;
+mxHierarchicalLayout.prototype.resizeParent = !1;
+mxHierarchicalLayout.prototype.moveParent = !1;
+mxHierarchicalLayout.prototype.parentBorder = 0;
+mxHierarchicalLayout.prototype.intraCellSpacing = 30;
+mxHierarchicalLayout.prototype.interRankCellSpacing = 100;
+mxHierarchicalLayout.prototype.interHierarchySpacing = 60;
+mxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;
+mxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+mxHierarchicalLayout.prototype.fineTuning = !0;
+mxHierarchicalLayout.prototype.tightenToSource = !0;
+mxHierarchicalLayout.prototype.disableEdgeStyle = !0;
+mxHierarchicalLayout.prototype.traverseAncestors = !0;
+mxHierarchicalLayout.prototype.model = null;
+mxHierarchicalLayout.prototype.edgesCache = null;
+mxHierarchicalLayout.prototype.getModel = function() {
+  return this.model
+};
+mxHierarchicalLayout.prototype.execute = function(a, b) {
+  this.parent = a;
+  var c = this.graph.model;
+  this.edgesCache = {};
+  if (!(null == b && null == a)) {
+    if (null != b && null != a) {
+      for (var d = [], e = 0; e < b.length; e++) c.isAncestor(a, b[e]) && d.push(b[e]);
+      this.roots = d
+    } else this.roots = b;
+    c.beginUpdate();
+    try {
+      this.run(a), this.resizeParent && !this.graph.isCellCollapsed(a) && this.graph.updateGroupBounds([a], this.parentBorder, this.moveParent)
+    } finally {
+      c.endUpdate()
+    }
+  }
+};
+mxHierarchicalLayout.prototype.findRoots = function(a, b) {
+  var c = [];
+  if (null != a && null != b) {
+    var d = this.graph.model,
+      e = null,
+      f = -1E5,
+      g;
+    for (g in b) {
+      var h = b[g];
+      if (d.isVertex(h) && this.graph.isCellVisible(h)) {
+        for (var k = this.getEdges(h), l = 0, m = 0, n = 0; n < k.length; n++) this.getVisibleTerminal(k[n], !0) == h ? l++ : m++;
+        0 == m && 0 < l && c.push(h);
+        k = l - m;
+        k > f && (f = k, e = h)
+      }
+    }
+    0 == c.length && null != e && c.push(e)
+  }
+  return c
+};
+mxHierarchicalLayout.prototype.getEdges = function(a) {
+  var b = mxCellPath.create(a);
+  if (null != this.edgesCache[b]) return this.edgesCache[b];
+  for (var c = this.graph.model, d = [], e = this.graph.isCellCollapsed(a), f = c.getChildCount(a), g = 0; g < f; g++) {
+    var h = c.getChildAt(a, g);
+    if (this.isPort(h)) d = d.concat(c.getEdges(h, !0, !0));
+    else if (e || !this.graph.isCellVisible(h)) d = d.concat(c.getEdges(h, !0, !0))
+  }
+  d = d.concat(c.getEdges(a, !0, !0));
+  c = [];
+  for (g = 0; g < d.length; g++) e = this.getVisibleTerminal(d[g], !0), f = this.getVisibleTerminal(d[g], !1), (e == f || e != f && (f == a && (null == this.parent || this.graph.isValidAncestor(e,
+    this.parent, this.traverseAncestors)) || e == a && (null == this.parent || this.graph.isValidAncestor(f, this.parent, this.traverseAncestors)))) && c.push(d[g]);
+  return this.edgesCache[b] = c
+};
+mxHierarchicalLayout.prototype.getVisibleTerminal = function(a, b) {
+  var c = this.graph.view.getState(a),
+    c = null != c ? c.getVisibleTerminal(b) : this.graph.view.getVisibleTerminal(a, b);
+  this.isPort(c) && (c = this.graph.model.getParent(c));
+  return c
+};
+mxHierarchicalLayout.prototype.run = function(a) {
+  var b = [],
+    c = [];
+  if (null == this.roots && null != a) {
+    var d = {};
+    this.filterDescendants(a, d);
+    this.roots = [];
+    var e = !0,
+      f;
+    for (f in d)
+      if (null != d[f]) {
+        e = !1;
+        break
+      }
+    for (; !e;) {
+      for (var g = this.findRoots(a, d), e = 0; e < g.length; e++) {
+        var h = [];
+        b.push(h);
+        this.traverse(g[e], !0, null, c, h, b, d)
+      }
+      for (e = 0; e < g.length; e++) this.roots.push(g[e]);
+      e = !0;
+      for (f in d)
+        if (null != d[f]) {
+          e = !1;
+          break
+        }
+    }
+  } else
+    for (e = 0; e < roots.length; e++) h = [], b.push(h), traverse(roots.get(e), !0, null, c, h, b, null);
+  for (e = c = 0; e < b.length; e++) {
+    h =
+      b[e];
+    d = [];
+    for (f in h) d.push(h[f]);
+    this.model = new mxGraphHierarchyModel(this, d, this.roots, a, this.tightenToSource);
+    this.cycleStage(a);
+    this.layeringStage();
+    this.crossingStage(a);
+    c = this.placementStage(c, a)
+  }
+};
+mxHierarchicalLayout.prototype.filterDescendants = function(a, b) {
+  var c = this.graph.model;
+  c.isVertex(a) && (a != this.parent && this.graph.isCellVisible(a)) && (b[mxCellPath.create(a)] = a);
+  if (this.traverseAncestors || a == this.parent && this.graph.isCellVisible(a))
+    for (var d = c.getChildCount(a), e = 0; e < d; e++) {
+      var f = c.getChildAt(a, e);
+      this.isPort(f) || this.filterDescendants(f, b)
+    }
+};
+mxHierarchicalLayout.prototype.isPort = function(a) {
+  return a.geometry.relative ? !0 : !1
+};
+mxHierarchicalLayout.prototype.getEdgesBetween = function(a, b, c) {
+  c = null != c ? c : !1;
+  for (var d = this.getEdges(a), e = [], f = 0; f < d.length; f++) {
+    var g = this.getVisibleTerminal(d[f], !0),
+      h = this.getVisibleTerminal(d[f], !1);
+    (g == a && h == b || !c && g == b && h == a) && e.push(d[f])
+  }
+  return e
+};
+mxHierarchicalLayout.prototype.traverse = function(a, b, c, d, e, f, g) {
+  if (null != a && null != d) {
+    var h = mxCellPath.create(a);
+    if (null == d[h] && (null == g || null != g[h])) {
+      null == e[h] && (e[h] = a);
+      null == d[h] && (d[h] = a);
+      delete g[h];
+      var k = this.getEdges(a);
+      for (c = 0; c < k.length; c++)
+        if (h = this.getVisibleTerminal(k[c], !0) == a, !b || h) h = this.getVisibleTerminal(k[c], !h), e = this.traverse(h, b, k[c], d, e, f, g)
+    } else if (null == e[h])
+      for (c = 0; c < f.length; c++)
+        if (a = f[c], null != a[h]) {
+          for (k in e) a[k] = e[k];
+          f.pop();
+          return a
+        }
+  }
+  return e
+};
+mxHierarchicalLayout.prototype.cycleStage = function(a) {
+  (new mxMinimumCycleRemover(this)).execute(a)
+};
+mxHierarchicalLayout.prototype.layeringStage = function() {
+  this.model.initialRank();
+  this.model.fixRanks()
+};
+mxHierarchicalLayout.prototype.crossingStage = function(a) {
+  (new mxMedianHybridCrossingReduction(this)).execute(a)
+};
+mxHierarchicalLayout.prototype.placementStage = function(a, b) {
+  var c = new mxCoordinateAssignment(this, this.intraCellSpacing, this.interRankCellSpacing, this.orientation, a, this.parallelEdgeSpacing);
+  c.fineTuning = this.fineTuning;
+  c.execute(b);
+  return c.limitX + this.interHierarchySpacing
+};
+
+function mxGraphModel(a) {
+  this.currentEdit = this.createUndoableEdit();
+  null != a ? this.setRoot(a) : this.clear()
+}
+mxGraphModel.prototype = new mxEventSource;
+mxGraphModel.prototype.constructor = mxGraphModel;
+mxGraphModel.prototype.root = null;
+mxGraphModel.prototype.cells = null;
+mxGraphModel.prototype.maintainEdgeParent = !0;
+mxGraphModel.prototype.createIds = !0;
+mxGraphModel.prototype.prefix = "";
+mxGraphModel.prototype.postfix = "";
+mxGraphModel.prototype.nextId = 0;
+mxGraphModel.prototype.currentEdit = null;
+mxGraphModel.prototype.updateLevel = 0;
+mxGraphModel.prototype.endingUpdate = !1;
+mxGraphModel.prototype.clear = function() {
+  this.setRoot(this.createRoot())
+};
+mxGraphModel.prototype.isCreateIds = function() {
+  return this.createIds
+};
+mxGraphModel.prototype.setCreateIds = function(a) {
+  this.createIds = a
+};
+mxGraphModel.prototype.createRoot = function() {
+  var a = new mxCell;
+  a.insert(new mxCell);
+  return a
+};
+mxGraphModel.prototype.getCell = function(a) {
+  return null != this.cells ? this.cells[a] : null
+};
+mxGraphModel.prototype.filterCells = function(a, b) {
+  var c = null;
+  if (null != a)
+    for (var c = [], d = 0; d < a.length; d++) b(a[d]) && c.push(a[d]);
+  return c
+};
+mxGraphModel.prototype.getDescendants = function(a) {
+  return this.filterDescendants(null, a)
+};
+mxGraphModel.prototype.filterDescendants = function(a, b) {
+  var c = [];
+  b = b || this.getRoot();
+  (null == a || a(b)) && c.push(b);
+  for (var d = this.getChildCount(b), e = 0; e < d; e++) var f = this.getChildAt(b, e),
+    c = c.concat(this.filterDescendants(a, f));
+  return c
+};
+mxGraphModel.prototype.getRoot = function(a) {
+  var b = a || this.root;
+  if (null != a)
+    for (; null != a;) b = a, a = this.getParent(a);
+  return b
+};
+mxGraphModel.prototype.setRoot = function(a) {
+  this.execute(new mxRootChange(this, a));
+  return a
+};
+mxGraphModel.prototype.rootChanged = function(a) {
+  var b = this.root;
+  this.root = a;
+  this.nextId = 0;
+  this.cells = null;
+  this.cellAdded(a);
+  return b
+};
+mxGraphModel.prototype.isRoot = function(a) {
+  return null != a && this.root == a
+};
+mxGraphModel.prototype.isLayer = function(a) {
+  return this.isRoot(this.getParent(a))
+};
+mxGraphModel.prototype.isAncestor = function(a, b) {
+  for (; null != b && b != a;) b = this.getParent(b);
+  return b == a
+};
+mxGraphModel.prototype.contains = function(a) {
+  return this.isAncestor(this.root, a)
+};
+mxGraphModel.prototype.getParent = function(a) {
+  return null != a ? a.getParent() : null
+};
+mxGraphModel.prototype.add = function(a, b, c) {
+  if (b != a && null != a && null != b) {
+    null == c && (c = this.getChildCount(a));
+    var d = a != this.getParent(b);
+    this.execute(new mxChildChange(this, a, b, c));
+    this.maintainEdgeParent && d && this.updateEdgeParents(b)
+  }
+  return b
+};
+mxGraphModel.prototype.cellAdded = function(a) {
+  if (null != a) {
+    null == a.getId() && this.createIds && a.setId(this.createId(a));
+    if (null != a.getId()) {
+      var b = this.getCell(a.getId());
+      if (b != a) {
+        for (; null != b;) a.setId(this.createId(a)), b = this.getCell(a.getId());
+        null == this.cells && (this.cells = {});
+        this.cells[a.getId()] = a
+      }
+    }
+    mxUtils.isNumeric(a.getId()) && (this.nextId = Math.max(this.nextId, a.getId()));
+    for (var b = this.getChildCount(a), c = 0; c < b; c++) this.cellAdded(this.getChildAt(a, c))
+  }
+};
+mxGraphModel.prototype.createId = function(a) {
+  a = this.nextId;
+  this.nextId++;
+  return this.prefix + a + this.postfix
+};
+mxGraphModel.prototype.updateEdgeParents = function(a, b) {
+  b = b || this.getRoot(a);
+  for (var c = this.getChildCount(a), d = 0; d < c; d++) {
+    var e = this.getChildAt(a, d);
+    this.updateEdgeParents(e, b)
+  }
+  e = this.getEdgeCount(a);
+  c = [];
+  for (d = 0; d < e; d++) c.push(this.getEdgeAt(a, d));
+  for (d = 0; d < c.length; d++) e = c[d], this.isAncestor(b, e) && this.updateEdgeParent(e, b)
+};
+mxGraphModel.prototype.updateEdgeParent = function(a, b) {
+  for (var c = this.getTerminal(a, !0), d = this.getTerminal(a, !1), e = null; null != c && !this.isEdge(c) && null != c.geometry && c.geometry.relative;) c = this.getParent(c);
+  for (; null != d && !this.isEdge(d) && null != d.geometry && d.geometry.relative;) d = this.getParent(d);
+  if (this.isAncestor(b, c) && this.isAncestor(b, d) && (e = c == d ? this.getParent(c) : this.getNearestCommonAncestor(c, d), null != e && (this.getParent(e) != this.root || this.isAncestor(e, a)) &&
+      this.getParent(a) != e)) {
+    c = this.getGeometry(a);
+    if (null != c) {
+      var f = this.getOrigin(this.getParent(a)),
+        g = this.getOrigin(e),
+        d = g.x - f.x,
+        f = g.y - f.y,
+        c = c.clone();
+      c.translate(-d, -f);
+      this.setGeometry(a, c)
+    }
+    this.add(e, a, this.getChildCount(e))
+  }
+};
+mxGraphModel.prototype.getOrigin = function(a) {
+  var b = null;
+  null != a ? (b = this.getOrigin(this.getParent(a)), this.isEdge(a) || (a = this.getGeometry(a), null != a && (b.x += a.x, b.y += a.y))) : b = new mxPoint;
+  return b
+};
+mxGraphModel.prototype.getNearestCommonAncestor = function(a, b) {
+  if (null != a && null != b) {
+    var c = mxCellPath.create(b);
+    if (null != c && 0 < c.length) {
+      var d = a,
+        e = mxCellPath.create(d);
+      if (c.length < e.length) var d = b,
+        f = e,
+        e = c,
+        c = f;
+      for (; null != d;) {
+        f = this.getParent(d);
+        if (0 == c.indexOf(e + mxCellPath.PATH_SEPARATOR) && null != f) return d;
+        e = mxCellPath.getParentPath(e);
+        d = f
+      }
+    }
+  }
+  return null
+};
+mxGraphModel.prototype.remove = function(a) {
+  a == this.root ? this.setRoot(null) : null != this.getParent(a) && this.execute(new mxChildChange(this, null, a));
+  return a
+};
+mxGraphModel.prototype.cellRemoved = function(a) {
+  if (null != a && null != this.cells) {
+    for (var b = this.getChildCount(a) - 1; 0 <= b; b--) this.cellRemoved(this.getChildAt(a, b));
+    null != this.cells && null != a.getId() && delete this.cells[a.getId()]
+  }
+};
+mxGraphModel.prototype.parentForCellChanged = function(a, b, c) {
+  var d = this.getParent(a);
+  null != b ? (b != d || d.getIndex(a) != c) && b.insert(a, c) : null != d && (c = d.getIndex(a), d.remove(c));
+  !this.contains(d) && null != b ? this.cellAdded(a) : null == b && this.cellRemoved(a);
+  return d
+};
+mxGraphModel.prototype.getChildCount = function(a) {
+  return null != a ? a.getChildCount() : 0
+};
+mxGraphModel.prototype.getChildAt = function(a, b) {
+  return null != a ? a.getChildAt(b) : null
+};
+mxGraphModel.prototype.getChildren = function(a) {
+  return null != a ? a.children : null
+};
+mxGraphModel.prototype.getChildVertices = function(a) {
+  return this.getChildCells(a, !0, !1)
+};
+mxGraphModel.prototype.getChildEdges = function(a) {
+  return this.getChildCells(a, !1, !0)
+};
+mxGraphModel.prototype.getChildCells = function(a, b, c) {
+  b = null != b ? b : !1;
+  c = null != c ? c : !1;
+  for (var d = this.getChildCount(a), e = [], f = 0; f < d; f++) {
+    var g = this.getChildAt(a, f);
+    (!c && !b || c && this.isEdge(g) || b && this.isVertex(g)) && e.push(g)
+  }
+  return e
+};
+mxGraphModel.prototype.getTerminal = function(a, b) {
+  return null != a ? a.getTerminal(b) : null
+};
+mxGraphModel.prototype.setTerminal = function(a, b, c) {
+  var d = b != this.getTerminal(a, c);
+  this.execute(new mxTerminalChange(this, a, b, c));
+  this.maintainEdgeParent && d && this.updateEdgeParent(a, this.getRoot());
+  return b
+};
+mxGraphModel.prototype.setTerminals = function(a, b, c) {
+  this.beginUpdate();
+  try {
+    this.setTerminal(a, b, !0), this.setTerminal(a, c, !1)
+  } finally {
+    this.endUpdate()
+  }
+};
+mxGraphModel.prototype.terminalForCellChanged = function(a, b, c) {
+  var d = this.getTerminal(a, c);
+  null != b ? b.insertEdge(a, c) : null != d && d.removeEdge(a, c);
+  return d
+};
+mxGraphModel.prototype.getEdgeCount = function(a) {
+  return null != a ? a.getEdgeCount() : 0
+};
+mxGraphModel.prototype.getEdgeAt = function(a, b) {
+  return null != a ? a.getEdgeAt(b) : null
+};
+mxGraphModel.prototype.getDirectedEdgeCount = function(a, b, c) {
+  for (var d = 0, e = this.getEdgeCount(a), f = 0; f < e; f++) {
+    var g = this.getEdgeAt(a, f);
+    g != c && this.getTerminal(g, b) == a && d++
+  }
+  return d
+};
+mxGraphModel.prototype.getConnections = function(a) {
+  return this.getEdges(a, !0, !0, !1)
+};
+mxGraphModel.prototype.getIncomingEdges = function(a) {
+  return this.getEdges(a, !0, !1, !1)
+};
+mxGraphModel.prototype.getOutgoingEdges = function(a) {
+  return this.getEdges(a, !1, !0, !1)
+};
+mxGraphModel.prototype.getEdges = function(a, b, c, d) {
+  b = null != b ? b : !0;
+  c = null != c ? c : !0;
+  d = null != d ? d : !0;
+  for (var e = this.getEdgeCount(a), f = [], g = 0; g < e; g++) {
+    var h = this.getEdgeAt(a, g),
+      k = this.getTerminal(h, !0),
+      l = this.getTerminal(h, !1);
+    (d && k == l || k != l && (b && l == a || c && k == a)) && f.push(h)
+  }
+  return f
+};
+mxGraphModel.prototype.getEdgesBetween = function(a, b, c) {
+  c = null != c ? c : !1;
+  var d = this.getEdgeCount(a),
+    e = this.getEdgeCount(b),
+    f = a,
+    g = d;
+  e < d && (g = e, f = b);
+  d = [];
+  for (e = 0; e < g; e++) {
+    var h = this.getEdgeAt(f, e),
+      k = this.getTerminal(h, !0),
+      l = this.getTerminal(h, !1),
+      m = l == a && k == b;
+    (k == a && l == b || !c && m) && d.push(h)
+  }
+  return d
+};
+mxGraphModel.prototype.getOpposites = function(a, b, c, d) {
+  c = null != c ? c : !0;
+  d = null != d ? d : !0;
+  var e = [];
+  if (null != a)
+    for (var f = 0; f < a.length; f++) {
+      var g = this.getTerminal(a[f], !0),
+        h = this.getTerminal(a[f], !1);
+      g == b && null != h && h != b && d ? e.push(h) : h == b && (null != g && g != b && c) && e.push(g)
+    }
+  return e
+};
+mxGraphModel.prototype.getTopmostCells = function(a) {
+  for (var b = [], c = 0; c < a.length; c++) {
+    for (var d = a[c], e = !0, f = this.getParent(d); null != f;) {
+      if (0 <= mxUtils.indexOf(a, f)) {
+        e = !1;
+        break
+      }
+      f = this.getParent(f)
+    }
+    e && b.push(d)
+  }
+  return b
+};
+mxGraphModel.prototype.isVertex = function(a) {
+  return null != a ? a.isVertex() : !1
+};
+mxGraphModel.prototype.isEdge = function(a) {
+  return null != a ? a.isEdge() : !1
+};
+mxGraphModel.prototype.isConnectable = function(a) {
+  return null != a ? a.isConnectable() : !1
+};
+mxGraphModel.prototype.getValue = function(a) {
+  return null != a ? a.getValue() : null
+};
+mxGraphModel.prototype.setValue = function(a, b) {
+  this.execute(new mxValueChange(this, a, b));
+  return b
+};
+mxGraphModel.prototype.valueForCellChanged = function(a, b) {
+  return a.valueChanged(b)
+};
+mxGraphModel.prototype.getGeometry = function(a, b) {
+  return null != a ? a.getGeometry() : null
+};
+mxGraphModel.prototype.setGeometry = function(a, b) {
+  b != this.getGeometry(a) && this.execute(new mxGeometryChange(this, a, b));
+  return b
+};
+mxGraphModel.prototype.geometryForCellChanged = function(a, b) {
+  var c = this.getGeometry(a);
+  a.setGeometry(b);
+  return c
+};
+mxGraphModel.prototype.getStyle = function(a) {
+  return null != a ? a.getStyle() : null
+};
+mxGraphModel.prototype.setStyle = function(a, b) {
+  b != this.getStyle(a) && this.execute(new mxStyleChange(this, a, b));
+  return b
+};
+mxGraphModel.prototype.styleForCellChanged = function(a, b) {
+  var c = this.getStyle(a);
+  a.setStyle(b);
+  return c
+};
+mxGraphModel.prototype.isCollapsed = function(a) {
+  return null != a ? a.isCollapsed() : !1
+};
+mxGraphModel.prototype.setCollapsed = function(a, b) {
+  b != this.isCollapsed(a) && this.execute(new mxCollapseChange(this, a, b));
+  return b
+};
+mxGraphModel.prototype.collapsedStateForCellChanged = function(a, b) {
+  var c = this.isCollapsed(a);
+  a.setCollapsed(b);
+  return c
+};
+mxGraphModel.prototype.isVisible = function(a) {
+  return null != a ? a.isVisible() : !1
+};
+mxGraphModel.prototype.setVisible = function(a, b) {
+  b != this.isVisible(a) && this.execute(new mxVisibleChange(this, a, b));
+  return b
+};
+mxGraphModel.prototype.visibleStateForCellChanged = function(a, b) {
+  var c = this.isVisible(a);
+  a.setVisible(b);
+  return c
+};
+mxGraphModel.prototype.execute = function(a) {
+  a.execute();
+  this.beginUpdate();
+  this.currentEdit.add(a);
+  this.fireEvent(new mxEventObject(mxEvent.EXECUTE, "change", a));
+  this.fireEvent(new mxEventObject(mxEvent.EXECUTED, "change", a));
+  this.endUpdate()
+};
+mxGraphModel.prototype.beginUpdate = function() {
+  this.updateLevel++;
+  this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));
+  1 == this.updateLevel && this.fireEvent(new mxEventObject(mxEvent.START_EDIT))
+};
+mxGraphModel.prototype.endUpdate = function() {
+  this.updateLevel--;
+  0 == this.updateLevel && this.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+  if (!this.endingUpdate) {
+    this.endingUpdate = 0 == this.updateLevel;
+    this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, "edit", this.currentEdit));
+    try {
+      if (this.endingUpdate && !this.currentEdit.isEmpty()) {
+        this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, "edit", this.currentEdit));
+        var a = this.currentEdit;
+        this.currentEdit = this.createUndoableEdit();
+        a.notify();
+        this.fireEvent(new mxEventObject(mxEvent.UNDO,
+          "edit", a))
+      }
+    } finally {
+      this.endingUpdate = !1
+    }
+  }
+};
+mxGraphModel.prototype.createUndoableEdit = function() {
+  var a = new mxUndoableEdit(this, !0);
+  a.notify = function() {
+    a.source.fireEvent(new mxEventObject(mxEvent.CHANGE, "edit", a, "changes", a.changes));
+    a.source.fireEvent(new mxEventObject(mxEvent.NOTIFY, "edit", a, "changes", a.changes))
+  };
+  return a
+};
+mxGraphModel.prototype.mergeChildren = function(a, b, c) {
+  c = null != c ? c : !0;
+  this.beginUpdate();
+  try {
+    var d = {};
+    this.mergeChildrenImpl(a, b, c, d);
+    for (var e in d) {
+      var f = d[e],
+        g = this.getTerminal(f, !0);
+      null != g && (g = d[mxCellPath.create(g)], this.setTerminal(f, g, !0));
+      g = this.getTerminal(f, !1);
+      null != g && (g = d[mxCellPath.create(g)], this.setTerminal(f, g, !1))
+    }
+  } finally {
+    this.endUpdate()
+  }
+};
+mxGraphModel.prototype.mergeChildrenImpl = function(a, b, c, d) {
+  this.beginUpdate();
+  try {
+    for (var e = a.getChildCount(), f = 0; f < e; f++) {
+      var g = a.getChildAt(f);
+      if ("function" == typeof g.getId) {
+        var h = g.getId(),
+          k = null != h && (!this.isEdge(g) || !c) ? this.getCell(h) : null;
+        if (null == k) {
+          var l = g.clone();
+          l.setId(h);
+          l.setTerminal(g.getTerminal(!0), !0);
+          l.setTerminal(g.getTerminal(!1), !1);
+          k = b.insert(l);
+          this.cellAdded(k)
+        }
+        d[mxCellPath.create(g)] = k;
+        this.mergeChildrenImpl(g, k, c, d)
+      }
+    }
+  } finally {
+    this.endUpdate()
+  }
+};
+mxGraphModel.prototype.getParents = function(a) {
+  var b = [];
+  if (null != a)
+    for (var c = {}, d = 0; d < a.length; d++) {
+      var e = this.getParent(a[d]);
+      if (null != e) {
+        var f = mxCellPath.create(e);
+        null == c[f] && (c[f] = e, b.push(e))
+      }
+    }
+  return b
+};
+mxGraphModel.prototype.cloneCell = function(a) {
+  return null != a ? this.cloneCells([a], !0)[0] : null
+};
+mxGraphModel.prototype.cloneCells = function(a, b) {
+  for (var c = {}, d = [], e = 0; e < a.length; e++) null != a[e] ? d.push(this.cloneCellImpl(a[e], c, b)) : d.push(null);
+  for (e = 0; e < d.length; e++) null != d[e] && this.restoreClone(d[e], a[e], c);
+  return d
+};
+mxGraphModel.prototype.cloneCellImpl = function(a, b, c) {
+  var d = this.cellCloned(a);
+  b[mxObjectIdentity.get(a)] = d;
+  if (c) {
+    c = this.getChildCount(a);
+    for (var e = 0; e < c; e++) {
+      var f = this.cloneCellImpl(this.getChildAt(a, e), b, !0);
+      d.insert(f)
+    }
+  }
+  return d
+};
+mxGraphModel.prototype.cellCloned = function(a) {
+  return a.clone()
+};
+mxGraphModel.prototype.restoreClone = function(a, b, c) {
+  var d = this.getTerminal(b, !0);
+  null != d && (d = c[mxObjectIdentity.get(d)], null != d && d.insertEdge(a, !0));
+  d = this.getTerminal(b, !1);
+  null != d && (d = c[mxObjectIdentity.get(d)], null != d && d.insertEdge(a, !1));
+  for (var d = this.getChildCount(a), e = 0; e < d; e++) this.restoreClone(this.getChildAt(a, e), this.getChildAt(b, e), c)
+};
+
+function mxRootChange(a, b) {
+  this.model = a;
+  this.previous = this.root = b
+}
+mxRootChange.prototype.execute = function() {
+  this.root = this.previous;
+  this.previous = this.model.rootChanged(this.previous)
+};
+
+function mxChildChange(a, b, c, d) {
+  this.model = a;
+  this.previous = this.parent = b;
+  this.child = c;
+  this.previousIndex = this.index = d
+}
+mxChildChange.prototype.execute = function() {
+  var a = this.model.getParent(this.child),
+    b = null != a ? a.getIndex(this.child) : 0;
+  null == this.previous && this.connect(this.child, !1);
+  a = this.model.parentForCellChanged(this.child, this.previous, this.previousIndex);
+  null != this.previous && this.connect(this.child, !0);
+  this.parent = this.previous;
+  this.previous = a;
+  this.index = this.previousIndex;
+  this.previousIndex = b
+};
+mxChildChange.prototype.connect = function(a, b) {
+  b = null != b ? b : !0;
+  var c = a.getTerminal(!0),
+    d = a.getTerminal(!1);
+  null != c && (b ? this.model.terminalForCellChanged(a, c, !0) : this.model.terminalForCellChanged(a, null, !0));
+  null != d && (b ? this.model.terminalForCellChanged(a, d, !1) : this.model.terminalForCellChanged(a, null, !1));
+  a.setTerminal(c, !0);
+  a.setTerminal(d, !1);
+  c = this.model.getChildCount(a);
+  for (d = 0; d < c; d++) this.connect(this.model.getChildAt(a, d), b)
+};
+
+function mxTerminalChange(a, b, c, d) {
+  this.model = a;
+  this.cell = b;
+  this.previous = this.terminal = c;
+  this.source = d
+}
+mxTerminalChange.prototype.execute = function() {
+  this.terminal = this.previous;
+  this.previous = this.model.terminalForCellChanged(this.cell, this.previous, this.source)
+};
+
+function mxValueChange(a, b, c) {
+  this.model = a;
+  this.cell = b;
+  this.previous = this.value = c
+}
+mxValueChange.prototype.execute = function() {
+  this.value = this.previous;
+  this.previous = this.model.valueForCellChanged(this.cell, this.previous)
+};
+
+function mxStyleChange(a, b, c) {
+  this.model = a;
+  this.cell = b;
+  this.previous = this.style = c
+}
+mxStyleChange.prototype.execute = function() {
+  this.style = this.previous;
+  this.previous = this.model.styleForCellChanged(this.cell, this.previous)
+};
+
+function mxGeometryChange(a, b, c) {
+  this.model = a;
+  this.cell = b;
+  this.previous = this.geometry = c
+}
+mxGeometryChange.prototype.execute = function() {
+  this.geometry = this.previous;
+  this.previous = this.model.geometryForCellChanged(this.cell, this.previous)
+};
+
+function mxCollapseChange(a, b, c) {
+  this.model = a;
+  this.cell = b;
+  this.previous = this.collapsed = c
+}
+mxCollapseChange.prototype.execute = function() {
+  this.collapsed = this.previous;
+  this.previous = this.model.collapsedStateForCellChanged(this.cell, this.previous)
+};
+
+function mxVisibleChange(a, b, c) {
+  this.model = a;
+  this.cell = b;
+  this.previous = this.visible = c
+}
+mxVisibleChange.prototype.execute = function() {
+  this.visible = this.previous;
+  this.previous = this.model.visibleStateForCellChanged(this.cell, this.previous)
+};
+
+function mxCellAttributeChange(a, b, c) {
+  this.cell = a;
+  this.attribute = b;
+  this.previous = this.value = c
+}
+mxCellAttributeChange.prototype.execute = function() {
+  var a = this.cell.getAttribute(this.attribute);
+  null == this.previous ? this.cell.value.removeAttribute(this.attribute) : this.cell.setAttribute(this.attribute, this.previous);
+  this.previous = a
+};
+
+function mxCell(a, b, c) {
+  this.value = a;
+  this.setGeometry(b);
+  this.setStyle(c);
+  if (null != this.onInit) this.onInit()
+}
+mxCell.prototype.id = null;
+mxCell.prototype.value = null;
+mxCell.prototype.geometry = null;
+mxCell.prototype.style = null;
+mxCell.prototype.vertex = !1;
+mxCell.prototype.edge = !1;
+mxCell.prototype.connectable = !0;
+mxCell.prototype.visible = !0;
+mxCell.prototype.collapsed = !1;
+mxCell.prototype.parent = null;
+mxCell.prototype.source = null;
+mxCell.prototype.target = null;
+mxCell.prototype.children = null;
+mxCell.prototype.edges = null;
+mxCell.prototype.mxTransient = "id value parent source target children edges".split(" ");
+mxCell.prototype.getId = function() {
+  return this.id
+};
+mxCell.prototype.setId = function(a) {
+  this.id = a
+};
+mxCell.prototype.getValue = function() {
+  return this.value
+};
+mxCell.prototype.setValue = function(a) {
+  this.value = a
+};
+mxCell.prototype.valueChanged = function(a) {
+  var b = this.getValue();
+  this.setValue(a);
+  return b
+};
+mxCell.prototype.getGeometry = function() {
+  return this.geometry
+};
+mxCell.prototype.setGeometry = function(a) {
+  this.geometry = a
+};
+mxCell.prototype.getStyle = function() {
+  return this.style
+};
+mxCell.prototype.setStyle = function(a) {
+  this.style = a
+};
+mxCell.prototype.isVertex = function() {
+  return this.vertex
+};
+mxCell.prototype.setVertex = function(a) {
+  this.vertex = a
+};
+mxCell.prototype.isEdge = function() {
+  return this.edge
+};
+mxCell.prototype.setEdge = function(a) {
+  this.edge = a
+};
+mxCell.prototype.isConnectable = function() {
+  return this.connectable
+};
+mxCell.prototype.setConnectable = function(a) {
+  this.connectable = a
+};
+mxCell.prototype.isVisible = function() {
+  return this.visible
+};
+mxCell.prototype.setVisible = function(a) {
+  this.visible = a
+};
+mxCell.prototype.isCollapsed = function() {
+  return this.collapsed
+};
+mxCell.prototype.setCollapsed = function(a) {
+  this.collapsed = a
+};
+mxCell.prototype.getParent = function() {
+  return this.parent
+};
+mxCell.prototype.setParent = function(a) {
+  this.parent = a
+};
+mxCell.prototype.getTerminal = function(a) {
+  return a ? this.source : this.target
+};
+mxCell.prototype.setTerminal = function(a, b) {
+  b ? this.source = a : this.target = a;
+  return a
+};
+mxCell.prototype.getChildCount = function() {
+  return null == this.children ? 0 : this.children.length
+};
+mxCell.prototype.getIndex = function(a) {
+  return mxUtils.indexOf(this.children, a)
+};
+mxCell.prototype.getChildAt = function(a) {
+  return null == this.children ? null : this.children[a]
+};
+mxCell.prototype.insert = function(a, b) {
+  null != a && (null == b && (b = this.getChildCount(), a.getParent() == this && b--), a.removeFromParent(), a.setParent(this), null == this.children ? (this.children = [], this.children.push(a)) :
+    this.children.splice(b, 0, a));
+  return a
+};
+mxCell.prototype.remove = function(a) {
+  var b = null;
+  null != this.children && 0 <= a && (b = this.getChildAt(a), null != b && (this.children.splice(a, 1), b.setParent(null)));
+  return b
+};
+mxCell.prototype.removeFromParent = function() {
+  if (null != this.parent) {
+    var a = this.parent.getIndex(this);
+    this.parent.remove(a)
+  }
+};
+mxCell.prototype.getEdgeCount = function() {
+  return null == this.edges ? 0 : this.edges.length
+};
+mxCell.prototype.getEdgeIndex = function(a) {
+  return mxUtils.indexOf(this.edges, a)
+};
+mxCell.prototype.getEdgeAt = function(a) {
+  return null == this.edges ? null : this.edges[a]
+};
+mxCell.prototype.insertEdge = function(a, b) {
+  if (null != a && (a.removeFromTerminal(b), a.setTerminal(this, b), null == this.edges || a.getTerminal(!b) != this || 0 > mxUtils.indexOf(this.edges, a))) null == this.edges && (this.edges = []),
+    this.edges.push(a);
+  return a
+};
+mxCell.prototype.removeEdge = function(a, b) {
+  if (null != a) {
+    if (a.getTerminal(!b) != this && null != this.edges) {
+      var c = this.getEdgeIndex(a);
+      0 <= c && this.edges.splice(c, 1)
+    }
+    a.setTerminal(null, b)
+  }
+  return a
+};
+mxCell.prototype.removeFromTerminal = function(a) {
+  var b = this.getTerminal(a);
+  null != b && b.removeEdge(this, a)
+};
+mxCell.prototype.getAttribute = function(a, b) {
+  var c = this.getValue();
+  return (null != c && c.nodeType == mxConstants.NODETYPE_ELEMENT ? c.getAttribute(a) : null) || b
+};
+mxCell.prototype.setAttribute = function(a, b) {
+  var c = this.getValue();
+  null != c && c.nodeType == mxConstants.NODETYPE_ELEMENT && c.setAttribute(a, b)
+};
+mxCell.prototype.clone = function() {
+  var a = mxUtils.clone(this, this.mxTransient);
+  a.setValue(this.cloneValue());
+  return a
+};
+mxCell.prototype.cloneValue = function() {
+  var a = this.getValue();
+  null != a && ("function" == typeof a.clone ? a = a.clone() : isNaN(a.nodeType) || (a = a.cloneNode(!0)));
+  return a
+};
+
+function mxGeometry(a, b, c, d) {
+  mxRectangle.call(this, a, b, c, d)
+}
+mxGeometry.prototype = new mxRectangle;
+mxGeometry.prototype.constructor = mxGeometry;
+mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = !0;
+mxGeometry.prototype.alternateBounds = null;
+mxGeometry.prototype.sourcePoint = null;
+mxGeometry.prototype.targetPoint = null;
+mxGeometry.prototype.points = null;
+mxGeometry.prototype.offset = null;
+mxGeometry.prototype.relative = !1;
+mxGeometry.prototype.swap = function() {
+  if (null != this.alternateBounds) {
+    var a = new mxRectangle(this.x, this.y, this.width, this.height);
+    this.x = this.alternateBounds.x;
+    this.y = this.alternateBounds.y;
+    this.width = this.alternateBounds.width;
+    this.height = this.alternateBounds.height;
+    this.alternateBounds = a
+  }
+};
+mxGeometry.prototype.getTerminalPoint = function(a) {
+  return a ? this.sourcePoint : this.targetPoint
+};
+mxGeometry.prototype.setTerminalPoint = function(a, b) {
+  b ? this.sourcePoint = a : this.targetPoint = a;
+  return a
+};
+mxGeometry.prototype.translate = function(a, b) {
+  this.clone();
+  this.relative || (this.x += a, this.y += b);
+  null != this.sourcePoint && (this.sourcePoint.x += a, this.sourcePoint.y += b);
+  null != this.targetPoint && (this.targetPoint.x += a, this.targetPoint.y += b);
+  if (this.TRANSLATE_CONTROL_POINTS && null != this.points)
+    for (var c = this.points.length, d = 0; d < c; d++) {
+      var e = this.points[d];
+      null != e && (e.x += a, e.y += b)
+    }
+};
+var mxCellPath = {
+    PATH_SEPARATOR: ".",
+    create: function(a) {
+      var b = "";
+      if (null != a)
+        for (var c = a.getParent(); null != c;) b = c.getIndex(a) + mxCellPath.PATH_SEPARATOR + b, a = c, c = a.getParent();
+      a = b.length;
+      1 < a && (b = b.substring(0, a - 1));
+      return b
+    },
+    getParentPath: function(a) {
+      if (null != a) {
+        var b = a.lastIndexOf(mxCellPath.PATH_SEPARATOR);
+        if (0 <= b) return a.substring(0, b);
+        if (0 < a.length) return ""
+      }
+      return null
+    },
+    resolve: function(a, b) {
+      var c = a;
+      if (null != b)
+        for (var d = b.split(mxCellPath.PATH_SEPARATOR), e = 0; e < d.length; e++) c = c.getChildAt(parseInt(d[e]));
+      return c
+    },
+    compare: function(a, b) {
+      for (var c = Math.min(a.length, b.length), d = 0, e = 0; e < c; e++)
+        if (a[e] != b[e]) {
+          0 == a[e].length || 0 == b[e].length ? d = a[e] == b[e] ? 0 : a[e] > b[e] ? 1 : -1 : (c = parseInt(a[e]), e = parseInt(b[e]), d = c == e ? 0 : c > e ? 1 : -1);
+          break
+        }
+      0 == d && (c = a.length, e = b.length, c != e && (d = c > e ? 1 : -1));
+      return d
+    }
+  },
+  mxPerimeter = {
+    RectanglePerimeter: function(a, b, c, d) {
+      b = a.getCenterX();
+      var e = a.getCenterY(),
+        f = Math.atan2(c.y - e, c.x - b),
+        g = new mxPoint(0, 0),
+        h = Math.PI,
+        k = Math.PI / 2 - f,
+        l = Math.atan2(a.height, a.width);
+      f < -h + l || f > h - l ? (g.x = a.x, g.y = e - a.width *
+        Math.tan(f) / 2) : f < -l ? (g.y = a.y, g.x = b - a.height * Math.tan(k) / 2) : f < l ? (g.x = a.x + a.width, g.y = e + a.width * Math.tan(f) / 2) : (g.y = a.y + a.height, g.x = b + a.height *
+        Math.tan(k) / 2);
+      d && (c.x >= a.x && c.x <= a.x + a.width ? g.x = c.x : c.y >= a.y && c.y <= a.y + a.height && (g.y = c.y), c.x < a.x ? g.x = a.x : c.x > a.x + a.width && (g.x = a.x + a.width), c.y < a.y ? g.y =
+        a.y : c.y > a.y + a.height && (g.y = a.y + a.height));
+      return g
+    },
+    EllipsePerimeter: function(a, b, c, d) {
+      var e = a.x,
+        f = a.y,
+        g = a.width / 2,
+        h = a.height / 2,
+        k = e + g,
+        l = f + h;
+      b = c.x;
+      c = c.y;
+      var m = parseInt(b - k),
+        n = parseInt(c - l);
+      if (0 == m && 0 != n) return new mxPoint(k,
+        l + h * n / Math.abs(n));
+      if (0 == m && 0 == n) return new mxPoint(b, c);
+      if (d) {
+        if (c >= f && c <= f + a.height) return a = c - l, a = Math.sqrt(g * g * (1 - a * a / (h * h))) || 0, b <= e && (a = -a), new mxPoint(k + a, c);
+        if (b >= e && b <= e + a.width) return a = b - k, a = Math.sqrt(h * h * (1 - a * a / (g * g))) || 0, c <= f && (a = -a), new mxPoint(b, l + a)
+      }
+      e = n / m;
+      l -= e * k;
+      f = g * g * e * e + h * h;
+      a = -2 * k * f;
+      h = Math.sqrt(a * a - 4 * f * (g * g * e * e * k * k + h * h * k * k - g * g * h * h));
+      g = (-a + h) / (2 * f);
+      h = (-a - h) / (2 * f);
+      k = e * g + l;
+      l = e * h + l;
+      e = Math.sqrt(Math.pow(g - b, 2) + Math.pow(k - c, 2));
+      b = Math.sqrt(Math.pow(h - b, 2) + Math.pow(l - c, 2));
+      f = c = 0;
+      e < b ? (c =
+        g, f = k) : (c = h, f = l);
+      return new mxPoint(c, f)
+    },
+    RhombusPerimeter: function(a, b, c, d) {
+      b = a.x;
+      var e = a.y,
+        f = a.width;
+      a = a.height;
+      var g = b + f / 2,
+        h = e + a / 2,
+        k = c.x;
+      c = c.y;
+      if (g == k) return h > c ? new mxPoint(g, e) : new mxPoint(g, e + a);
+      if (h == c) return g > k ? new mxPoint(b, h) : new mxPoint(b + f, h);
+      var l = g,
+        m = h;
+      d && (k >= b && k <= b + f ? l = k : c >= e && c <= e + a && (m = c));
+      return k < g ? c < h ? mxUtils.intersection(k, c, l, m, g, e, b, h) : mxUtils.intersection(k, c, l, m, g, e + a, b, h) : c < h ? mxUtils.intersection(k, c, l, m, g, e, b + f, h) : mxUtils.intersection(
+        k, c, l, m, g, e + a, b + f, h)
+    },
+    TrianglePerimeter: function(a,
+      b, c, d) {
+      b = null != b ? b.style[mxConstants.STYLE_DIRECTION] : null;
+      var e = b == mxConstants.DIRECTION_NORTH || b == mxConstants.DIRECTION_SOUTH,
+        f = a.x,
+        g = a.y,
+        h = a.width;
+      a = a.height;
+      var k = f + h / 2,
+        l = g + a / 2,
+        m = new mxPoint(f, g),
+        n = new mxPoint(f + h, l),
+        p = new mxPoint(f, g + a);
+      b == mxConstants.DIRECTION_NORTH ? (m = p, n = new mxPoint(k, g), p = new mxPoint(f + h, g + a)) : b == mxConstants.DIRECTION_SOUTH ? (n = new mxPoint(k, g + a), p = new mxPoint(f + h, g)) :
+        b == mxConstants.DIRECTION_WEST && (m = new mxPoint(f + h, g), n = new mxPoint(f, l), p = new mxPoint(f + h, g + a));
+      var q = c.x -
+        k,
+        s = c.y - l,
+        q = e ? Math.atan2(q, s) : Math.atan2(s, q),
+        r = e ? Math.atan2(h, a) : Math.atan2(a, h),
+        s = !1,
+        s = b == mxConstants.DIRECTION_NORTH || b == mxConstants.DIRECTION_WEST ? q > -r && q < r : q < -Math.PI + r || q > Math.PI - r,
+        r = null;
+      s ? r = d && (e && c.x >= m.x && c.x <= p.x || !e && c.y >= m.y && c.y <= p.y) ? e ? new mxPoint(c.x, m.y) : new mxPoint(m.x, c.y) : b == mxConstants.DIRECTION_NORTH ? new mxPoint(f + h / 2 +
+        a * Math.tan(q) / 2, g + a) : b == mxConstants.DIRECTION_SOUTH ? new mxPoint(f + h / 2 - a * Math.tan(q) / 2, g) : b == mxConstants.DIRECTION_WEST ? new mxPoint(f + h, g + a / 2 + h *
+        Math.tan(q) / 2) : new mxPoint(f,
+        g + a / 2 - h * Math.tan(q) / 2) : (d && (d = new mxPoint(k, l), c.y >= g && c.y <= g + a ? (d.x = e ? k : b == mxConstants.DIRECTION_WEST ? f + h : f, d.y = c.y) : c.x >= f && c.x <= f +
+        h && (d.x = c.x, d.y = !e ? l : b == mxConstants.DIRECTION_NORTH ? g + a : g), k = d.x, l = d.y), r = e && c.x <= f + h / 2 || !e && c.y <= g + a / 2 ? mxUtils.intersection(c.x, c.y, k,
+        l, m.x, m.y, n.x, n.y) : mxUtils.intersection(c.x, c.y, k, l, n.x, n.y, p.x, p.y));
+      null == r && (r = new mxPoint(k, l));
+      return r
+    }
+  };
+
+function mxPrintPreview(a, b, c, d, e, f, g, h, k) {
+  this.graph = a;
+  this.scale = null != b ? b : 1 / a.pageScale;
+  this.border = null != d ? d : 0;
+  this.pageFormat = null != c ? c : a.pageFormat;
+  this.title = null != h ? h : "Printer-friendly version";
+  this.x0 = null != e ? e : 0;
+  this.y0 = null != f ? f : 0;
+  this.borderColor = g;
+  this.pageSelector = null != k ? k : !0
+}
+mxPrintPreview.prototype.graph = null;
+mxPrintPreview.prototype.pageFormat = null;
+mxPrintPreview.prototype.scale = null;
+mxPrintPreview.prototype.border = 0;
+mxPrintPreview.prototype.x0 = 0;
+mxPrintPreview.prototype.y0 = 0;
+mxPrintPreview.prototype.autoOrigin = !0;
+mxPrintPreview.prototype.printOverlays = !1;
+mxPrintPreview.prototype.borderColor = null;
+mxPrintPreview.prototype.title = null;
+mxPrintPreview.prototype.pageSelector = null;
+mxPrintPreview.prototype.wnd = null;
+mxPrintPreview.prototype.pageCount = 0;
+mxPrintPreview.prototype.getWindow = function() {
+  return this.wnd
+};
+mxPrintPreview.prototype.getDoctype = function() {
+  var a = "";
+  8 == document.documentMode && (a = '\x3cmeta http-equiv\x3d"X-UA-Compatible" content\x3d"IE\x3d8"\x3e');
+  return a
+};
+mxPrintPreview.prototype.open = function(a) {
+  var b = this.graph.cellRenderer.initializeOverlay,
+    c = null;
+  try {
+    this.printOverlays && (this.graph.cellRenderer.initializeOverlay = function(a, b) {
+      b.init(a.view.getDrawPane())
+    });
+    if (null == this.wnd) {
+      this.wnd = window.open();
+      var d = this.wnd.document,
+        e = this.getDoctype();
+      null != e && 0 < e.length && d.writeln(e);
+      d.writeln("\x3chtml\x3e");
+      d.writeln("\x3chead\x3e");
+      this.writeHead(d, a);
+      d.writeln("\x3c/head\x3e");
+      d.writeln('\x3cbody class\x3d"mxPage"\x3e');
+      mxClient.link("stylesheet", mxClient.basePath +
+        "/css/common.css", d);
+      mxClient.IS_VML && (d.namespaces.add("v", "urn:schemas-microsoft-com:vml"), d.namespaces.add("o", "urn:schemas-microsoft-com:office:office"), d.createStyleSheet().cssText =
+        "v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}", mxClient.link("stylesheet", mxClient.basePath + "/css/explorer.css", d));
+      var f = this.graph.getGraphBounds().clone(),
+        g = this.graph.getView().getScale(),
+        h = g / this.scale,
+        k = this.graph.getView().getTranslate();
+      this.autoOrigin || (this.x0 = -k.x * this.scale, this.y0 = -k.y * this.scale, f.width += f.x, f.height += f.y, f.x = 0, this.border = f.y = 0);
+      f.width /= h;
+      f.height /= h;
+      var l = this.pageFormat.width - 2 * this.border,
+        m = this.pageFormat.height - 2 * this.border,
+        n = Math.max(1, Math.ceil((f.width + this.x0) / l)),
+        p = Math.max(1, Math.ceil((f.height + this.y0) / m));
+      this.pageCount = n * p;
+      var q = mxUtils.bind(this, function() {
+        if (this.pageSelector && (1 < p || 1 < n)) {
+          var a = this.createPageSelector(p, n);
+          d.body.appendChild(a);
+          if (mxClient.IS_IE) {
+            a.style.position = "absolute";
+            var b = function() {
+              a.style.top = d.body.scrollTop +
+                10 + "px"
+            };
+            mxEvent.addListener(this.wnd, "scroll", function(a) {
+              b()
+            });
+            mxEvent.addListener(this.wnd, "resize", function(a) {
+              b()
+            })
+          }
+        }
+      });
+      a = function(a, b) {
+        null != this.borderColor && (a.style.borderColor = this.borderColor, a.style.borderStyle = "solid", a.style.borderWidth = "1px");
+        a.style.background = "white";
+        b && (a.style.pageBreakAfter = "always");
+        mxClient.IS_IE ? (d.writeln(a.outerHTML), a.parentNode.removeChild(a)) : (a.parentNode.removeChild(a), d.body.appendChild(a));
+        if (b) {
+          var c = d.createElement("hr");
+          c.className = "mxPageBreak";
+          d.body.appendChild(c)
+        }
+      };
+      var s = this.getCoverPages(this.pageFormat.width, this.pageFormat.height);
+      if (null != s)
+        for (var r = 0; r < s.length; r++) a(s[r], !0);
+      for (var t = this.getAppendices(this.pageFormat.width, this.pageFormat.height), r = 0; r < p; r++)
+        for (var v = r * m / this.scale - this.y0 / this.scale + (f.y - k.y * g) / g, s = 0; s < n; s++) {
+          if (null == this.wnd) return null;
+          var w = s * l / this.scale - this.x0 / this.scale + (f.x - k.x * g) / g,
+            u = r * n + s + 1,
+            c = this.renderPage(this.pageFormat.width, this.pageFormat.height, mxUtils.bind(this, function(a) {
+              this.addGraphFragment(-w, -v, this.scale, u, a)
+            }));
+          c.setAttribute("id", "mxPage-" + u);
+          a(c, null != t || r < p - 1 || s < n - 1)
+        }
+      if (null != t)
+        for (r = 0; r < t.length; r++) a(t[r], r < t.length);
+      d.writeln("\x3c/body\x3e");
+      d.writeln("\x3c/html\x3e");
+      d.close();
+      q();
+      mxEvent.release(d.body)
+    }
+    this.wnd.focus()
+  } catch (y) {
+    null != c && null != c.parentNode && c.parentNode.removeChild(c)
+  } finally {
+    this.graph.cellRenderer.initializeOverlay = b
+  }
+  return this.wnd
+};
+mxPrintPreview.prototype.writeHead = function(a, b) {
+  null != this.title && a.writeln("\x3ctitle\x3e" + this.title + "\x3c/title\x3e");
+  a.writeln('\x3cstyle type\x3d"text/css"\x3e');
+  a.writeln("@media print {");
+  a.writeln("  table.mxPageSelector { display: none; }");
+  a.writeln("  hr.mxPageBreak { display: none; }");
+  a.writeln("}");
+  a.writeln("@media screen {");
+  a.writeln("  table.mxPageSelector { position: fixed; right: 10px; top: 10px;font-family: Arial; font-size:10pt; border: solid 1px darkgray;background: white; border-collapse:collapse; }");
+  a.writeln("  table.mxPageSelector td { border: solid 1px gray; padding:4px; }");
+  a.writeln("  body.mxPage { background: gray; }");
+  a.writeln("}");
+  null != b && a.writeln(b);
+  a.writeln("\x3c/style\x3e")
+};
+mxPrintPreview.prototype.createPageSelector = function(a, b) {
+  var c = this.wnd.document,
+    d = c.createElement("table");
+  d.className = "mxPageSelector";
+  d.setAttribute("border", "0");
+  for (var e = c.createElement("tbody"), f = 0; f < a; f++) {
+    for (var g = c.createElement("tr"), h = 0; h < b; h++) {
+      var k = f * b + h + 1,
+        l = c.createElement("td");
+      if (!mxClient.IS_NS || mxClient.IS_SF || mxClient.IS_GC) {
+        var m = c.createElement("a");
+        m.setAttribute("href", "#mxPage-" + k);
+        mxUtils.write(m, k, c);
+        l.appendChild(m)
+      } else mxUtils.write(l, k, c);
+      g.appendChild(l)
+    }
+    e.appendChild(g)
+  }
+  d.appendChild(e);
+  return d
+};
+mxPrintPreview.prototype.renderPage = function(a, b, c) {
+  var d = document.createElement("div");
+  try {
+    d.style.width = a + "px";
+    d.style.height = b + "px";
+    d.style.overflow = "hidden";
+    d.style.pageBreakInside = "avoid";
+    d.style.position = "relative";
+    var e = document.createElement("div");
+    e.style.top = this.border + "px";
+    e.style.left = this.border + "px";
+    e.style.width = a - 2 * this.border + "px";
+    e.style.height = b - 2 * this.border + "px";
+    e.style.overflow = "hidden";
+    this.graph.dialect == mxConstants.DIALECT_VML && (e.style.position = "absolute");
+    d.appendChild(e);
+    document.body.appendChild(d);
+    c(e)
+  } catch (f) {
+    throw d.parentNode.removeChild(d), f;
+  }
+  return d
+};
+mxPrintPreview.prototype.addGraphFragment = function(a, b, c, d, e) {
+  d = this.graph.getView();
+  var f = this.graph.container;
+  this.graph.container = e;
+  var g = d.getCanvas(),
+    h = d.getBackgroundPane(),
+    k = d.getDrawPane(),
+    l = d.getOverlayPane();
+  this.graph.dialect == mxConstants.DIALECT_SVG ? d.createSvg() : this.graph.dialect == mxConstants.DIALECT_VML ? d.createVml() : d.createHtml();
+  var m = d.isEventsEnabled();
+  d.setEventsEnabled(!1);
+  var n = this.graph.isEnabled();
+  this.graph.setEnabled(!1);
+  var p = d.getTranslate();
+  d.translate = new mxPoint(a,
+    b);
+  a = null;
+  try {
+    var q = [this.graph.getModel().getRoot()];
+    a = new mxTemporaryCellStates(d, c, q)
+  } finally {
+    if (mxClient.IS_IE) d.overlayPane.innerHTML = "";
+    else
+      for (c = e.firstChild; null != c;) q = c.nextSibling, b = c.nodeName.toLowerCase(), "svg" == b ? (c.setAttribute("width", parseInt(e.style.width)), c.setAttribute("height", parseInt(e.style.height))) :
+        "default" != c.style.cursor && "table" != b && c.parentNode.removeChild(c), c = q;
+    d.overlayPane.parentNode.removeChild(d.overlayPane);
+    this.graph.setEnabled(n);
+    this.graph.container = f;
+    d.canvas =
+      g;
+    d.backgroundPane = h;
+    d.drawPane = k;
+    d.overlayPane = l;
+    d.translate = p;
+    a.destroy();
+    d.setEventsEnabled(m)
+  }
+};
+mxPrintPreview.prototype.getCoverPages = function() {
+  return null
+};
+mxPrintPreview.prototype.getAppendices = function() {
+  return null
+};
+mxPrintPreview.prototype.print = function() {
+  var a = this.open();
+  null != a && a.print()
+};
+mxPrintPreview.prototype.close = function() {
+  null != this.wnd && (this.wnd.close(), this.wnd = null)
+};
+
+function mxStylesheet() {
+  this.styles = {};
+  this.putDefaultVertexStyle(this.createDefaultVertexStyle());
+  this.putDefaultEdgeStyle(this.createDefaultEdgeStyle())
+}
+mxStylesheet.prototype.createDefaultVertexStyle = function() {
+  var a = {};
+  a[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+  a[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+  a[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+  a[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+  a[mxConstants.STYLE_FILLCOLOR] = "#C3D9FF";
+  a[mxConstants.STYLE_STROKECOLOR] = "#6482B9";
+  a[mxConstants.STYLE_FONTCOLOR] = "#774400";
+  return a
+};
+mxStylesheet.prototype.createDefaultEdgeStyle = function() {
+  var a = {};
+  a[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
+  a[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+  a[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+  a[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+  a[mxConstants.STYLE_STROKECOLOR] = "#6482B9";
+  a[mxConstants.STYLE_FONTCOLOR] = "#446299";
+  return a
+};
+mxStylesheet.prototype.putDefaultVertexStyle = function(a) {
+  this.putCellStyle("defaultVertex", a)
+};
+mxStylesheet.prototype.putDefaultEdgeStyle = function(a) {
+  this.putCellStyle("defaultEdge", a)
+};
+mxStylesheet.prototype.getDefaultVertexStyle = function() {
+  return this.styles.defaultVertex
+};
+mxStylesheet.prototype.getDefaultEdgeStyle = function() {
+  return this.styles.defaultEdge
+};
+mxStylesheet.prototype.putCellStyle = function(a, b) {
+  this.styles[a] = b
+};
+mxStylesheet.prototype.getCellStyle = function(a, b) {
+  var c = b;
+  if (null != a && 0 < a.length)
+    for (var d = a.split(";"), c = null != c && ";" != a.charAt(0) ? mxUtils.clone(c) : {}, e = 0; e < d.length; e++) {
+      var f = d[e],
+        g = f.indexOf("\x3d");
+      if (0 <= g) {
+        var h = f.substring(0, g),
+          f = f.substring(g + 1);
+        f == mxConstants.NONE ? delete c[h] : mxUtils.isNumeric(f) ? c[h] = parseFloat(f) : c[h] = f
+      } else if (f = this.styles[f], null != f)
+        for (h in f) c[h] = f[h]
+    }
+  return c
+};
+
+function mxCellState(a, b, c) {
+  this.view = a;
+  this.cell = b;
+  this.style = c;
+  this.origin = new mxPoint;
+  this.absoluteOffset = new mxPoint
+}
+mxCellState.prototype = new mxRectangle;
+mxCellState.prototype.constructor = mxCellState;
+mxCellState.prototype.view = null;
+mxCellState.prototype.cell = null;
+mxCellState.prototype.style = null;
+mxCellState.prototype.invalid = !0;
+mxCellState.prototype.invalidOrder = !1;
+mxCellState.prototype.orderChanged = !1;
+mxCellState.prototype.origin = null;
+mxCellState.prototype.absolutePoints = null;
+mxCellState.prototype.absoluteOffset = null;
+mxCellState.prototype.visibleSourceState = null;
+mxCellState.prototype.visibleTargetState = null;
+mxCellState.prototype.terminalDistance = 0;
+mxCellState.prototype.length = 0;
+mxCellState.prototype.segments = null;
+mxCellState.prototype.shape = null;
+mxCellState.prototype.text = null;
+mxCellState.prototype.getPerimeterBounds = function(a, b) {
+  a = a || 0;
+  b = null != b ? b : new mxRectangle(this.x, this.y, this.width, this.height);
+  if (null != this.shape && null != this.shape.stencil) {
+    var c = this.shape.stencil.computeAspect(this.style, b.x, b.y, b.width, b.height);
+    b.x = c.x;
+    b.y = c.y;
+    b.width = this.shape.stencil.w0 * c.width;
+    b.height = this.shape.stencil.h0 * c.height
+  }
+  0 != a && b.grow(a);
+  return b
+};
+mxCellState.prototype.setAbsoluteTerminalPoint = function(a, b) {
+  b ? (null == this.absolutePoints && (this.absolutePoints = []), 0 == this.absolutePoints.length ? this.absolutePoints.push(a) : this.absolutePoints[0] = a) : null == this.absolutePoints ? (this.absolutePoints = [],
+    this.absolutePoints.push(null), this.absolutePoints.push(a)) : 1 == this.absolutePoints.length ? this.absolutePoints.push(a) : this.absolutePoints[this.absolutePoints.length - 1] = a
+};
+mxCellState.prototype.setCursor = function(a) {
+  null != this.shape && this.shape.setCursor(a);
+  null != this.text && this.text.setCursor(a)
+};
+mxCellState.prototype.getVisibleTerminal = function(a) {
+  a = this.getVisibleTerminalState(a);
+  return null != a ? a.cell : null
+};
+mxCellState.prototype.getVisibleTerminalState = function(a) {
+  return a ? this.visibleSourceState : this.visibleTargetState
+};
+mxCellState.prototype.setVisibleTerminalState = function(a, b) {
+  b ? this.visibleSourceState = a : this.visibleTargetState = a
+};
+mxCellState.prototype.destroy = function() {
+  this.view.graph.cellRenderer.destroy(this)
+};
+mxCellState.prototype.clone = function() {
+  var a = new mxCellState(this.view, this.cell, this.style);
+  if (null != this.absolutePoints) {
+    a.absolutePoints = [];
+    for (var b = 0; b < this.absolutePoints.length; b++) a.absolutePoints[b] = this.absolutePoints[b].clone()
+  }
+  null != this.origin && (a.origin = this.origin.clone());
+  null != this.absoluteOffset && (a.absoluteOffset = this.absoluteOffset.clone());
+  null != this.boundingBox && (a.boundingBox = this.boundingBox.clone());
+  a.terminalDistance = this.terminalDistance;
+  a.segments = this.segments;
+  a.length =
+    this.length;
+  a.x = this.x;
+  a.y = this.y;
+  a.width = this.width;
+  a.height = this.height;
+  return a
+};
+
+function mxGraphSelectionModel(a) {
+  this.graph = a;
+  this.cells = []
+}
+mxGraphSelectionModel.prototype = new mxEventSource;
+mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;
+mxGraphSelectionModel.prototype.doneResource = "none" != mxClient.language ? "done" : "";
+mxGraphSelectionModel.prototype.updatingSelectionResource = "none" != mxClient.language ? "updatingSelection" : "";
+mxGraphSelectionModel.prototype.graph = null;
+mxGraphSelectionModel.prototype.singleSelection = !1;
+mxGraphSelectionModel.prototype.isSingleSelection = function() {
+  return this.singleSelection
+};
+mxGraphSelectionModel.prototype.setSingleSelection = function(a) {
+  this.singleSelection = a
+};
+mxGraphSelectionModel.prototype.isSelected = function(a) {
+  return null != a ? 0 <= mxUtils.indexOf(this.cells, a) : !1
+};
+mxGraphSelectionModel.prototype.isEmpty = function() {
+  return 0 == this.cells.length
+};
+mxGraphSelectionModel.prototype.clear = function() {
+  this.changeSelection(null, this.cells)
+};
+mxGraphSelectionModel.prototype.setCell = function(a) {
+  null != a && this.setCells([a])
+};
+mxGraphSelectionModel.prototype.setCells = function(a) {
+  if (null != a) {
+    this.singleSelection && (a = [this.getFirstSelectableCell(a)]);
+    for (var b = [], c = 0; c < a.length; c++) this.graph.isCellSelectable(a[c]) && b.push(a[c]);
+    this.changeSelection(b, this.cells)
+  }
+};
+mxGraphSelectionModel.prototype.getFirstSelectableCell = function(a) {
+  if (null != a)
+    for (var b = 0; b < a.length; b++)
+      if (this.graph.isCellSelectable(a[b])) return a[b];
+  return null
+};
+mxGraphSelectionModel.prototype.addCell = function(a) {
+  null != a && this.addCells([a])
+};
+mxGraphSelectionModel.prototype.addCells = function(a) {
+  if (null != a) {
+    var b = null;
+    this.singleSelection && (b = this.cells, a = [this.getFirstSelectableCell(a)]);
+    for (var c = [], d = 0; d < a.length; d++) !this.isSelected(a[d]) && this.graph.isCellSelectable(a[d]) && c.push(a[d]);
+    this.changeSelection(c, b)
+  }
+};
+mxGraphSelectionModel.prototype.removeCell = function(a) {
+  null != a && this.removeCells([a])
+};
+mxGraphSelectionModel.prototype.removeCells = function(a) {
+  if (null != a) {
+    for (var b = [], c = 0; c < a.length; c++) this.isSelected(a[c]) && b.push(a[c]);
+    this.changeSelection(null, b)
+  }
+};
+mxGraphSelectionModel.prototype.changeSelection = function(a, b) {
+  if (null != a && 0 < a.length && null != a[0] || null != b && 0 < b.length && null != b[0]) {
+    var c = new mxSelectionChange(this, a, b);
+    c.execute();
+    var d = new mxUndoableEdit(this, !1);
+    d.add(c);
+    this.fireEvent(new mxEventObject(mxEvent.UNDO, "edit", d))
+  }
+};
+mxGraphSelectionModel.prototype.cellAdded = function(a) {
+  null != a && !this.isSelected(a) && this.cells.push(a)
+};
+mxGraphSelectionModel.prototype.cellRemoved = function(a) {
+  null != a && (a = mxUtils.indexOf(this.cells, a), 0 <= a && this.cells.splice(a, 1))
+};
+
+function mxSelectionChange(a, b, c) {
+  this.selectionModel = a;
+  this.added = null != b ? b.slice() : null;
+  this.removed = null != c ? c.slice() : null
+}
+mxSelectionChange.prototype.execute = function() {
+  var a = mxLog.enter("mxSelectionChange.execute");
+  window.status = mxResources.get(this.selectionModel.updatingSelectionResource) || this.selectionModel.updatingSelectionResource;
+  if (null != this.removed)
+    for (var b = 0; b < this.removed.length; b++) this.selectionModel.cellRemoved(this.removed[b]);
+  if (null != this.added)
+    for (b = 0; b < this.added.length; b++) this.selectionModel.cellAdded(this.added[b]);
+  b = this.added;
+  this.added = this.removed;
+  this.removed = b;
+  window.status = mxResources.get(this.selectionModel.doneResource) ||
+    this.selectionModel.doneResource;
+  mxLog.leave("mxSelectionChange.execute", a);
+  this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE, "added", this.added, "removed", this.removed))
+};
+
+function mxCellEditor(a) {
+  this.graph = a
+}
+mxCellEditor.prototype.graph = null;
+mxCellEditor.prototype.textarea = null;
+mxCellEditor.prototype.editingCell = null;
+mxCellEditor.prototype.trigger = null;
+mxCellEditor.prototype.modified = !1;
+mxCellEditor.prototype.autoSize = !0;
+mxCellEditor.prototype.emptyLabelText = "";
+mxCellEditor.prototype.textNode = "";
+mxCellEditor.prototype.init = function() {
+  this.textarea = document.createElement("textarea");
+  this.textarea.className = "mxCellEditor";
+  this.textarea.style.position = "absolute";
+  this.textarea.style.overflow = "visible";
+  this.textarea.setAttribute("cols", "20");
+  this.textarea.setAttribute("rows", "4");
+  mxClient.IS_NS && (this.textarea.style.resize = "none");
+  mxEvent.addListener(this.textarea, "blur", mxUtils.bind(this, function(a) {
+    this.focusLost()
+  }));
+  mxEvent.addListener(this.textarea, "keydown", mxUtils.bind(this, function(a) {
+    mxEvent.isConsumed(a) ||
+      (113 == a.keyCode || this.graph.isEnterStopsCellEditing() && 13 == a.keyCode && !mxEvent.isControlDown(a) && !mxEvent.isShiftDown(a) ? (this.graph.stopEditing(!1), mxEvent.consume(a)) :
+        27 == a.keyCode ? (this.graph.stopEditing(!0), mxEvent.consume(a)) : (this.clearOnChange && (this.clearOnChange = !1, this.textarea.value = ""), this.setModified(!0)))
+  }));
+  mxEvent.addListener(this.textarea, "keypress", mxUtils.bind(this, function(a) {
+    this.autoSize && !mxEvent.isConsumed(a) && setTimeout(mxUtils.bind(this, function() {
+      this.resize()
+    }), 0)
+  }))
+};
+mxCellEditor.prototype.resize = function() {
+  if (null != this.textDiv) {
+    var a = this.graph.getView().getState(this.editingCell),
+      b = this.graph.isLabelClipped(a.cell),
+      c = this.graph.isWrapping(a.cell);
+    if (this.graph.getModel().isEdge(a.cell)) this.bounds.x = a.absoluteOffset.x, this.bounds.y = a.absoluteOffset.y, this.bounds.width = 0, this.bounds.height = 0;
+    else if (null != this.bounds) {
+      this.bounds.x = a.x;
+      this.bounds.y = a.y;
+      this.bounds.width = a.width;
+      this.bounds.height = a.height;
+      var d = mxUtils.getValue(a.style, mxConstants.STYLE_LABEL_POSITION,
+        mxConstants.ALIGN_CENTER);
+      d == mxConstants.ALIGN_LEFT ? this.bounds.x -= a.width : d == mxConstants.ALIGN_RIGHT && (this.bounds.x += a.width);
+      d = mxUtils.getValue(a.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+      d == mxConstants.ALIGN_TOP ? this.bounds.y -= a.height : d == mxConstants.ALIGN_BOTTOM && (this.bounds.y += a.height)
+    }
+    d = this.textarea.value;
+    if ("\n" == d.charAt(d.length - 1) || "" == d) d += "\x26nbsp;";
+    d = mxUtils.htmlEntities(d, !1);
+    c ? (this.textDiv.style.whiteSpace = "normal", this.textDiv.style.width = this.bounds.width +
+      "px") : d = d.replace(/ /g, "\x26nbsp;");
+    d = d.replace(/\n/g, "\x3cbr/\x3e");
+    this.textDiv.innerHTML = d;
+    var d = this.textDiv.offsetWidth + 30,
+      e = this.textDiv.offsetHeight + 16,
+      d = Math.max(d, 40),
+      e = Math.max(e, 20);
+    b && (d = Math.min(this.bounds.width - 4, d), e = Math.min(this.bounds.height, e));
+    var f = null != a.text ? a.text.margin : null;
+    null == f && (f = mxUtils.getValue(a.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER), a = mxUtils.getValue(a.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE), f =
+      mxUtils.getAlignmentAsPoint(f,
+        a));
+    if (null != f) {
+      if (b || !c) this.textarea.style.left = Math.max(0, Math.round(this.bounds.x - f.x * this.bounds.width + f.x * d) - 3) + "px";
+      this.textarea.style.top = Math.max(0, Math.round(this.bounds.y - f.y * this.bounds.height + f.y * e) + 4) + "px"
+    }
+    if (b || !c) this.textarea.style.width = d + "px";
+    this.textarea.style.height = e + "px"
+  }
+};
+mxCellEditor.prototype.isModified = function() {
+  return this.modified
+};
+mxCellEditor.prototype.setModified = function(a) {
+  this.modified = a
+};
+mxCellEditor.prototype.focusLost = function() {
+  this.stopEditing(!this.graph.isInvokesStopCellEditing())
+};
+mxCellEditor.prototype.startEditing = function(a, b) {
+  null == this.textarea && this.init();
+  this.stopEditing(!0);
+  var c = this.graph.getView().getState(a);
+  if (null != c) {
+    this.editingCell = a;
+    this.trigger = b;
+    this.textNode = null;
+    null != c.text && this.isHideLabel(c) && (this.textNode = c.text.node, this.textNode.style.visibility = "hidden");
+    var d = this.graph.getView().scale,
+      d = mxUtils.getValue(c.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * d,
+      e = mxUtils.getValue(c.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY),
+      f = mxUtils.getValue(c.style, mxConstants.STYLE_FONTCOLOR, "black"),
+      g = mxUtils.getValue(c.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT),
+      h = (mxUtils.getValue(c.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD,
+      k = (mxUtils.getValue(c.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC,
+      l = (mxUtils.getValue(c.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;
+    this.textarea.style.fontSize = Math.round(d) +
+      "px";
+    this.textarea.style.lineHeight = Math.round(d * mxConstants.LINE_HEIGHT) + "px";
+    this.textarea.style.fontFamily = e;
+    this.textarea.style.textAlign = g;
+    this.textarea.style.color = f;
+    this.textarea.style.fontWeight = h ? "bold" : "normal";
+    this.textarea.style.fontStyle = k ? "italic" : "";
+    this.textarea.style.textDecoration = l ? "underline" : "";
+    this.textarea.style.overflow = "auto";
+    this.textarea.style.outline = "none";
+    this.bounds = d = this.getEditorBounds(c);
+    this.textarea.style.left = d.x + "px";
+    this.textarea.style.top = d.y + "px";
+    this.textarea.style.width =
+      d.width + "px";
+    this.textarea.style.height = d.height + "px";
+    this.textarea.style.zIndex = 5;
+    c = this.getInitialValue(c, b);
+    null == c || 0 == c.length ? (c = this.getEmptyLabelText(), this.clearOnChange = !0) : this.clearOnChange = !1;
+    this.setModified(!1);
+    this.textarea.value = c;
+    this.graph.container.appendChild(this.textarea);
+    "none" != this.textarea.style.display && (this.autoSize && (this.textDiv = this.createTextDiv(), document.body.appendChild(this.textDiv), this.resize()), this.textarea.focus(), this.textarea.select())
+  }
+};
+mxCellEditor.prototype.createTextDiv = function() {
+  var a = document.createElement("div"),
+    b = a.style;
+  b.position = "absolute";
+  b.whiteSpace = "nowrap";
+  b.visibility = "hidden";
+  b.display = mxClient.IS_QUIRKS ? "inline" : "inline-block";
+  b.zoom = "1";
+  b.verticalAlign = "top";
+  b.lineHeight = this.textarea.style.lineHeight;
+  b.fontSize = this.textarea.style.fontSize;
+  b.fontFamily = this.textarea.style.fontFamily;
+  b.fontWeight = this.textarea.style.fontWeight;
+  b.textAlign = this.textarea.style.textAlign;
+  b.fontStyle = this.textarea.style.fontStyle;
+  b.textDecoration = this.textarea.style.textDecoration;
+  return a
+};
+mxCellEditor.prototype.stopEditing = function(a) {
+  null != this.editingCell && (null != this.textNode && (this.textNode.style.visibility = "visible", this.textNode = null), !a && this.isModified() && this.graph.labelChanged(this.editingCell, this
+      .getCurrentValue(), this.trigger), null != this.textDiv && (document.body.removeChild(this.textDiv), this.textDiv = null), this.bounds = this.trigger = this.editingCell = null, this.textarea
+    .blur(), this.textarea.parentNode.removeChild(this.textarea))
+};
+mxCellEditor.prototype.getInitialValue = function(a, b) {
+  return this.graph.getEditingValue(a.cell, b)
+};
+mxCellEditor.prototype.getCurrentValue = function() {
+  return this.textarea.value.replace(/\r/g, "")
+};
+mxCellEditor.prototype.isHideLabel = function(a) {
+  return !0
+};
+mxCellEditor.prototype.getMinimumSize = function(a) {
+  var b = this.graph.getView().scale;
+  return new mxRectangle(0, 0, null == a.text ? 30 : a.text.size * b + 20, "left" == this.textarea.style.textAlign ? 120 : 40)
+};
+mxCellEditor.prototype.getEditorBounds = function(a) {
+  var b = this.graph.getModel().isEdge(a.cell),
+    c = this.graph.getView().scale,
+    d = this.getMinimumSize(a),
+    e = d.width,
+    d = d.height,
+    f = parseInt(a.style[mxConstants.STYLE_SPACING] || 2) * c,
+    g = parseInt(a.style[mxConstants.STYLE_SPACING_TOP] || 0) * c + f,
+    h = parseInt(a.style[mxConstants.STYLE_SPACING_RIGHT] || 0) * c + f,
+    k = parseInt(a.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) * c + f,
+    c = parseInt(a.style[mxConstants.STYLE_SPACING_LEFT] || 0) * c + f,
+    h = new mxRectangle(a.x, a.y, Math.max(e, a.width -
+      c - h), Math.max(d, a.height - g - k));
+  b ? (h.x = a.absoluteOffset.x, h.y = a.absoluteOffset.y, null != a.text && null != a.text.boundingBox && (0 < a.text.boundingBox.x && (h.x = a.text.boundingBox.x), 0 < a.text.boundingBox.y && (h.y =
+    a.text.boundingBox.y))) : null != a.text && null != a.text.boundingBox && (h.x = Math.min(h.x, a.text.boundingBox.x), h.y = Math.min(h.y, a.text.boundingBox.y));
+  h.x += c;
+  h.y += g;
+  null != a.text && null != a.text.boundingBox && (b ? (h.width = Math.max(e, a.text.boundingBox.width), h.height = Math.max(d, a.text.boundingBox.height)) : (h.width =
+    Math.max(h.width, a.text.boundingBox.width), h.height = Math.max(h.height, a.text.boundingBox.height)));
+  this.graph.getModel().isVertex(a.cell) && (b = mxUtils.getValue(a.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER), b == mxConstants.ALIGN_LEFT ? h.x -= a.width : b ==
+    mxConstants.ALIGN_RIGHT && (h.x += a.width), b = mxUtils.getValue(a.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE), b == mxConstants.ALIGN_TOP ? h.y -= a.height :
+    b == mxConstants.ALIGN_BOTTOM && (h.y += a.height));
+  return h
+};
+mxCellEditor.prototype.getEmptyLabelText = function(a) {
+  return this.emptyLabelText
+};
+mxCellEditor.prototype.getEditingCell = function() {
+  return this.editingCell
+};
+mxCellEditor.prototype.destroy = function() {
+  null != this.textarea && (mxEvent.release(this.textarea), null != this.textarea.parentNode && this.textarea.parentNode.removeChild(this.textarea), this.textarea = null)
+};
+
+function mxCellRenderer() {}
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+mxCellRenderer.prototype.defaultTextShape = mxText;
+mxCellRenderer.prototype.legacyControlPosition = !0;
+mxCellRenderer.prototype.defaultShapes = {};
+mxCellRenderer.registerShape = function(a, b) {
+  mxCellRenderer.prototype.defaultShapes[a] = b
+};
+mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);
+mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);
+mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);
+mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);
+mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);
+mxCellRenderer.prototype.initialize = function(a, b) {
+  var c = a.view.graph.getModel();
+  if (null != a.view.graph.container && null == a.shape && a.cell != a.view.currentRoot && (c.isVertex(a.cell) || c.isEdge(a.cell)))
+    if (this.createShape(a), null != a.shape && (null == b || b)) this.initializeShape(a), a.view.graph.ordered || c.isEdge(a.cell) ? a.invalidOrder = !0 : a.view.graph.keepEdgesInForeground &&
+      null != this.firstEdge && (this.firstEdge.parentNode == a.shape.node.parentNode ? this.insertState(a, this.firstEdge) : this.firstEdge = null), a.shape.scale =
+      a.view.scale, this.createCellOverlays(a), this.installListeners(a)
+};
+mxCellRenderer.prototype.initializeShape = function(a) {
+  a.shape.init(a.view.getDrawPane())
+};
+mxCellRenderer.prototype.getPreviousStateInContainer = function(a, b) {
+  for (var c = null, d = a.view.graph, e = d.getModel(), f = a.cell, g = e.getParent(f); null != g && null == c;) c = this.findPreviousStateInContainer(d, g, f, b), f = g, g = e.getParent(f);
+  return c
+};
+mxCellRenderer.prototype.findPreviousStateInContainer = function(a, b, c, d) {
+  var e = null,
+    f = a.getModel();
+  for (c = null != c ? b.getIndex(c) - 1 : f.getChildCount(b) - 1; 0 <= c && null == e; c--) e = this.findPreviousStateInContainer(a, f.getChildAt(b, c), null, d);
+  if (null == e && (e = a.view.getState(b), null != e && (null == e.shape || null == e.shape.node || e.shape.node.parentNode != d))) e = null;
+  return e
+};
+mxCellRenderer.prototype.order = function(a) {
+  var b = a.shape.node.parentNode,
+    c = this.getPreviousStateInContainer(a, b),
+    d = b.firstChild;
+  null != c && (d = c.shape.node, null != c.text && (null != c.text.node && c.text.node.parentNode == b) && (d = c.text.node), d = d.nextSibling);
+  this.insertState(a, d)
+};
+mxCellRenderer.prototype.orderEdge = function(a) {
+  var b = a.view,
+    c = b.graph.getModel();
+  if (b.graph.keepEdgesInForeground) {
+    if (null == this.firstEdge || null == this.firstEdge.parentNode || this.firstEdge.parentNode != a.shape.node.parentNode) this.firstEdge = a.shape.node
+  } else if (b.graph.keepEdgesInBackground) {
+    var d = a.shape.node,
+      e = d.parentNode,
+      c = c.getParent(a.cell),
+      b = b.getState(c),
+      e = null != b && null != b.shape && null != b.shape.node ? b.shape.node.nextSibling : e.firstChild;
+    null != e && e != d && this.insertState(a, e)
+  }
+};
+mxCellRenderer.prototype.insertState = function(a, b) {
+  a.shape.node.parentNode.insertBefore(a.shape.node, b);
+  null != a.text && (null != a.text.node && a.text.node.parentNode == a.shape.node.parentNode) && a.shape.node.parentNode.insertBefore(a.text.node, a.shape.node.nextSibling)
+};
+mxCellRenderer.prototype.createShape = function(a) {
+  if (null != a.style) {
+    var b = mxStencilRegistry.getStencil(a.style[mxConstants.STYLE_SHAPE]);
+    null != b ? a.shape = new mxShape(b) : (b = this.getShapeConstructor(a), a.shape = new b);
+    a.shape.points = a.absolutePoints;
+    a.shape.bounds = new mxRectangle(a.x, a.y, a.width, a.height);
+    a.shape.dialect = a.view.graph.dialect;
+    this.configureShape(a)
+  }
+};
+mxCellRenderer.prototype.getShape = function(a) {
+  return null != a ? mxCellRenderer.prototype.defaultShapes[a] : null
+};
+mxCellRenderer.prototype.getShapeConstructor = function(a) {
+  var b = this.getShape(a.style[mxConstants.STYLE_SHAPE]);
+  null == b && (b = a.view.graph.getModel().isEdge(a.cell) ? this.defaultEdgeShape : this.defaultVertexShape);
+  return b
+};
+mxCellRenderer.prototype.configureShape = function(a) {
+  a.shape.apply(a);
+  a.shape.image = a.view.graph.getImage(a);
+  a.shape.indicatorShape = this.getShape(a.view.graph.getIndicatorShape(a));
+  a.shape.indicatorColor = a.view.graph.getIndicatorColor(a);
+  a.shape.indicatorGradientColor = a.view.graph.getIndicatorGradientColor(a);
+  a.shape.indicatorDirection = a.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+  a.shape.indicatorImage = a.view.graph.getIndicatorImage(a);
+  this.postConfigureShape(a)
+};
+mxCellRenderer.prototype.postConfigureShape = function(a) {
+  null != a.shape && (this.resolveColor(a, "indicatorColor", mxConstants.STYLE_FILLCOLOR), this.resolveColor(a, "indicatorGradientColor", mxConstants.STYLE_GRADIENTCOLOR), this.resolveColor(a,
+    "fill", mxConstants.STYLE_FILLCOLOR), this.resolveColor(a, "stroke", mxConstants.STYLE_STROKECOLOR), this.resolveColor(a, "gradient", mxConstants.STYLE_GRADIENTCOLOR))
+};
+mxCellRenderer.prototype.resolveColor = function(a, b, c) {
+  var d = a.shape[b],
+    e = a.view.graph,
+    f = null;
+  "inherit" == d ? f = e.model.getParent(a.cell) : "swimlane" == d ? (f = null != e.model.getTerminal(a.cell, !1) ? e.model.getTerminal(a.cell, !1) : a.cell, f = e.getSwimlane(f), c = e.swimlaneIndicatorColorAttribute) :
+    "indicated" == d && (a.shape[b] = a.shape.indicatorColor);
+  null != f && (d = e.getView().getState(f), a.shape[b] = null, null != d && (a.shape[b] = null != d.shape && "indicatorColor" != b ? d.shape[b] : d.style[c]))
+};
+mxCellRenderer.prototype.getLabelValue = function(a) {
+  return a.view.graph.getLabel(a.cell)
+};
+mxCellRenderer.prototype.createLabel = function(a, b) {
+  var c = a.view.graph;
+  c.getModel().isEdge(a.cell);
+  if (0 < a.style[mxConstants.STYLE_FONTSIZE] || null == a.style[mxConstants.STYLE_FONTSIZE]) {
+    var d = c.isHtmlLabel(a.cell) || null != b && mxUtils.isNode(b);
+    a.text = new this.defaultTextShape(b, new mxRectangle, a.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER, c.getVerticalAlign(a), a.style[mxConstants.STYLE_FONTCOLOR], a.style[
+        mxConstants.STYLE_FONTFAMILY], a.style[mxConstants.STYLE_FONTSIZE], a.style[mxConstants.STYLE_FONTSTYLE],
+      a.style[mxConstants.STYLE_SPACING], a.style[mxConstants.STYLE_SPACING_TOP], a.style[mxConstants.STYLE_SPACING_RIGHT], a.style[mxConstants.STYLE_SPACING_BOTTOM], a.style[mxConstants.STYLE_SPACING_LEFT],
+      a.style[mxConstants.STYLE_HORIZONTAL], a.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR], a.style[mxConstants.STYLE_LABEL_BORDERCOLOR], c.isWrapping(a.cell) && c.isHtmlLabel(a.cell), c.isLabelClipped(
+        a.cell), a.style[mxConstants.STYLE_OVERFLOW], a.style[mxConstants.STYLE_LABEL_PADDING]);
+    a.text.opacity = mxUtils.getValue(a.style,
+      mxConstants.STYLE_TEXT_OPACITY, 100);
+    a.text.dialect = d ? mxConstants.DIALECT_STRICTHTML : a.view.graph.dialect;
+    a.text.state = a;
+    this.initializeLabel(a);
+    var e = !1,
+      f = function(b) {
+        var d = a;
+        if (mxClient.IS_TOUCH || e) d = mxEvent.getClientX(b), b = mxEvent.getClientY(b), b = mxUtils.convertPoint(c.container, d, b), d = c.view.getState(c.getCellAt(b.x, b.y));
+        return d
+      };
+    mxEvent.addGestureListeners(a.text.node, mxUtils.bind(this, function(b) {
+      this.isLabelEvent(a, b) && (c.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(b, a)), e = c.dialect !=
+        mxConstants.DIALECT_SVG && "IMG" == mxEvent.getSource(b).nodeName)
+    }), mxUtils.bind(this, function(b) {
+      this.isLabelEvent(a, b) && c.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(b, f(b)))
+    }), mxUtils.bind(this, function(b) {
+      this.isLabelEvent(a, b) && (c.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(b, f(b))), e = !1)
+    }));
+    mxEvent.addListener(a.text.node, "dblclick", mxUtils.bind(this, function(b) {
+      this.isLabelEvent(a, b) && (c.dblClick(b, a.cell), mxEvent.consume(b))
+    }))
+  }
+};
+mxCellRenderer.prototype.initializeLabel = function(a) {
+  var b = a.view.graph;
+  a.text.dialect != mxConstants.DIALECT_SVG && (mxClient.IS_SVG && mxClient.NO_FO ? a.text.init(b.container) : mxUtils.isVml(a.view.getDrawPane()) && (null != a.shape.label ? a.text.init(a.shape.label) :
+    a.text.init(a.shape.node)));
+  null == a.text.node && (a.text.init(a.view.getDrawPane()), null != a.shape && null != a.text && a.shape.node.parentNode.insertBefore(a.text.node, a.shape.node.nextSibling))
+};
+mxCellRenderer.prototype.createCellOverlays = function(a) {
+  var b = a.view.graph.getCellOverlays(a.cell),
+    c = null;
+  if (null != b)
+    for (var c = new mxDictionary, d = 0; d < b.length; d++) {
+      var e = null != a.overlays ? a.overlays.remove(b[d]) : null;
+      null == e && (e = new mxImageShape(new mxRectangle, b[d].image.src), e.dialect = a.view.graph.dialect, e.preserveImageAspect = !1, e.overlay = b[d], this.initializeOverlay(a, e), this.installCellOverlayListeners(
+        a, b[d], e), null != b[d].cursor && (e.node.style.cursor = b[d].cursor));
+      c.put(b[d], e)
+    }
+  null != a.overlays &&
+    a.overlays.visit(function(a, b) {
+      b.destroy()
+    });
+  a.overlays = c
+};
+mxCellRenderer.prototype.initializeOverlay = function(a, b) {
+  b.init(a.view.getOverlayPane())
+};
+mxCellRenderer.prototype.installCellOverlayListeners = function(a, b, c) {
+  var d = a.view.graph;
+  mxEvent.addListener(c.node, "click", function(c) {
+    d.isEditing() && d.stopEditing(!d.isInvokesStopCellEditing());
+    b.fireEvent(new mxEventObject(mxEvent.CLICK, "event", c, "cell", a.cell))
+  });
+  mxEvent.addGestureListeners(c.node, function(a) {
+    mxEvent.consume(a)
+  }, function(b) {
+    d.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(b, a))
+  });
+  mxClient.IS_TOUCH && mxEvent.addListener(c.node, "touchend", function(c) {
+    b.fireEvent(new mxEventObject(mxEvent.CLICK,
+      "event", c, "cell", a.cell))
+  })
+};
+mxCellRenderer.prototype.createControl = function(a) {
+  var b = a.view.graph,
+    c = b.getFoldingImage(a);
+  if (b.foldingEnabled && null != c) {
+    if (null == a.control) {
+      var d = new mxRectangle(0, 0, c.width, c.height);
+      a.control = new mxImageShape(d, c.src);
+      a.control.preserveImageAspect = !1;
+      a.control.dialect = b.dialect;
+      this.initControl(a, a.control, !0, function(c) {
+        if (b.isEnabled()) {
+          var d = !b.isCellCollapsed(a.cell);
+          b.foldCells(d, !1, [a.cell]);
+          mxEvent.consume(c)
+        }
+      })
+    }
+  } else null != a.control && (a.control.destroy(), a.control = null)
+};
+mxCellRenderer.prototype.initControl = function(a, b, c, d) {
+  var e = a.view.graph;
+  e.isHtmlLabel(a.cell) && mxClient.NO_FO && e.dialect == mxConstants.DIALECT_SVG ? (b.dialect = mxConstants.DIALECT_PREFERHTML, b.init(e.container), b.node.style.zIndex = 1) : b.init(a.view.getOverlayPane());
+  b = b.innerNode || b.node;
+  d && (e.isEnabled() && (b.style.cursor = "pointer"), mxEvent.addListener(b, "click", d));
+  c && mxEvent.addGestureListeners(b, function(b) {
+    e.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(b, a));
+    mxEvent.consume(b)
+  }, function(b) {
+    e.fireMouseEvent(mxEvent.MOUSE_MOVE,
+      new mxMouseEvent(b, a))
+  });
+  return b
+};
+mxCellRenderer.prototype.isShapeEvent = function(a, b) {
+  return !0
+};
+mxCellRenderer.prototype.isLabelEvent = function(a, b) {
+  return !0
+};
+mxCellRenderer.prototype.installListeners = function(a) {
+  var b = a.view.graph,
+    c = function(c) {
+      var d = a;
+      if (b.dialect != mxConstants.DIALECT_SVG && "IMG" == mxEvent.getSource(c).nodeName || mxClient.IS_TOUCH) d = mxEvent.getClientX(c), c = mxEvent.getClientY(c), c = mxUtils.convertPoint(b.container,
+        d, c), d = b.view.getState(b.getCellAt(c.x, c.y));
+      return d
+    },
+    d = !1;
+  mxEvent.addListener(a.shape.node, "gesturestart", mxUtils.bind(this, function(a) {
+    b.lastTouchTime = 0;
+    d = !0;
+    mxEvent.consume(a)
+  }));
+  mxEvent.addGestureListeners(a.shape.node, mxUtils.bind(this,
+    function(c) {
+      this.isShapeEvent(a, c) && !d ? b.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(c, null != a.shape && mxEvent.getSource(c) == a.shape.content ? null : a)) : d && mxEvent.consume(c)
+    }), mxUtils.bind(this, function(e) {
+    this.isShapeEvent(a, e) && !d ? b.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(e, null != a.shape && mxEvent.getSource(e) == a.shape.content ? null : c(e))) : d && mxEvent.consume(
+      e)
+  }), mxUtils.bind(this, function(e) {
+    this.isShapeEvent(a, e) && !d ? b.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(e, null != a.shape &&
+      mxEvent.getSource(e) == a.shape.content ? null : c(e))) : d && mxEvent.consume(e)
+  }));
+  var e = mxClient.IS_TOUCH ? "gestureend" : "dblclick";
+  mxEvent.addListener(a.shape.node, e, mxUtils.bind(this, function(c) {
+    d = !1;
+    "gestureend" == e ? (b.lastTouchTime = 0, b.gestureEnabled && (b.handleGesture(a, c), mxEvent.consume(c))) : this.isShapeEvent(a, c) && (b.dblClick(c, null != a.shape && mxEvent.getSource(
+      c) == a.shape.content ? null : a.cell), mxEvent.consume(c))
+  }))
+};
+mxCellRenderer.prototype.redrawLabel = function(a, b) {
+  var c = this.getLabelValue(a);
+  if (null == a.text && null != c && (mxUtils.isNode(c) || 0 < c.length)) this.createLabel(a, c);
+  else if (null != a.text && (null == c || 0 == c.length)) a.text.destroy(), a.text = null;
+  if (null != a.text) {
+    var d = a.view.graph,
+      e = d.isWrapping(a.cell),
+      d = d.isLabelClipped(a.cell),
+      f = this.getLabelBounds(a);
+    if (b || a.text.value != c || a.text.isWrapping != e || a.text.isClipping != d || a.text.scale != a.view.scale || !a.text.bounds.equals(f)) a.text.value = c, a.text.bounds = f, a.text.scale =
+      this.getTextScale(a), a.text.isWrapping = e, a.text.isClipping = d, a.text.redraw()
+  }
+};
+mxCellRenderer.prototype.getTextScale = function(a) {
+  return a.view.scale
+};
+mxCellRenderer.prototype.getLabelBounds = function(a) {
+  var b = a.view.graph,
+    c = a.view.scale,
+    d = b.getModel().isEdge(a.cell),
+    e = new mxRectangle(a.absoluteOffset.x, a.absoluteOffset.y);
+  a.text.updateMargin();
+  d ? (d = a.text.getSpacing(), e.x += d.x * c, e.y += d.y * c, b = b.getCellGeometry(a.cell), null != b && (e.width = Math.max(0, a.text.margin.x * b.width * c - a.text.spacingLeft * c - a.text.spacingRight *
+    c), e.height = Math.max(0, a.text.margin.y * b.height * c - a.text.spacingTop * c - a.text.spacingBottom * c))) : (a.text.isPaintBoundsInverted() && (d = e.x,
+      e.x = e.y, e.y = d), e.x += a.x, e.y += a.y, e.width = Math.max(1, a.width), e.height = Math.max(1, a.height), b.isSwimlane(a.cell) && (b = b.getStartSize(a.cell), 0 < b.width ? (d = Math.min(
+      e.width, b.width * c), a.shape.flipH && (e.x += e.width - d), e.width = d) : 0 < b.height && (d = Math.min(e.height, b.height * c), a.shape.flipV && (e.y += e.height - d), e.height = d)),
+    this.rotateLabelBounds(a, e));
+  return e
+};
+mxCellRenderer.prototype.rotateLabelBounds = function(a, b) {
+  if (a.text.isPaintBoundsInverted()) {
+    var c = (a.width - a.height) / 2;
+    b.x += c;
+    b.y -= c;
+    c = b.width;
+    b.width = b.height;
+    b.height = c
+  }
+  b.x -= a.text.margin.x * b.width;
+  b.y -= a.text.margin.y * b.height;
+  if ("fill" != a.style[mxConstants.STYLE_OVERFLOW]) {
+    var c = a.view.scale,
+      d = a.text.getSpacing();
+    b.x += d.x * c;
+    b.y += d.y * c;
+    b.width = Math.max(0, b.width - a.text.spacingLeft * c - a.text.spacingRight * c);
+    b.height = Math.max(0, b.height - a.text.spacingTop * c - a.text.spacingBottom * c)
+  }
+  var e = a.text.getTextRotation();
+  if (0 != e && (null != a && a.view.graph.model.isVertex(a.cell)) && (c = a.getCenterX(), d = a.getCenterY(), b.x != c || b.y != d)) e *= Math.PI / 180, pt = mxUtils.getRotatedPoint(new mxPoint(b.x,
+    b.y), Math.cos(e), Math.sin(e), new mxPoint(c, d)), b.x = pt.x, b.y = pt.y
+};
+mxCellRenderer.prototype.redrawCellOverlays = function(a, b) {
+  this.createCellOverlays(a);
+  if (null != a.overlays) {
+    var c = mxUtils.mod(mxUtils.getValue(a.style, mxConstants.STYLE_ROTATION, 0), 90),
+      d = mxUtils.toRadians(c),
+      e = Math.cos(d),
+      f = Math.sin(d);
+    a.overlays.visit(function(d, h) {
+      var k = h.overlay.getBounds(a);
+      if (!a.view.graph.getModel().isEdge(a.cell) && null != a.shape && 0 != c) {
+        var l = k.getCenterX(),
+          m = k.getCenterY(),
+          m = mxUtils.getRotatedPoint(new mxPoint(l, m), e, f, new mxPoint(a.getCenterX(), a.getCenterY())),
+          l = m.x,
+          m = m.y;
+        k.x =
+          Math.round(l - k.width / 2);
+        k.y = Math.round(m - k.height / 2)
+      }
+      if (b || null == h.bounds || h.scale != a.view.scale || !h.bounds.equals(k)) h.bounds = k, h.scale = a.view.scale, h.redraw()
+    })
+  }
+};
+mxCellRenderer.prototype.redrawControl = function(a, b) {
+  if (null != a.control) {
+    var c = this.getControlBounds(a),
+      d = this.legacyControlPosition ? mxUtils.getValue(a.style, mxConstants.STYLE_ROTATION, 0) : a.shape.getTextRotation(),
+      e = a.view.scale;
+    if (b || a.control.scale != e || !a.control.bounds.equals(c) || a.control.rotation != d) a.control.rotation = d, a.control.bounds = c, a.control.scale = e, a.control.redraw()
+  }
+};
+mxCellRenderer.prototype.getControlBounds = function(a) {
+  if (null != a.control) {
+    var b = a.control.scale,
+      c = a.control.bounds.width / b,
+      b = a.control.bounds.height / b,
+      d = a.view.scale,
+      e = a.getCenterX(),
+      f = a.getCenterY();
+    if (!a.view.graph.getModel().isEdge(a.cell) && (e = a.x + c * d, f = a.y + b * d, null != a.shape)) {
+      var g = a.shape.getShapeRotation();
+      if (this.legacyControlPosition) g = mxUtils.getValue(a.style, mxConstants.STYLE_ROTATION, 0);
+      else if (a.shape.isPaintBoundsInverted()) var h = (a.width - a.height) / 2,
+        e = e + h,
+        f = f - h;
+      0 != g && (h = mxUtils.toRadians(g),
+        g = Math.cos(h), h = Math.sin(h), f = mxUtils.getRotatedPoint(new mxPoint(e, f), g, h, new mxPoint(a.getCenterX(), a.getCenterY())), e = f.x, f = f.y)
+    }
+    return a.view.graph.getModel().isEdge(a.cell), new mxRectangle(Math.round(e - c / 2 * d), Math.round(f - b / 2 * d), Math.round(c * d), Math.round(b * d))
+  }
+  return null
+};
+mxCellRenderer.prototype.redraw = function(a, b, c) {
+  if (null != a.shape) {
+    var d = !1;
+    a.view.graph.getModel().isEdge(a.cell);
+    reconfigure = null != b ? b : !1;
+    this.createControl(a);
+    if (a.orderChanged || a.invalidOrder) a.view.graph.ordered ? this.order(a) : this.orderEdge(a);
+    if (a.orderChanged || !mxUtils.equalEntries(a.shape.style, a.style)) this.configureShape(a), b = !0;
+    delete a.invalidOrder;
+    delete a.orderChanged;
+    if (b || null == a.shape.bounds || a.shape.scale != a.view.scale || !a.shape.bounds.equals(a) || !mxUtils.equalPoints(a.shape.points,
+        a.absolutePoints)) d = !0, a.shape.points = null != a.absolutePoints ? a.absolutePoints.slice() : null, a.shape.bounds = new mxRectangle(a.x, a.y, a.width, a.height), a.shape.scale = a.view
+      .scale, null == c || c ? a.shape.redraw() : a.shape.updateBoundingBox();
+    if (null == c || c) this.redrawLabel(a, d), this.redrawCellOverlays(a, d), this.redrawControl(a, d)
+  }
+};
+mxCellRenderer.prototype.destroy = function(a) {
+  null != a.shape && (null != a.text && (a.text.destroy(), a.text = null), null != a.overlays && (a.overlays.visit(function(a, c) {
+    c.destroy()
+  }), a.overlays = null), null != a.control && (a.control.destroy(), a.control = null), a.shape.destroy(), a.shape = null)
+};
+var mxEdgeStyle = {
+    EntityRelation: function(a, b, c, d, e) {
+      var f = a.view,
+        g = f.graph;
+      d = mxUtils.getValue(a.style, mxConstants.STYLE_SEGMENT, mxConstants.ENTITY_SEGMENT) * f.scale;
+      var h = a.absolutePoints,
+        k = h[0],
+        l = h[h.length - 1],
+        h = !1;
+      if (null != k) b = new mxCellState, b.x = k.x, b.y = k.y;
+      else if (null != b) {
+        var m = mxUtils.getPortConstraints(b, a, !0, mxConstants.DIRECTION_MASK_NONE);
+        m != mxConstants.DIRECTION_MASK_NONE ? h = m == mxConstants.DIRECTION_MASK_WEST : (k = g.getCellGeometry(b.cell), k.relative ? h = 0.5 >= k.x : null != c && (h = c.x + c.width < b.x))
+      } else return;
+      k = !0;
+      null != l ? (c = new mxCellState, c.x = l.x, c.y = l.y) : null != c && (m = mxUtils.getPortConstraints(c, a, !1, mxConstants.DIRECTION_MASK_NONE), m != mxConstants.DIRECTION_MASK_NONE ? k = m ==
+        mxConstants.DIRECTION_MASK_WEST : (a = g.getCellGeometry(c.cell), a.relative ? k = 0.5 >= a.x : null != b && (k = b.x + b.width < c.x)));
+      null != b && null != c && (a = h ? b.x : b.x + b.width, b = f.getRoutingCenterY(b), g = k ? c.x : c.x + c.width, c = f.getRoutingCenterY(c), f = new mxPoint(a + (h ? -d : d), b), l = new mxPoint(
+        g + (k ? -d : d), c), h == k ? (d = h ? Math.min(a, g) - d : Math.max(a, g) + d, e.push(new mxPoint(d,
+        b)), e.push(new mxPoint(d, c))) : (f.x < l.x == h ? (d = b + (c - b) / 2, e.push(f), e.push(new mxPoint(f.x, d)), e.push(new mxPoint(l.x, d))) : e.push(f), e.push(l)))
+    },
+    Loop: function(a, b, c, d, e) {
+      if (null != b) {
+        c = a.view;
+        var f = c.graph;
+        d = null != d && 0 < d.length ? d[0] : null;
+        null != d && (d = c.transformControlPoint(a, d), mxUtils.contains(b, d.x, d.y) && (d = null));
+        var g = 0,
+          h = 0,
+          k = 0,
+          l = 0,
+          f = mxUtils.getValue(a.style, mxConstants.STYLE_SEGMENT, f.gridSize) * c.scale;
+        a = mxUtils.getValue(a.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_WEST);
+        a == mxConstants.DIRECTION_NORTH ||
+          a == mxConstants.DIRECTION_SOUTH ? (g = c.getRoutingCenterX(b), h = f) : (k = c.getRoutingCenterY(b), l = f);
+        null == d || d.x < b.x || d.x > b.x + b.width ? null != d ? (g = d.x, l = Math.max(Math.abs(k - d.y), l)) : a == mxConstants.DIRECTION_NORTH ? k = b.y - 2 * h : a == mxConstants.DIRECTION_SOUTH ?
+          k = b.y + b.height + 2 * h : g = a == mxConstants.DIRECTION_EAST ? b.x - 2 * l : b.x + b.width + 2 * l : null != d && (g = c.getRoutingCenterX(b), h = Math.max(Math.abs(g - d.x), l), k =
+            d.y, l = 0);
+        e.push(new mxPoint(g - h, k - l));
+        e.push(new mxPoint(g + h, k + l))
+      }
+    },
+    ElbowConnector: function(a, b, c, d, e) {
+      var f = null !=
+        d && 0 < d.length ? d[0] : null,
+        g = !1,
+        h = !1;
+      if (null != b && null != c)
+        if (null != f) var k = Math.min(b.x, c.x),
+          l = Math.max(b.x + b.width, c.x + c.width),
+          h = Math.min(b.y, c.y),
+          m = Math.max(b.y + b.height, c.y + c.height),
+          f = a.view.transformControlPoint(a, f),
+          g = f.y < h || f.y > m,
+          h = f.x < k || f.x > l;
+        else k = Math.max(b.x, c.x), l = Math.min(b.x + b.width, c.x + c.width), g = k == l, g || (h = Math.max(b.y, c.y), m = Math.min(b.y + b.height, c.y + c.height), h = h == m);
+        !h && (g || a.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL) ? mxEdgeStyle.TopToBottom(a, b, c, d, e) : mxEdgeStyle.SideToSide(a,
+        b, c, d, e)
+    },
+    SideToSide: function(a, b, c, d, e) {
+      var f = a.view;
+      d = null != d && 0 < d.length ? d[0] : null;
+      var g = a.absolutePoints,
+        h = g[0],
+        g = g[g.length - 1];
+      null != d && (d = f.transformControlPoint(a, d));
+      null != h && (b = new mxCellState, b.x = h.x, b.y = h.y);
+      null != g && (c = new mxCellState, c.x = g.x, c.y = g.y);
+      null != b && null != c && (a = Math.max(b.x, c.x), h = Math.min(b.x + b.width, c.x + c.width), a = null != d ? d.x : h + (a - h) / 2, h = f.getRoutingCenterY(b), f = f.getRoutingCenterY(c),
+        null != d && (d.y >= b.y && d.y <= b.y + b.height && (h = d.y), d.y >= c.y && d.y <= c.y + c.height && (f = d.y)), !mxUtils.contains(c,
+          a, h) && !mxUtils.contains(b, a, h) && e.push(new mxPoint(a, h)), !mxUtils.contains(c, a, f) && !mxUtils.contains(b, a, f) && e.push(new mxPoint(a, f)), 1 == e.length && (null != d ? !
+          mxUtils.contains(c, a, d.y) && !mxUtils.contains(b, a, d.y) && e.push(new mxPoint(a, d.y)) : (f = Math.max(b.y, c.y), b = Math.min(b.y + b.height, c.y + c.height), e.push(new mxPoint(a,
+            f + (b - f) / 2)))))
+    },
+    TopToBottom: function(a, b, c, d, e) {
+      var f = a.view;
+      d = null != d && 0 < d.length ? d[0] : null;
+      var g = a.absolutePoints,
+        h = g[0],
+        g = g[g.length - 1];
+      null != d && (d = f.transformControlPoint(a, d));
+      null !=
+        h && (b = new mxCellState, b.x = h.x, b.y = h.y);
+      null != g && (c = new mxCellState, c.x = g.x, c.y = g.y);
+      null != b && null != c && (h = Math.max(b.y, c.y), g = Math.min(b.y + b.height, c.y + c.height), a = f.getRoutingCenterX(b), null != d && (d.x >= b.x && d.x <= b.x + b.width) && (a = d.x), h =
+        null != d ? d.y : g + (h - g) / 2, !mxUtils.contains(c, a, h) && !mxUtils.contains(b, a, h) && e.push(new mxPoint(a, h)), a = null != d && d.x >= c.x && d.x <= c.x + c.width ? d.x : f.getRoutingCenterX(
+          c), !mxUtils.contains(c, a, h) && !mxUtils.contains(b, a, h) && e.push(new mxPoint(a, h)), 1 == e.length && (null != d && 1 ==
+          e.length ? !mxUtils.contains(c, d.x, h) && !mxUtils.contains(b, d.x, h) && e.push(new mxPoint(d.x, h)) : (f = Math.max(b.x, c.x), b = Math.min(b.x + b.width, c.x + c.width), e.push(new mxPoint(
+            f + (b - f) / 2, h)))))
+    },
+    SegmentConnector: function(a, b, c, d, e) {
+      var f = a.absolutePoints,
+        g = !0,
+        h = null,
+        k = f[0];
+      null == k && null != b ? k = new mxPoint(a.view.getRoutingCenterX(b), a.view.getRoutingCenterY(b)) : null != k && (k = k.clone());
+      var l = f.length - 1;
+      if (null != d && 0 < d.length) {
+        for (var h = a.view.transformControlPoint(a, d[0]), m = b, n = f[0], p = !1, q = !1, p = h, s = d.length, r =
+            0; 2 > r; r++) {
+          var t = null != n && n.x == p.x,
+            v = null != n && n.y == p.y,
+            w = null != m && p.y >= m.y && p.y <= m.y + m.height,
+            m = null != m && p.x >= m.x && p.x <= m.x + m.width,
+            p = v || null == n && w,
+            q = t || null == n && m;
+          if (null != n && !v && !t && (w || m)) {
+            g = w ? !1 : !0;
+            break
+          }
+          if (q || p) {
+            g = p;
+            1 == r && (g = 0 == d.length % 2 ? p : q);
+            break
+          }
+          m = c;
+          n = f[l];
+          p = a.view.transformControlPoint(a, d[s - 1])
+        }
+        g && (null != f[0] && f[0].y != h.y || null == f[0] && null != b && (h.y < b.y || h.y > b.y + b.height)) ? e.push(new mxPoint(k.x, h.y)) : !g && (null != f[0] && f[0].x != h.x || null == f[
+          0] && null != b && (h.x < b.x || h.x > b.x + b.width)) && e.push(new mxPoint(h.x,
+          k.y));
+        g ? k.y = h.y : k.x = h.x;
+        for (r = 0; r < d.length; r++) g = !g, h = a.view.transformControlPoint(a, d[r]), g ? k.y = h.y : k.x = h.x, e.push(k.clone())
+      } else h = k, g = !0;
+      k = f[l];
+      null == k && null != c && (k = new mxPoint(a.view.getRoutingCenterX(c), a.view.getRoutingCenterY(c)));
+      g && (null != f[l] && f[l].y != h.y || null == f[l] && null != c && (h.y < c.y || h.y > c.y + c.height)) ? e.push(new mxPoint(k.x, h.y)) : !g && (null != f[l] && f[l].x != h.x || null == f[l] &&
+        null != c && (h.x < c.x || h.x > c.x + c.width)) && e.push(new mxPoint(h.x, k.y));
+      if (null == f[0] && null != b)
+        for (; 1 < e.length && mxUtils.contains(b,
+            e[1].x, e[1].y);) e = e.splice(1, 1);
+      if (null == f[l] && null != c)
+        for (; 1 < e.length && mxUtils.contains(c, e[e.length - 1].x, e[e.length - 1].y);) e = e.splice(e.length - 1, 1)
+    },
+    orthBuffer: 10,
+    dirVectors: [
+      [-1, 0],
+      [0, -1],
+      [1, 0],
+      [0, 1],
+      [-1, 0],
+      [0, -1],
+      [1, 0]
+    ],
+    wayPoints1: [
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0],
+      [0, 0]
+    ],
+    routePatterns: [
+      [
+        [513, 2308, 2081, 2562],
+        [513, 1090, 514, 2184, 2114, 2561],
+        [513, 1090, 514, 2564, 2184, 2562],
+        [513, 2308, 2561, 1090, 514, 2568, 2308]
+      ],
+      [
+        [514, 1057, 513, 2308, 2081, 2562],
+        [514, 2184, 2114, 2561],
+        [514, 2184,
+          2562, 1057, 513, 2564, 2184
+        ],
+        [514, 1057, 513, 2568, 2308, 2561]
+      ],
+      [
+        [1090, 514, 1057, 513, 2308, 2081, 2562],
+        [2114, 2561],
+        [1090, 2562, 1057, 513, 2564, 2184],
+        [1090, 514, 1057, 513, 2308, 2561, 2568]
+      ],
+      [
+        [2081, 2562],
+        [1057, 513, 1090, 514, 2184, 2114, 2561],
+        [1057, 513, 1090, 514, 2184, 2562, 2564],
+        [1057, 2561, 1090, 514, 2568, 2308]
+      ]
+    ],
+    inlineRoutePatterns: [
+      [null, [2114, 2568], null, null],
+      [null, [514, 2081, 2114, 2568], null, null],
+      [null, [2114, 2561], null, null],
+      [
+        [2081, 2562],
+        [1057, 2114, 2568],
+        [2184, 2562], null
+      ]
+    ],
+    vertexSeperations: [],
+    limits: [
+      [0, 0, 0, 0, 0, 0, 0, 0, 0],
+      [0, 0, 0, 0, 0, 0, 0, 0, 0]
+    ],
+    LEFT_MASK: 32,
+    TOP_MASK: 64,
+    RIGHT_MASK: 128,
+    BOTTOM_MASK: 256,
+    LEFT: 1,
+    TOP: 2,
+    RIGHT: 4,
+    BOTTOM: 8,
+    SIDE_MASK: 480,
+    CENTER_MASK: 512,
+    SOURCE_MASK: 1024,
+    TARGET_MASK: 2048,
+    VERTEX_MASK: 3072,
+    OrthConnector: function(a, b, c, d, e) {
+      var f = a.view.graph,
+        g = null == b ? !1 : f.getModel().isEdge(b.cell),
+        f = null == c ? !1 : f.getModel().isEdge(c.cell);
+      if (null != d && 0 < d.length || g || f) mxEdgeStyle.SegmentConnector(a, b, c, d, e);
+      else {
+        d = a.absolutePoints;
+        var h = d[0],
+          k = d[d.length - 1];
+        d = null != b ? b.x : h.x;
+        var g = null != b ? b.y : h.y,
+          l = null != b ? b.width :
+          1,
+          m = null != b ? b.height : 1,
+          n = null != c ? c.x : k.x,
+          p = null != c ? c.y : k.y,
+          q = null != c ? c.width : 1,
+          s = null != c ? c.height : 1,
+          f = a.view.scale * mxEdgeStyle.orthBuffer,
+          r = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
+        null != b && (r[0] = mxUtils.getPortConstraints(b, a, !0, mxConstants.DIRECTION_MASK_ALL));
+        null != c && (r[1] = mxUtils.getPortConstraints(c, a, !1, mxConstants.DIRECTION_MASK_ALL));
+        a = [0, 0];
+        d = [
+          [d, g, l, m],
+          [n, p, q, s]
+        ];
+        for (l = 0; 2 > l; l++) mxEdgeStyle.limits[l][1] = d[l][0] - f, mxEdgeStyle.limits[l][2] = d[l][1] - f, mxEdgeStyle.limits[l][4] =
+          d[l][0] + d[l][2] + f, mxEdgeStyle.limits[l][8] = d[l][1] + d[l][3] + f;
+        l = d[0][0] + d[0][2] / 2 - (d[1][0] + d[1][2] / 2);
+        m = d[0][1] + d[0][3] / 2 - (d[1][1] + d[1][3] / 2);
+        g = 0;
+        0 > l ? g = 0 > m ? 2 : 1 : 0 >= m && (g = 3, 0 == l && (g = 2));
+        m = null;
+        null != b && (m = h);
+        b = [
+          [0.5, 0.5],
+          [0.5, 0.5]
+        ];
+        for (l = 0; 2 > l; l++) null != m && (b[l][0] = (m.x - d[l][0]) / d[l][2], 0.01 > b[l][0] ? a[l] = mxConstants.DIRECTION_MASK_WEST : 0.99 < b[l][0] && (a[l] = mxConstants.DIRECTION_MASK_EAST),
+            b[l][1] = (m.y - d[l][1]) / d[l][3], 0.01 > b[l][1] ? a[l] = mxConstants.DIRECTION_MASK_NORTH : 0.99 < b[l][1] && (a[l] = mxConstants.DIRECTION_MASK_SOUTH)),
+          m = null, null != c && (m = k);
+        l = d[0][1] - (d[1][1] + d[1][3]);
+        m = d[0][0] - (d[1][0] + d[1][2]);
+        n = d[1][1] - (d[0][1] + d[0][3]);
+        p = d[1][0] - (d[0][0] + d[0][2]);
+        mxEdgeStyle.vertexSeperations[1] = Math.max(m - 2 * f, 0);
+        mxEdgeStyle.vertexSeperations[2] = Math.max(l - 2 * f, 0);
+        mxEdgeStyle.vertexSeperations[4] = Math.max(n - 2 * f, 0);
+        mxEdgeStyle.vertexSeperations[3] = Math.max(p - 2 * f, 0);
+        c = [];
+        h = [];
+        k = [];
+        h[0] = m >= p ? mxConstants.DIRECTION_MASK_WEST : mxConstants.DIRECTION_MASK_EAST;
+        k[0] = l >= n ? mxConstants.DIRECTION_MASK_NORTH : mxConstants.DIRECTION_MASK_SOUTH;
+        h[1] = mxUtils.reversePortConstraints(h[0]);
+        k[1] = mxUtils.reversePortConstraints(k[0]);
+        m = m >= p ? m : p;
+        n = l >= n ? l : n;
+        p = [
+          [0, 0],
+          [0, 0]
+        ];
+        q = !1;
+        for (l = 0; 2 > l; l++) 0 == a[l] && (0 == (h[l] & r[l]) && (h[l] = mxUtils.reversePortConstraints(h[l])), 0 == (k[l] & r[l]) && (k[l] = mxUtils.reversePortConstraints(k[l])), p[l][0] = k[l],
+          p[l][1] = h[l]);
+        n > 2 * f && m > 2 * f && (0 < (h[0] & r[0]) && 0 < (k[1] & r[1]) ? (p[0][0] = h[0], p[0][1] = k[0], p[1][0] = k[1], p[1][1] = h[1], q = !0) : 0 < (k[0] & r[0]) && 0 < (h[1] & r[1]) && (p[0]
+          [0] = k[0], p[0][1] = h[0], p[1][0] = h[1], p[1][1] = k[1], q = !0));
+        n > 2 * f && !q &&
+          (p[0][0] = k[0], p[0][1] = h[0], p[1][0] = k[1], p[1][1] = h[1], q = !0);
+        m > 2 * f && !q && (p[0][0] = h[0], p[0][1] = k[0], p[1][0] = h[1], p[1][1] = k[1]);
+        for (l = 0; 2 > l; l++)
+          if (0 == a[l] && (0 == (p[l][0] & r[l]) && (p[l][0] = p[l][1]), c[l] = p[l][0] & r[l], c[l] |= (p[l][1] & r[l]) << 8, c[l] |= (p[1 - l][l] & r[l]) << 16, c[l] |= (p[1 - l][1 - l] & r[l]) <<
+              24, 0 == (c[l] & 15) && (c[l] <<= 8), 0 == (c[l] & 3840) && (c[l] = c[l] & 15 | c[l] >> 8), 0 == (c[l] & 983040) && (c[l] = c[l] & 65535 | (c[l] & 251658240) >> 8), a[l] = c[l] & 15,
+              r[l] == mxConstants.DIRECTION_MASK_WEST || r[l] == mxConstants.DIRECTION_MASK_NORTH || r[l] ==
+              mxConstants.DIRECTION_MASK_EAST || r[l] == mxConstants.DIRECTION_MASK_SOUTH)) a[l] = r[l];
+        l = a[0] == mxConstants.DIRECTION_MASK_EAST ? 3 : a[0];
+        r = a[1] == mxConstants.DIRECTION_MASK_EAST ? 3 : a[1];
+        l -= g;
+        r -= g;
+        1 > l && (l += 4);
+        1 > r && (r += 4);
+        r = mxEdgeStyle.routePatterns[l - 1][r - 1];
+        mxEdgeStyle.wayPoints1[0][0] = d[0][0];
+        mxEdgeStyle.wayPoints1[0][1] = d[0][1];
+        switch (a[0]) {
+          case mxConstants.DIRECTION_MASK_WEST:
+            mxEdgeStyle.wayPoints1[0][0] -= f;
+            mxEdgeStyle.wayPoints1[0][1] += b[0][1] * d[0][3];
+            break;
+          case mxConstants.DIRECTION_MASK_SOUTH:
+            mxEdgeStyle.wayPoints1[0][0] +=
+              b[0][0] * d[0][2];
+            mxEdgeStyle.wayPoints1[0][1] += d[0][3] + f;
+            break;
+          case mxConstants.DIRECTION_MASK_EAST:
+            mxEdgeStyle.wayPoints1[0][0] += d[0][2] + f;
+            mxEdgeStyle.wayPoints1[0][1] += b[0][1] * d[0][3];
+            break;
+          case mxConstants.DIRECTION_MASK_NORTH:
+            mxEdgeStyle.wayPoints1[0][0] += b[0][0] * d[0][2], mxEdgeStyle.wayPoints1[0][1] -= f
+        }
+        f = 0;
+        h = c = 0 < (a[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) ? 0 : 1;
+        for (l = k = 0; l < r.length; l++) {
+          k = r[l] & 15;
+          s = k == mxConstants.DIRECTION_MASK_EAST ? 3 : k;
+          s += g;
+          4 < s && (s -= 4);
+          m = mxEdgeStyle.dirVectors[s -
+            1];
+          k = 0 < s % 2 ? 0 : 1;
+          k != c && (f++, mxEdgeStyle.wayPoints1[f][0] = mxEdgeStyle.wayPoints1[f - 1][0], mxEdgeStyle.wayPoints1[f][1] = mxEdgeStyle.wayPoints1[f - 1][1]);
+          var t = 0 < (r[l] & mxEdgeStyle.TARGET_MASK),
+            q = 0 < (r[l] & mxEdgeStyle.SOURCE_MASK),
+            n = (r[l] & mxEdgeStyle.SIDE_MASK) >> 5,
+            n = n << g;
+          15 < n && (n >>= 4);
+          p = 0 < (r[l] & mxEdgeStyle.CENTER_MASK);
+          (q || t) && 9 > n ? (s = 0, q = q ? 0 : 1, s = p && 0 == k ? d[q][0] + b[q][0] * d[q][2] : p ? d[q][1] + b[q][1] * d[q][3] : mxEdgeStyle.limits[q][n], 0 == k ? (n = (s - mxEdgeStyle.wayPoints1[
+            f][0]) * m[0], 0 < n && (mxEdgeStyle.wayPoints1[f][0] +=
+            m[0] * n)) : (n = (s - mxEdgeStyle.wayPoints1[f][1]) * m[1], 0 < n && (mxEdgeStyle.wayPoints1[f][1] += m[1] * n))) : p && (mxEdgeStyle.wayPoints1[f][0] += m[0] * Math.abs(mxEdgeStyle.vertexSeperations[
+            s] / 2), mxEdgeStyle.wayPoints1[f][1] += m[1] * Math.abs(mxEdgeStyle.vertexSeperations[s] / 2));
+          0 < f && mxEdgeStyle.wayPoints1[f][k] == mxEdgeStyle.wayPoints1[f - 1][k] ? f-- : c = k
+        }
+        for (l = 0; l <= f && !(l == f && ((0 < (a[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) ? 0 : 1) == h ? 0 : 1) != (f + 1) % 2); l++) e.push(new mxPoint(
+          mxEdgeStyle.wayPoints1[l][0],
+          mxEdgeStyle.wayPoints1[l][1]))
+      }
+    },
+    getRoutePattern: function(a, b, c, d) {
+      var e = a[0] == mxConstants.DIRECTION_MASK_EAST ? 3 : a[0];
+      a = a[1] == mxConstants.DIRECTION_MASK_EAST ? 3 : a[1];
+      e -= b;
+      a -= b;
+      1 > e && (e += 4);
+      1 > a && (a += 4);
+      b = routePatterns[e - 1][a - 1];
+      if (0 == c || 0 == d) null != inlineRoutePatterns[e - 1][a - 1] && (b = inlineRoutePatterns[e - 1][a - 1]);
+      return b
+    }
+  },
+  mxStyleRegistry = {
+    values: [],
+    putValue: function(a, b) {
+      mxStyleRegistry.values[a] = b
+    },
+    getValue: function(a) {
+      return mxStyleRegistry.values[a]
+    },
+    getName: function(a) {
+      for (var b in mxStyleRegistry.values)
+        if (mxStyleRegistry.values[b] ==
+          a) return b;
+      return null
+    }
+  };
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
+
+function mxGraphView(a) {
+  this.graph = a;
+  this.translate = new mxPoint;
+  this.graphBounds = new mxRectangle;
+  this.states = new mxDictionary
+}
+mxGraphView.prototype = new mxEventSource;
+mxGraphView.prototype.constructor = mxGraphView;
+mxGraphView.prototype.EMPTY_POINT = new mxPoint;
+mxGraphView.prototype.doneResource = "none" != mxClient.language ? "done" : "";
+mxGraphView.prototype.updatingDocumentResource = "none" != mxClient.language ? "updatingDocument" : "";
+mxGraphView.prototype.allowEval = !1;
+mxGraphView.prototype.captureDocumentGesture = !0;
+mxGraphView.prototype.optimizeVmlReflows = !0;
+mxGraphView.prototype.rendering = !0;
+mxGraphView.prototype.graph = null;
+mxGraphView.prototype.currentRoot = null;
+mxGraphView.prototype.graphBounds = null;
+mxGraphView.prototype.scale = 1;
+mxGraphView.prototype.translate = null;
+mxGraphView.prototype.updateStyle = !1;
+mxGraphView.prototype.getGraphBounds = function() {
+  return this.graphBounds
+};
+mxGraphView.prototype.setGraphBounds = function(a) {
+  this.graphBounds = a
+};
+mxGraphView.prototype.getBounds = function(a) {
+  var b = null;
+  if (null != a && 0 < a.length)
+    for (var c = this.graph.getModel(), d = 0; d < a.length; d++)
+      if (c.isVertex(a[d]) || c.isEdge(a[d])) {
+        var e = this.getState(a[d]);
+        null != e && (null == b ? b = new mxRectangle(e.x, e.y, e.width, e.height) : b.add(e))
+      }
+  return b
+};
+mxGraphView.prototype.setCurrentRoot = function(a) {
+  if (this.currentRoot != a) {
+    var b = new mxCurrentRootChange(this, a);
+    b.execute();
+    var c = new mxUndoableEdit(this, !1);
+    c.add(b);
+    this.fireEvent(new mxEventObject(mxEvent.UNDO, "edit", c));
+    this.graph.sizeDidChange()
+  }
+  return a
+};
+mxGraphView.prototype.scaleAndTranslate = function(a, b, c) {
+  var d = this.scale,
+    e = new mxPoint(this.translate.x, this.translate.y);
+  if (this.scale != a || this.translate.x != b || this.translate.y != c) this.scale = a, this.translate.x = b, this.translate.y = c, this.isEventsEnabled() && (this.revalidate(), this.graph.sizeDidChange());
+  this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE, "scale", a, "previousScale", d, "translate", this.translate, "previousTranslate", e))
+};
+mxGraphView.prototype.getScale = function() {
+  return this.scale
+};
+mxGraphView.prototype.setScale = function(a) {
+  var b = this.scale;
+  this.scale != a && (this.scale = a, this.isEventsEnabled() && (this.revalidate(), this.graph.sizeDidChange()));
+  this.fireEvent(new mxEventObject(mxEvent.SCALE, "scale", a, "previousScale", b))
+};
+mxGraphView.prototype.getTranslate = function() {
+  return this.translate
+};
+mxGraphView.prototype.setTranslate = function(a, b) {
+  var c = new mxPoint(this.translate.x, this.translate.y);
+  if (this.translate.x != a || this.translate.y != b) this.translate.x = a, this.translate.y = b, this.isEventsEnabled() && (this.revalidate(), this.graph.sizeDidChange());
+  this.fireEvent(new mxEventObject(mxEvent.TRANSLATE, "translate", this.translate, "previousTranslate", c))
+};
+mxGraphView.prototype.refresh = function() {
+  null != this.currentRoot && this.clear();
+  this.revalidate()
+};
+mxGraphView.prototype.revalidate = function() {
+  this.invalidate();
+  this.validate()
+};
+mxGraphView.prototype.clear = function(a, b, c) {
+  var d = this.graph.getModel();
+  a = a || d.getRoot();
+  b = null != b ? b : !1;
+  c = null != c ? c : !0;
+  this.removeState(a);
+  if (c && (b || a != this.currentRoot)) {
+    c = d.getChildCount(a);
+    for (var e = 0; e < c; e++) this.clear(d.getChildAt(a, e), b)
+  } else this.invalidate(a)
+};
+mxGraphView.prototype.invalidate = function(a, b, c, d) {
+  var e = this.graph.getModel();
+  a = a || e.getRoot();
+  b = null != b ? b : !0;
+  c = null != c ? c : !0;
+  d = null != d ? d : !1;
+  var f = this.getState(a);
+  null != f && (f.invalid = !0, d && (f.orderChanged = !0));
+  if (b)
+    for (var g = e.getChildCount(a), f = 0; f < g; f++) {
+      var h = e.getChildAt(a, f);
+      this.invalidate(h, b, c, d)
+    }
+  if (c) {
+    d = e.getEdgeCount(a);
+    for (f = 0; f < d; f++) this.invalidate(e.getEdgeAt(a, f), b, c)
+  }
+};
+mxGraphView.prototype.validate = function(a) {
+  var b = mxLog.enter("mxGraphView.validate");
+  window.status = mxResources.get(this.updatingDocumentResource) || this.updatingDocumentResource;
+  var c = null;
+  if (this.optimizeVmlReflows && null != this.canvas && null == this.textDiv && (8 == document.documentMode || mxClient.IS_QUIRKS)) {
+    c = this.canvas.style.display;
+    this.canvas.style.display = "none";
+    var d = document.createElement("div");
+    d.style.position = "absolute";
+    d.style.whiteSpace = "nowrap";
+    d.style.visibility = "hidden";
+    d.style.display = mxClient.IS_QUIRKS ?
+      "inline" : "inline-block";
+    d.style.zoom = "1";
+    document.body.appendChild(d);
+    this.textDiv = d
+  }
+  a = a || (null != this.currentRoot ? this.currentRoot : this.graph.getModel().getRoot());
+  this.validateBounds(null, a);
+  a = this.validatePoints(null, a);
+  null == a && (a = new mxRectangle);
+  this.setGraphBounds(a);
+  this.validateBackground();
+  null != c && (this.canvas.style.display = c, document.body.removeChild(this.textDiv), this.textDiv = null);
+  window.status = mxResources.get(this.doneResource) || this.doneResource;
+  mxLog.leave("mxGraphView.validate", b)
+};
+mxGraphView.prototype.createBackgroundPageShape = function(a) {
+  return new mxRectangleShape(a, "white", "black")
+};
+mxGraphView.prototype.validateBackground = function() {
+  var a = this.graph.getBackgroundImage();
+  if (null != a) {
+    if (null == this.backgroundImage || this.backgroundImage.image != a.src) {
+      null != this.backgroundImage && this.backgroundImage.destroy();
+      var b = new mxRectangle(0, 0, 1, 1);
+      this.backgroundImage = new mxImageShape(b, a.src);
+      this.backgroundImage.dialect = this.graph.dialect;
+      this.backgroundImage.init(this.backgroundPane);
+      this.backgroundImage.redraw()
+    }
+    this.redrawBackgroundImage(this.backgroundImage, a)
+  } else null != this.backgroundImage &&
+    (this.backgroundImage.destroy(), this.backgroundImage = null);
+  this.graph.pageVisible ? (b = this.getBackgroundPageBounds(), null == this.backgroundPageShape ? (this.backgroundPageShape = this.createBackgroundPageShape(b), this.backgroundPageShape.scale =
+    this.scale, this.backgroundPageShape.isShadow = !0, this.backgroundPageShape.dialect = this.graph.dialect, this.backgroundPageShape.init(this.backgroundPane), this.backgroundPageShape.redraw(),
+    mxEvent.addListener(this.backgroundPageShape.node, "dblclick", mxUtils.bind(this,
+      function(a) {
+        this.graph.dblClick(a)
+      })), mxEvent.addGestureListeners(this.backgroundPageShape.node, mxUtils.bind(this, function(a) {
+      this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(a))
+    }), mxUtils.bind(this, function(a) {
+      null != this.graph.tooltipHandler && this.graph.tooltipHandler.isHideOnHover() && this.graph.tooltipHandler.hide();
+      this.graph.isMouseDown && !mxEvent.isConsumed(a) && this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(a))
+    }), mxUtils.bind(this, function(a) {
+      this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+        new mxMouseEvent(a))
+    }))) : (this.backgroundPageShape.scale = this.scale, this.backgroundPageShape.bounds = b, this.backgroundPageShape.redraw())) : null != this.backgroundPageShape && (this.backgroundPageShape.destroy(),
+    this.backgroundPageShape = null)
+};
+mxGraphView.prototype.getBackgroundPageBounds = function() {
+  var a = this.graph.pageFormat,
+    b = this.scale * this.graph.pageScale;
+  return new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y, a.width * b, a.height * b)
+};
+mxGraphView.prototype.redrawBackgroundImage = function(a, b) {
+  a.scale = this.scale;
+  a.bounds.x = this.scale * this.translate.x;
+  a.bounds.y = this.scale * this.translate.y;
+  a.bounds.width = this.scale * b.width;
+  a.bounds.height = this.scale * b.height;
+  a.redraw()
+};
+mxGraphView.prototype.validateBounds = function(a, b) {
+  var c = this.graph.getModel(),
+    d = this.getState(b, !0);
+  if (null != d && d.invalid) {
+    if (this.graph.isCellVisible(b)) {
+      if (b != this.currentRoot && null != a) {
+        d.absoluteOffset.x = 0;
+        d.absoluteOffset.y = 0;
+        d.origin.x = a.origin.x;
+        d.origin.y = a.origin.y;
+        var e = this.graph.getCellGeometry(b);
+        if (null != e) {
+          if (!c.isEdge(b)) {
+            var f = e.offset || this.EMPTY_POINT;
+            e.relative ? (d.origin.x += e.x * a.width / this.scale + f.x, d.origin.y += e.y * a.height / this.scale + f.y) : (d.absoluteOffset.x = this.scale * f.x, d.absoluteOffset.y =
+              this.scale * f.y, d.origin.x += e.x, d.origin.y += e.y)
+          }
+          d.x = this.scale * (this.translate.x + d.origin.x);
+          d.y = this.scale * (this.translate.y + d.origin.y);
+          d.width = this.scale * e.width;
+          d.height = this.scale * e.height;
+          if (c.isVertex(b)) {
+            if (e.relative && (f = mxUtils.toRadians(a.style[mxConstants.STYLE_ROTATION] || "0"), 0 != f)) {
+              var e = Math.cos(f),
+                f = Math.sin(f),
+                g = new mxPoint(d.getCenterX(), d.getCenterY()),
+                h = new mxPoint(a.getCenterX(), a.getCenterY()),
+                e = mxUtils.getRotatedPoint(g, e, f, h);
+              d.x = e.x - d.width / 2;
+              d.y = e.y - d.height / 2
+            }
+            this.updateVertexLabelOffset(d)
+          }
+        }
+      }
+    } else this.removeState(b);
+    f = this.graph.getChildOffsetForCell(b);
+    null != f && (d.origin.x += f.x, d.origin.y += f.y)
+  }
+  if (null != d && (!this.graph.isCellCollapsed(b) || b == this.currentRoot)) {
+    e = c.getChildCount(b);
+    for (f = 0; f < e; f++) g = c.getChildAt(b, f), this.validateBounds(d, g)
+  }
+};
+mxGraphView.prototype.updateVertexLabelOffset = function(a) {
+  var b = mxUtils.getValue(a.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+  b == mxConstants.ALIGN_LEFT ? a.absoluteOffset.x -= a.width : b == mxConstants.ALIGN_RIGHT && (a.absoluteOffset.x += a.width);
+  b = mxUtils.getValue(a.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+  b == mxConstants.ALIGN_TOP ? a.absoluteOffset.y -= a.height : b == mxConstants.ALIGN_BOTTOM && (a.absoluteOffset.y += a.height)
+};
+mxGraphView.prototype.validatePoints = function(a, b) {
+  var c = this.graph.getModel(),
+    d = this.getState(b),
+    e = null;
+  if (null != d) {
+    if (d.invalid) {
+      var f = this.graph.getCellGeometry(b);
+      if (null != f && c.isEdge(b)) {
+        var g = this.getState(this.getVisibleTerminal(b, !0));
+        d.setVisibleTerminalState(g, !0);
+        if (null != g && c.isEdge(g.cell) && !c.isAncestor(g.cell, b)) {
+          var h = this.getState(c.getParent(g.cell));
+          this.validatePoints(h, g.cell)
+        }
+        var k = this.getState(this.getVisibleTerminal(b, !1));
+        d.setVisibleTerminalState(k, !1);
+        null != k && (c.isEdge(k.cell) &&
+          !c.isAncestor(k.cell, b)) && (h = this.getState(c.getParent(k.cell)), this.validatePoints(h, k.cell));
+        this.updateFixedTerminalPoints(d, g, k);
+        this.updatePoints(d, f.points, g, k);
+        this.updateFloatingTerminalPoints(d, g, k);
+        this.updateEdgeBounds(d);
+        this.updateEdgeLabelOffset(d)
+      } else null != f && (f.relative && null != a && c.isEdge(a.cell)) && (f = this.getPoint(a, f), null != f && (d.x = f.x, d.y = f.y, f.x = f.x / this.scale - this.translate.x, f.y = f.y / this.scale -
+        this.translate.y, d.origin = f, this.childMoved(a, d)));
+      d.invalid = !1;
+      b != this.currentRoot &&
+        this.graph.cellRenderer.redraw(d, !1, this.isRendering())
+    }
+    if (c.isEdge(b) || c.isVertex(b)) null != d.shape && null != d.shape.boundingBox && (e = d.shape.boundingBox.clone()), null != d.text && !this.graph.isLabelClipped(d.cell) && null != d.text.boundingBox &&
+      (null != e ? e.add(d.text.boundingBox) : e = d.text.boundingBox.clone())
+  }
+  if (null != d && (!this.graph.isCellCollapsed(b) || b == this.currentRoot)) {
+    f = c.getChildCount(b);
+    for (g = 0; g < f; g++) h = c.getChildAt(b, g), h = this.validatePoints(d, h), null != h && (null == e ? e = h : e.add(h))
+  }
+  return e
+};
+mxGraphView.prototype.childMoved = function(a, b) {
+  var c = b.cell;
+  if (!this.graph.isCellCollapsed(c) || c == this.currentRoot)
+    for (var d = this.graph.getModel(), e = d.getChildCount(c), f = 0; f < e; f++) this.validateBounds(b, d.getChildAt(c, f))
+};
+mxGraphView.prototype.updateFixedTerminalPoints = function(a, b, c) {
+  this.updateFixedTerminalPoint(a, b, !0, this.graph.getConnectionConstraint(a, b, !0));
+  this.updateFixedTerminalPoint(a, c, !1, this.graph.getConnectionConstraint(a, c, !1))
+};
+mxGraphView.prototype.updateFixedTerminalPoint = function(a, b, c, d) {
+  var e = null;
+  null != d && (e = this.graph.getConnectionPoint(b, d));
+  if (null == e && null == b) {
+    b = this.scale;
+    d = this.translate;
+    var f = a.origin,
+      e = this.graph.getCellGeometry(a.cell).getTerminalPoint(c);
+    null != e && (e = new mxPoint(b * (d.x + e.x + f.x), b * (d.y + e.y + f.y)))
+  }
+  a.setAbsoluteTerminalPoint(e, c)
+};
+mxGraphView.prototype.updatePoints = function(a, b, c, d) {
+  if (null != a) {
+    var e = [];
+    e.push(a.absolutePoints[0]);
+    var f = this.getEdgeStyle(a, b, c, d);
+    if (null != f) c = this.getTerminalPort(a, c, !0), d = this.getTerminalPort(a, d, !1), f(a, c, d, b, e);
+    else if (null != b)
+      for (f = 0; f < b.length; f++) null != b[f] && (d = mxUtils.clone(b[f]), e.push(this.transformControlPoint(a, d)));
+    b = a.absolutePoints;
+    e.push(b[b.length - 1]);
+    a.absolutePoints = e
+  }
+};
+mxGraphView.prototype.transformControlPoint = function(a, b) {
+  var c = a.origin;
+  return new mxPoint(this.scale * (b.x + this.translate.x + c.x), this.scale * (b.y + this.translate.y + c.y))
+};
+mxGraphView.prototype.getEdgeStyle = function(a, b, c, d) {
+  a = null != c && c == d ? mxUtils.getValue(a.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle) : !mxUtils.getValue(a.style, mxConstants.STYLE_NOEDGESTYLE, !1) ? a.style[mxConstants.STYLE_EDGE] :
+    null;
+  "string" == typeof a && (b = mxStyleRegistry.getValue(a), null == b && this.isAllowEval() && (b = mxUtils.eval(a)), a = b);
+  return "function" == typeof a ? a : null
+};
+mxGraphView.prototype.updateFloatingTerminalPoints = function(a, b, c) {
+  var d = a.absolutePoints,
+    e = d[0];
+  null == d[d.length - 1] && null != c && this.updateFloatingTerminalPoint(a, c, b, !1);
+  null == e && null != b && this.updateFloatingTerminalPoint(a, b, c, !0)
+};
+mxGraphView.prototype.updateFloatingTerminalPoint = function(a, b, c, d) {
+  b = this.getTerminalPort(a, b, d);
+  var e = this.getNextPoint(a, c, d),
+    f = this.graph.isOrthogonal(a);
+  c = mxUtils.toRadians(Number(b.style[mxConstants.STYLE_ROTATION] || "0"));
+  var g = new mxPoint(b.getCenterX(), b.getCenterY());
+  if (0 != c) var h = Math.cos(-c),
+    k = Math.sin(-c),
+    e = mxUtils.getRotatedPoint(e, h, k, g);
+  h = parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+  h += parseFloat(a.style[d ? mxConstants.STYLE_SOURCE_PERIMETER_SPACING : mxConstants.STYLE_TARGET_PERIMETER_SPACING] ||
+    0);
+  b = this.getPerimeterPoint(b, e, 0 == c && f, h);
+  0 != c && (h = Math.cos(c), k = Math.sin(c), b = mxUtils.getRotatedPoint(b, h, k, g));
+  a.setAbsoluteTerminalPoint(b, d)
+};
+mxGraphView.prototype.getTerminalPort = function(a, b, c) {
+  a = mxUtils.getValue(a.style, c ? mxConstants.STYLE_SOURCE_PORT : mxConstants.STYLE_TARGET_PORT);
+  null != a && (a = this.getState(this.graph.getModel().getCell(a)), null != a && (b = a));
+  return b
+};
+mxGraphView.prototype.getPerimeterPoint = function(a, b, c, d) {
+  var e = null;
+  if (null != a) {
+    var f = this.getPerimeterFunction(a);
+    if (null != f && null != b && (d = this.getPerimeterBounds(a, d), 0 < d.width || 0 < d.height)) e = f(d, a, b, c);
+    null == e && (e = this.getPoint(a))
+  }
+  return e
+};
+mxGraphView.prototype.getRoutingCenterX = function(a) {
+  var b = null != a.style ? parseFloat(a.style[mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
+  return a.getCenterX() + b * a.width
+};
+mxGraphView.prototype.getRoutingCenterY = function(a) {
+  var b = null != a.style ? parseFloat(a.style[mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
+  return a.getCenterY() + b * a.height
+};
+mxGraphView.prototype.getPerimeterBounds = function(a, b) {
+  b = null != b ? b : 0;
+  null != a && (b += parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING] || 0));
+  return a.getPerimeterBounds(b * this.scale)
+};
+mxGraphView.prototype.getPerimeterFunction = function(a) {
+  a = a.style[mxConstants.STYLE_PERIMETER];
+  if ("string" == typeof a) {
+    var b = mxStyleRegistry.getValue(a);
+    null == b && this.isAllowEval() && (b = mxUtils.eval(a));
+    a = b
+  }
+  return "function" == typeof a ? a : null
+};
+mxGraphView.prototype.getNextPoint = function(a, b, c) {
+  a = a.absolutePoints;
+  var d = null;
+  if (null != a && (c || 2 < a.length || null == b)) d = a.length, d = a[c ? Math.min(1, d - 1) : Math.max(0, d - 2)];
+  null == d && null != b && (d = new mxPoint(b.getCenterX(), b.getCenterY()));
+  return d
+};
+mxGraphView.prototype.getVisibleTerminal = function(a, b) {
+  for (var c = this.graph.getModel(), d = c.getTerminal(a, b), e = d; null != d && d != this.currentRoot;) {
+    if (!this.graph.isCellVisible(e) || this.graph.isCellCollapsed(d)) e = d;
+    d = c.getParent(d)
+  }
+  c.getParent(e) == c.getRoot() && (e = null);
+  return e
+};
+mxGraphView.prototype.updateEdgeBounds = function(a) {
+  var b = a.absolutePoints;
+  a.length = 0;
+  if (null != b && 0 < b.length) {
+    var c = b[0],
+      d = b[b.length - 1];
+    if (null == c || null == d) a.cell != this.currentRoot && this.clear(a.cell, !0);
+    else {
+      if (c.x != d.x || c.y != d.y) {
+        var e = d.x - c.x,
+          f = d.y - c.y;
+        a.terminalDistance = Math.sqrt(e * e + f * f)
+      } else a.terminalDistance = 0;
+      var d = 0,
+        g = [],
+        f = c;
+      if (null != f) {
+        for (var c = f.x, h = f.y, k = c, l = h, m = 1; m < b.length; m++) {
+          var n = b[m];
+          null != n && (e = f.x - n.x, f = f.y - n.y, e = Math.sqrt(e * e + f * f), g.push(e), d += e, f = n, c = Math.min(f.x, c), h = Math.min(f.y,
+            h), k = Math.max(f.x, k), l = Math.max(f.y, l))
+        }
+        a.length = d;
+        a.segments = g;
+        a.x = c;
+        a.y = h;
+        a.width = Math.max(1, k - c);
+        a.height = Math.max(1, l - h)
+      }
+    }
+  }
+};
+mxGraphView.prototype.getPoint = function(a, b) {
+  var c = a.getCenterX(),
+    d = a.getCenterY();
+  if (null != a.segments && (null == b || b.relative)) {
+    for (var e = a.absolutePoints.length, f = ((null != b ? b.x / 2 : 0) + 0.5) * a.length, g = a.segments[0], h = 0, k = 1; f > h + g && k < e - 1;) h += g, g = a.segments[k++];
+    e = 0 == g ? 0 : (f - h) / g;
+    f = a.absolutePoints[k - 1];
+    k = a.absolutePoints[k];
+    if (null != f && null != k) {
+      h = c = d = 0;
+      if (null != b) {
+        var d = b.y,
+          l = b.offset;
+        null != l && (c = l.x, h = l.y)
+      }
+      l = k.x - f.x;
+      k = k.y - f.y;
+      c = f.x + l * e + ((0 == g ? 0 : k / g) * d + c) * this.scale;
+      d = f.y + k * e - ((0 == g ? 0 : l / g) * d - h) * this.scale
+    }
+  } else null !=
+    b && (l = b.offset, null != l && (c += l.x, d += l.y));
+  return new mxPoint(c, d)
+};
+mxGraphView.prototype.getRelativePoint = function(a, b, c) {
+  var d = this.graph.getModel().getGeometry(a.cell);
+  if (null != d) {
+    var e = a.absolutePoints.length;
+    if (d.relative && 1 < e) {
+      for (var d = a.length, f = a.segments, g = a.absolutePoints[0], h = a.absolutePoints[1], k = mxUtils.ptSegDistSq(g.x, g.y, h.x, h.y, b, c), l = 0, m = 0, n = 0, p = 2; p < e; p++) m += f[p -
+        2], h = a.absolutePoints[p], g = mxUtils.ptSegDistSq(g.x, g.y, h.x, h.y, b, c), g <= k && (k = g, l = p - 1, n = m), g = h;
+      e = f[l];
+      g = a.absolutePoints[l];
+      h = a.absolutePoints[l + 1];
+      k = h.x;
+      f = h.y;
+      a = g.x - k;
+      l = g.y - f;
+      k = b - k;
+      f = c - f;
+      k = a - k;
+      f = l - f;
+      f = k * a + f * l;
+      a = Math.sqrt(0 >= f ? 0 : f * f / (a * a + l * l));
+      a > e && (a = e);
+      e = Math.sqrt(mxUtils.ptSegDistSq(g.x, g.y, h.x, h.y, b, c)); - 1 == mxUtils.relativeCcw(g.x, g.y, h.x, h.y, b, c) && (e = -e);
+      return new mxPoint(-2 * ((d / 2 - n - a) / d), e / this.scale)
+    }
+  }
+  return new mxPoint
+};
+mxGraphView.prototype.updateEdgeLabelOffset = function(a) {
+  var b = a.absolutePoints;
+  a.absoluteOffset.x = a.getCenterX();
+  a.absoluteOffset.y = a.getCenterY();
+  if (null != b && 0 < b.length && null != a.segments) {
+    var c = this.graph.getCellGeometry(a.cell);
+    if (c.relative) {
+      var d = this.getPoint(a, c);
+      null != d && (a.absoluteOffset = d)
+    } else {
+      var d = b[0],
+        e = b[b.length - 1];
+      if (null != d && null != e) {
+        var b = e.x - d.x,
+          f = e.y - d.y,
+          g = e = 0,
+          c = c.offset;
+        null != c && (e = c.x, g = c.y);
+        c = d.y + f / 2 + g * this.scale;
+        a.absoluteOffset.x = d.x + b / 2 + e * this.scale;
+        a.absoluteOffset.y = c
+      }
+    }
+  }
+};
+mxGraphView.prototype.getState = function(a, b) {
+  b = b || !1;
+  var c = null;
+  null != a && (c = this.states.get(a), this.graph.isCellVisible(a) && (null == c && b && this.graph.isCellVisible(a) ? (c = this.createState(a), this.states.put(a, c)) : b && (null != c && this.updateStyle) &&
+    (c.style = this.graph.getCellStyle(a))));
+  return c
+};
+mxGraphView.prototype.isRendering = function() {
+  return this.rendering
+};
+mxGraphView.prototype.setRendering = function(a) {
+  this.rendering = a
+};
+mxGraphView.prototype.isAllowEval = function() {
+  return this.allowEval
+};
+mxGraphView.prototype.setAllowEval = function(a) {
+  this.allowEval = a
+};
+mxGraphView.prototype.getStates = function() {
+  return this.states
+};
+mxGraphView.prototype.setStates = function(a) {
+  this.states = a
+};
+mxGraphView.prototype.getCellStates = function(a) {
+  if (null == a) return this.states;
+  for (var b = [], c = 0; c < a.length; c++) {
+    var d = this.getState(a[c]);
+    null != d && b.push(d)
+  }
+  return b
+};
+mxGraphView.prototype.removeState = function(a) {
+  var b = null;
+  null != a && (b = this.states.remove(a), null != b && (this.graph.cellRenderer.destroy(b), b.destroy()));
+  return b
+};
+mxGraphView.prototype.createState = function(a) {
+  var b = this.graph.getCellStyle(a);
+  a = new mxCellState(this, a, b);
+  this.graph.cellRenderer.initialize(a, this.isRendering());
+  return a
+};
+mxGraphView.prototype.getCanvas = function() {
+  return this.canvas
+};
+mxGraphView.prototype.getBackgroundPane = function() {
+  return this.backgroundPane
+};
+mxGraphView.prototype.getDrawPane = function() {
+  return this.drawPane
+};
+mxGraphView.prototype.getOverlayPane = function() {
+  return this.overlayPane
+};
+mxGraphView.prototype.isContainerEvent = function(a) {
+  a = mxEvent.getSource(a);
+  return a == this.graph.container || a.parentNode == this.backgroundPane || null != a.parentNode && a.parentNode.parentNode == this.backgroundPane || a == this.canvas.parentNode || a == this.canvas ||
+    a == this.backgroundPane || a == this.drawPane || a == this.overlayPane
+};
+mxGraphView.prototype.isScrollEvent = function(a) {
+  var b = mxUtils.getOffset(this.graph.container);
+  a = new mxPoint(a.clientX - b.x, a.clientY - b.y);
+  var b = this.graph.container.offsetWidth,
+    c = this.graph.container.clientWidth;
+  if (b > c && a.x > c + 2 && a.x <= b) return !0;
+  b = this.graph.container.offsetHeight;
+  c = this.graph.container.clientHeight;
+  return b > c && a.y > c + 2 && a.y <= b ? !0 : !1
+};
+mxGraphView.prototype.init = function() {
+  this.installListeners();
+  var a = this.graph;
+  a.dialect == mxConstants.DIALECT_SVG ? this.createSvg() : a.dialect == mxConstants.DIALECT_VML ? this.createVml() : this.createHtml()
+};
+mxGraphView.prototype.installListeners = function() {
+  var a = this.graph,
+    b = a.container;
+  if (null != b) {
+    mxEvent.addGestureListeners(b, mxUtils.bind(this, function(b) {
+      mxClient.IS_TOUCH && a.isEditing() && a.stopEditing(!a.isInvokesStopCellEditing());
+      this.isContainerEvent(b) && (!mxClient.IS_IE && !mxClient.IS_GC && !mxClient.IS_OP && !mxClient.IS_SF || !this.isScrollEvent(b)) && a.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(
+        b))
+    }), mxUtils.bind(this, function(b) {
+      this.isContainerEvent(b) && a.fireMouseEvent(mxEvent.MOUSE_MOVE,
+        new mxMouseEvent(b))
+    }), mxUtils.bind(this, function(b) {
+      this.isContainerEvent(b) && a.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(b))
+    }));
+    mxEvent.addListener(b, "dblclick", mxUtils.bind(this, function(b) {
+      a.dblClick(b)
+    }));
+    var c = function(c) {
+      var e = null;
+      mxClient.IS_TOUCH && (e = mxEvent.getClientX(c), c = mxEvent.getClientY(c), c = mxUtils.convertPoint(b, e, c), e = a.view.getState(a.getCellAt(c.x, c.y)));
+      return e
+    };
+    a.addMouseListener({
+      mouseDown: function(b, c) {
+        a.panningHandler.hideMenu()
+      },
+      mouseMove: function() {},
+      mouseUp: function() {}
+    });
+    this.moveHandler = mxUtils.bind(this, function(b) {
+      null != a.tooltipHandler && a.tooltipHandler.isHideOnHover() && a.tooltipHandler.hide();
+      this.captureDocumentGesture && (a.isMouseDown && !mxEvent.isConsumed(b)) && a.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(b, c(b)))
+    });
+    this.endHandler = mxUtils.bind(this, function(b) {
+      this.captureDocumentGesture && a.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(b))
+    });
+    mxEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler)
+  }
+};
+mxGraphView.prototype.createHtml = function() {
+  var a = this.graph.container;
+  null != a && (this.canvas = this.createHtmlPane("100%", "100%"), this.backgroundPane = this.createHtmlPane("1px", "1px"), this.drawPane = this.createHtmlPane("1px", "1px"), this.overlayPane =
+    this.createHtmlPane("1px", "1px"), this.canvas.appendChild(this.backgroundPane), this.canvas.appendChild(this.drawPane), this.canvas.appendChild(this.overlayPane), a.appendChild(this.canvas),
+    mxClient.IS_QUIRKS && (a = mxUtils.bind(this, function(a) {
+      a = this.getGraphBounds();
+      this.updateHtmlCanvasSize(a.x + a.width + this.graph.border, a.y + a.height + this.graph.border)
+    }), mxEvent.addListener(window, "resize", a)))
+};
+mxGraphView.prototype.updateHtmlCanvasSize = function(a, b) {
+  if (null != this.graph.container) {
+    var c = this.graph.container.offsetHeight;
+    this.canvas.style.width = this.graph.container.offsetWidth < a ? a + "px" : "100%";
+    this.canvas.style.height = c < b ? b + "px" : "100%"
+  }
+};
+mxGraphView.prototype.createHtmlPane = function(a, b) {
+  var c = document.createElement("DIV");
+  null != a && null != b ? (c.style.position = "absolute", c.style.left = "0px", c.style.top = "0px", c.style.width = a, c.style.height = b) : c.style.position = "relative";
+  return c
+};
+mxGraphView.prototype.createVml = function() {
+  var a = this.graph.container;
+  if (null != a) {
+    var b = a.offsetWidth,
+      c = a.offsetHeight;
+    this.canvas = this.createVmlPane(b, c);
+    this.backgroundPane = this.createVmlPane(b, c);
+    this.drawPane = this.createVmlPane(b, c);
+    this.overlayPane = this.createVmlPane(b, c);
+    this.canvas.appendChild(this.backgroundPane);
+    this.canvas.appendChild(this.drawPane);
+    this.canvas.appendChild(this.overlayPane);
+    a.appendChild(this.canvas)
+  }
+};
+mxGraphView.prototype.createVmlPane = function(a, b) {
+  var c = document.createElement(mxClient.VML_PREFIX + ":group");
+  c.style.position = "absolute";
+  c.style.left = "0px";
+  c.style.top = "0px";
+  c.style.width = a + "px";
+  c.style.height = b + "px";
+  c.setAttribute("coordsize", a + "," + b);
+  c.setAttribute("coordorigin", "0,0");
+  return c
+};
+mxGraphView.prototype.createSvg = function() {
+  var a = this.graph.container;
+  this.canvas = document.createElementNS(mxConstants.NS_SVG, "g");
+  this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, "g");
+  this.canvas.appendChild(this.backgroundPane);
+  this.drawPane = document.createElementNS(mxConstants.NS_SVG, "g");
+  this.canvas.appendChild(this.drawPane);
+  this.overlayPane = document.createElementNS(mxConstants.NS_SVG, "g");
+  this.canvas.appendChild(this.overlayPane);
+  var b = document.createElementNS(mxConstants.NS_SVG,
+    "svg");
+  b.style.width = "100%";
+  b.style.height = "100%";
+  b.style.display = "block";
+  b.appendChild(this.canvas);
+  null != a && (a.appendChild(b), "static" == mxUtils.getCurrentStyle(a).position && (a.style.position = "relative"))
+};
+mxGraphView.prototype.destroy = function() {
+  var a = null != this.canvas ? this.canvas.ownerSVGElement : null;
+  null == a && (a = this.canvas);
+  null != a && null != a.parentNode && (this.clear(this.currentRoot, !0), mxEvent.removeGestureListeners(document, null, this.moveHandler, this.endHandler), mxEvent.release(this.graph.container), a
+    .parentNode.removeChild(a), this.overlayPane = this.drawPane = this.backgroundPane = this.canvas = this.endHandler = this.moveHandler = null)
+};
+
+function mxCurrentRootChange(a, b) {
+  this.view = a;
+  this.previous = this.root = b;
+  this.isUp = null == b;
+  if (!this.isUp)
+    for (var c = this.view.currentRoot, d = this.view.graph.getModel(); null != c;) {
+      if (c == b) {
+        this.isUp = !0;
+        break
+      }
+      c = d.getParent(c)
+    }
+}
+mxCurrentRootChange.prototype.execute = function() {
+  var a = this.view.currentRoot;
+  this.view.currentRoot = this.previous;
+  this.previous = a;
+  a = this.view.graph.getTranslateForRoot(this.view.currentRoot);
+  null != a && (this.view.translate = new mxPoint(-a.x, -a.y));
+  this.view.fireEvent(new mxEventObject(this.isUp ? mxEvent.UP : mxEvent.DOWN, "root", this.view.currentRoot, "previous", this.previous));
+  this.isUp ? (this.view.clear(this.view.currentRoot, !0), this.view.validate()) : this.view.refresh();
+  this.isUp = !this.isUp
+};
+
+function mxGraph(a, b, c, d) {
+  this.mouseListeners = null;
+  this.renderHint = c;
+  this.dialect = mxClient.IS_SVG ? mxConstants.DIALECT_SVG : c == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML ? mxConstants.DIALECT_VML : c == mxConstants.RENDERING_HINT_FASTEST ? mxConstants
+    .DIALECT_STRICTHTML : c == mxConstants.RENDERING_HINT_FASTER ? mxConstants.DIALECT_PREFERHTML : mxConstants.DIALECT_MIXEDHTML;
+  this.model = null != b ? b : new mxGraphModel;
+  this.multiplicities = [];
+  this.imageBundles = [];
+  this.cellRenderer = this.createCellRenderer();
+  this.setSelectionModel(this.createSelectionModel());
+  this.setStylesheet(null != d ? d : this.createStylesheet());
+  this.view = this.createGraphView();
+  this.graphModelChangeListener = mxUtils.bind(this, function(a, b) {
+    this.graphModelChanged(b.getProperty("edit").changes)
+  });
+  this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);
+  this.createHandlers();
+  null != a && this.init(a);
+  this.view.revalidate()
+}
+mxLoadResources && mxResources.add(mxClient.basePath + "/resources/graph");
+mxGraph.prototype = new mxEventSource;
+mxGraph.prototype.constructor = mxGraph;
+mxGraph.prototype.EMPTY_ARRAY = [];
+mxGraph.prototype.mouseListeners = null;
+mxGraph.prototype.isMouseDown = !1;
+mxGraph.prototype.model = null;
+mxGraph.prototype.view = null;
+mxGraph.prototype.stylesheet = null;
+mxGraph.prototype.selectionModel = null;
+mxGraph.prototype.cellEditor = null;
+mxGraph.prototype.cellRenderer = null;
+mxGraph.prototype.multiplicities = null;
+mxGraph.prototype.renderHint = null;
+mxGraph.prototype.dialect = null;
+mxGraph.prototype.gridSize = 10;
+mxGraph.prototype.gridEnabled = !0;
+mxGraph.prototype.portsEnabled = !0;
+mxGraph.prototype.doubleTapEnabled = !0;
+mxGraph.prototype.doubleTapTimeout = 700;
+mxGraph.prototype.doubleTapTolerance = 25;
+mxGraph.prototype.lastTouchY = 0;
+mxGraph.prototype.lastTouchY = 0;
+mxGraph.prototype.lastTouchTime = 0;
+mxGraph.prototype.gestureEnabled = !0;
+mxGraph.prototype.tolerance = 4;
+mxGraph.prototype.defaultOverlap = 0.5;
+mxGraph.prototype.defaultParent = null;
+mxGraph.prototype.alternateEdgeStyle = null;
+mxGraph.prototype.backgroundImage = null;
+mxGraph.prototype.pageVisible = !1;
+mxGraph.prototype.pageBreaksVisible = !1;
+mxGraph.prototype.pageBreakColor = "gray";
+mxGraph.prototype.pageBreakDashed = !0;
+mxGraph.prototype.minPageBreakDist = 20;
+mxGraph.prototype.preferPageSize = !1;
+mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+mxGraph.prototype.pageScale = 1.5;
+mxGraph.prototype.enabled = !0;
+mxGraph.prototype.escapeEnabled = !0;
+mxGraph.prototype.invokesStopCellEditing = !0;
+mxGraph.prototype.enterStopsCellEditing = !1;
+mxGraph.prototype.useScrollbarsForPanning = !0;
+mxGraph.prototype.exportEnabled = !0;
+mxGraph.prototype.importEnabled = !0;
+mxGraph.prototype.cellsLocked = !1;
+mxGraph.prototype.cellsCloneable = !0;
+mxGraph.prototype.foldingEnabled = !0;
+mxGraph.prototype.cellsEditable = !0;
+mxGraph.prototype.cellsDeletable = !0;
+mxGraph.prototype.cellsMovable = !0;
+mxGraph.prototype.edgeLabelsMovable = !0;
+mxGraph.prototype.vertexLabelsMovable = !1;
+mxGraph.prototype.dropEnabled = !1;
+mxGraph.prototype.splitEnabled = !0;
+mxGraph.prototype.cellsResizable = !0;
+mxGraph.prototype.cellsBendable = !0;
+mxGraph.prototype.cellsSelectable = !0;
+mxGraph.prototype.cellsDisconnectable = !0;
+mxGraph.prototype.autoSizeCells = !1;
+mxGraph.prototype.autoScroll = !0;
+mxGraph.prototype.timerAutoScroll = !1;
+mxGraph.prototype.allowAutoPanning = !1;
+mxGraph.prototype.ignoreScrollbars = !1;
+mxGraph.prototype.autoExtend = !0;
+mxGraph.prototype.maximumGraphBounds = null;
+mxGraph.prototype.minimumGraphSize = null;
+mxGraph.prototype.minimumContainerSize = null;
+mxGraph.prototype.maximumContainerSize = null;
+mxGraph.prototype.resizeContainer = !1;
+mxGraph.prototype.border = 0;
+mxGraph.prototype.ordered = !0;
+mxGraph.prototype.keepEdgesInForeground = !1;
+mxGraph.prototype.keepEdgesInBackground = !0;
+mxGraph.prototype.allowNegativeCoordinates = !0;
+mxGraph.prototype.constrainChildren = !0;
+mxGraph.prototype.extendParents = !0;
+mxGraph.prototype.extendParentsOnAdd = !0;
+mxGraph.prototype.collapseToPreferredSize = !0;
+mxGraph.prototype.zoomFactor = 1.2;
+mxGraph.prototype.keepSelectionVisibleOnZoom = !1;
+mxGraph.prototype.centerZoom = !0;
+mxGraph.prototype.resetViewOnRootChange = !0;
+mxGraph.prototype.resetEdgesOnResize = !1;
+mxGraph.prototype.resetEdgesOnMove = !1;
+mxGraph.prototype.resetEdgesOnConnect = !0;
+mxGraph.prototype.allowLoops = !1;
+mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;
+mxGraph.prototype.multigraph = !0;
+mxGraph.prototype.connectableEdges = !1;
+mxGraph.prototype.allowDanglingEdges = !0;
+mxGraph.prototype.cloneInvalidEdges = !1;
+mxGraph.prototype.disconnectOnMove = !0;
+mxGraph.prototype.labelsVisible = !0;
+mxGraph.prototype.htmlLabels = !1;
+mxGraph.prototype.swimlaneSelectionEnabled = !0;
+mxGraph.prototype.swimlaneNesting = !0;
+mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;
+mxGraph.prototype.imageBundles = null;
+mxGraph.prototype.minFitScale = 0.1;
+mxGraph.prototype.maxFitScale = 8;
+mxGraph.prototype.panDx = 0;
+mxGraph.prototype.panDy = 0;
+mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + "/collapsed.gif", 9, 9);
+mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + "/expanded.gif", 9, 9);
+// mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + "/warning" + (mxClient.IS_MAC ? ".png" : ".gif"), 16, 16);
+mxGraph.prototype.warningImage = new mxImage("/assets/plugins/mxgraph/images/warning.gif", 16, 16);
+
+
+mxGraph.prototype.alreadyConnectedResource = "none" != mxClient.language ? "alreadyConnected" : "";
+mxGraph.prototype.containsValidationErrorsResource = "none" != mxClient.language ? "containsValidationErrors" : "";
+mxGraph.prototype.collapseExpandResource = "none" != mxClient.language ? "collapse-expand" : "";
+mxGraph.prototype.init = function(a) {
+  this.container = a;
+  this.cellEditor = this.createCellEditor();
+  this.view.init();
+  this.sizeDidChange();
+  mxClient.IS_IE && (mxEvent.addListener(window, "unload", mxUtils.bind(this, function() {
+    this.destroy()
+  })), mxEvent.addListener(a, "selectstart", mxUtils.bind(this, function() {
+    return this.isEditing()
+  })));
+  8 == document.documentMode && a.insertAdjacentHTML("beforeend", '\x3cv:group style\x3d"DISPLAY: none;"\x3e\x3c/v:group\x3e')
+};
+mxGraph.prototype.createHandlers = function(a) {
+  this.tooltipHandler = new mxTooltipHandler(this);
+  this.tooltipHandler.setEnabled(!1);
+  this.panningHandler = new mxPanningHandler(this);
+  this.panningHandler.panningEnabled = !1;
+  this.selectionCellsHandler = new mxSelectionCellsHandler(this);
+  this.connectionHandler = new mxConnectionHandler(this);
+  this.connectionHandler.setEnabled(!1);
+  this.graphHandler = new mxGraphHandler(this)
+};
+mxGraph.prototype.createSelectionModel = function() {
+  return new mxGraphSelectionModel(this)
+};
+mxGraph.prototype.createStylesheet = function() {
+  return new mxStylesheet
+};
+mxGraph.prototype.createGraphView = function() {
+  return new mxGraphView(this)
+};
+mxGraph.prototype.createCellRenderer = function() {
+  return new mxCellRenderer
+};
+mxGraph.prototype.createCellEditor = function() {
+  return new mxCellEditor(this)
+};
+mxGraph.prototype.getModel = function() {
+  return this.model
+};
+mxGraph.prototype.getView = function() {
+  return this.view
+};
+mxGraph.prototype.getStylesheet = function() {
+  return this.stylesheet
+};
+mxGraph.prototype.setStylesheet = function(a) {
+  this.stylesheet = a
+};
+mxGraph.prototype.getSelectionModel = function() {
+  return this.selectionModel
+};
+mxGraph.prototype.setSelectionModel = function(a) {
+  this.selectionModel = a
+};
+mxGraph.prototype.getSelectionCellsForChanges = function(a) {
+  for (var b = [], c = 0; c < a.length; c++) {
+    var d = a[c];
+    if (d.constructor != mxRootChange) {
+      var e = null;
+      d instanceof mxChildChange && null == d.previous ? e = d.child : null != d.cell && d.cell instanceof mxCell && (e = d.cell);
+      null != e && 0 > mxUtils.indexOf(b, e) && b.push(e)
+    }
+  }
+  return this.getModel().getTopmostCells(b)
+};
+mxGraph.prototype.graphModelChanged = function(a) {
+  for (var b = 0; b < a.length; b++) this.processChange(a[b]);
+  this.removeSelectionCells(this.getRemovedCellsForChanges(a));
+  this.view.validate();
+  this.sizeDidChange()
+};
+mxGraph.prototype.getRemovedCellsForChanges = function(a) {
+  for (var b = [], c = 0; c < a.length; c++) {
+    var d = a[c];
+    if (d instanceof mxRootChange) break;
+    else d instanceof mxChildChange ? null != d.previous && null == d.parent && (b = b.concat(this.model.getDescendants(d.child))) : d instanceof mxVisibleChange && (b = b.concat(this.model.getDescendants(
+      d.cell)))
+  }
+  return b
+};
+mxGraph.prototype.processChange = function(a) {
+  if (a instanceof mxRootChange) this.clearSelection(), this.removeStateForCell(a.previous), this.resetViewOnRootChange && (this.view.scale = 1, this.view.translate.x = 0, this.view.translate.y = 0),
+    this.fireEvent(new mxEventObject(mxEvent.ROOT));
+  else if (a instanceof mxChildChange) {
+    var b = this.model.getParent(a.child);
+    null != b ? this.view.invalidate(a.child, !0, !1, null != a.previous) : (this.removeStateForCell(a.child), this.view.currentRoot == a.child && this.home());
+    b != a.previous &&
+      (null != b && this.view.invalidate(b, !1, !1), null != a.previous && this.view.invalidate(a.previous, !1, !1))
+  } else a instanceof mxTerminalChange || a instanceof mxGeometryChange ? this.view.invalidate(a.cell) : a instanceof mxValueChange ? this.view.invalidate(a.cell, !1, !1) : a instanceof mxStyleChange ?
+    (this.view.invalidate(a.cell, !0, !0, !1), this.view.removeState(a.cell)) : null != a.cell && a.cell instanceof mxCell && this.removeStateForCell(a.cell)
+};
+mxGraph.prototype.removeStateForCell = function(a) {
+  for (var b = this.model.getChildCount(a), c = 0; c < b; c++) this.removeStateForCell(this.model.getChildAt(a, c));
+  this.view.removeState(a)
+};
+mxGraph.prototype.addCellOverlay = function(a, b) {
+  null == a.overlays && (a.overlays = []);
+  a.overlays.push(b);
+  var c = this.view.getState(a);
+  null != c && this.cellRenderer.redraw(c);
+  this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY, "cell", a, "overlay", b));
+  return b
+};
+mxGraph.prototype.getCellOverlays = function(a) {
+  return a.overlays
+};
+mxGraph.prototype.removeCellOverlay = function(a, b) {
+  if (null == b) this.removeCellOverlays(a);
+  else {
+    var c = mxUtils.indexOf(a.overlays, b);
+    0 <= c ? (a.overlays.splice(c, 1), 0 == a.overlays.length && (a.overlays = null), c = this.view.getState(a), null != c && this.cellRenderer.redraw(c), this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+      "cell", a, "overlay", b))) : b = null
+  }
+  return b
+};
+mxGraph.prototype.removeCellOverlays = function(a) {
+  var b = a.overlays;
+  if (null != b) {
+    a.overlays = null;
+    var c = this.view.getState(a);
+    null != c && this.cellRenderer.redraw(c);
+    for (c = 0; c < b.length; c++) this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY, "cell", a, "overlay", b[c]))
+  }
+  return b
+};
+mxGraph.prototype.clearCellOverlays = function(a) {
+  a = null != a ? a : this.model.getRoot();
+  this.removeCellOverlays(a);
+  for (var b = this.model.getChildCount(a), c = 0; c < b; c++) {
+    var d = this.model.getChildAt(a, c);
+    this.clearCellOverlays(d)
+  }
+};
+mxGraph.prototype.setCellWarning = function(a, b, c, d) {
+  if (null != b && 0 < b.length) return c = null != c ? c : this.warningImage, b = new mxCellOverlay(c, "\x3cfont color\x3dred\x3e" + b + "\x3c/font\x3e"), d && b.addListener(mxEvent.CLICK, mxUtils
+    .bind(this, function(b, c) {
+      this.isEnabled() && this.setSelectionCell(a)
+    })), this.addCellOverlay(a, b);
+  this.removeCellOverlays(a);
+  return null
+};
+mxGraph.prototype.startEditing = function(a) {
+  this.startEditingAtCell(null, a)
+};
+mxGraph.prototype.startEditingAtCell = function(a, b) {
+  null == a && (a = this.getSelectionCell(), null != a && !this.isCellEditable(a) && (a = null));
+  null != a && (this.fireEvent(new mxEventObject(mxEvent.START_EDITING, "cell", a, "event", b)), this.cellEditor.startEditing(a, b))
+};
+mxGraph.prototype.getEditingValue = function(a, b) {
+  return this.convertValueToString(a)
+};
+mxGraph.prototype.stopEditing = function(a) {
+  this.cellEditor.stopEditing(a)
+};
+mxGraph.prototype.labelChanged = function(a, b, c) {
+  this.model.beginUpdate();
+  try {
+    this.cellLabelChanged(a, b, this.isAutoSizeCell(a)), this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED, "cell", a, "value", b, "event", c))
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.cellLabelChanged = function(a, b, c) {
+  this.model.beginUpdate();
+  try {
+    this.model.setValue(a, b), c && this.cellSizeUpdated(a, !1)
+  } finally {
+    this.model.endUpdate()
+  }
+};
+mxGraph.prototype.escape = function(a) {
+  this.stopEditing(!0);
+  this.connectionHandler.reset();
+  this.graphHandler.reset();
+  a = this.getSelectionCells();
+  for (var b = 0; b < a.length; b++) {
+    var c = this.view.getState(a[b]);
+    null != c && null != c.handler && c.handler.reset()
+  }
+};
+mxGraph.prototype.click = function(a) {
+  var b = a.getEvent(),
+    c = a.getCell(),
+    d = new mxEventObject(mxEvent.CLICK, "event", b, "cell", c);
+  a.isConsumed() && d.consume();
+  this.fireEvent(d);
+  this.isEnabled() && (!mxEvent.isConsumed(b) && !d.isConsumed()) && (null != c ? this.selectCellForEvent(c, b) : (c = null, this.isSwimlaneSelectionEnabled() && (c = this.getSwimlaneAt(a.getGraphX(),
+    a.getGraphY())), null != c ? this.selectCellForEvent(c, b) : this.isToggleEvent(b) || this.clearSelection()))
+};
+mxGraph.prototype.dblClick = function(a, b) {
+  var c = new mxEventObject(mxEvent.DOUBLE_CLICK, "event", a, "cell", b);
+  this.fireEvent(c);
+  this.isEnabled() && (!mxEvent.isConsumed(a) && !c.isConsumed() && null != b && this.isCellEditable(b)) && this.startEditingAtCell(b, a)
+};
+mxGraph.prototype.scrollPointToVisible = function(a, b, c, d) {
+  if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container))) {
+    var e = this.container;
+    d = null != d ? d : 20;
+    if (a >= e.scrollLeft && b >= e.scrollTop && a <= e.scrollLeft + e.clientWidth && b <= e.scrollTop + e.clientHeight) {
+      var f = e.scrollLeft + e.clientWidth - a;
+      if (f < d) {
+        if (a = e.scrollLeft, e.scrollLeft += d - f, c && a == e.scrollLeft) {
+          if (this.dialect == mxConstants.DIALECT_SVG) {
+            a = this.view.getDrawPane().ownerSVGElement;
+            var g = this.container.scrollWidth + d - f
+          } else g =
+            Math.max(e.clientWidth, e.scrollWidth) + d - f, a = this.view.getCanvas();
+          a.style.width = g + "px";
+          e.scrollLeft += d - f
+        }
+      } else f = a - e.scrollLeft, f < d && (e.scrollLeft -= d - f);
+      f = e.scrollTop + e.clientHeight - b;
+      f < d ? (a = e.scrollTop, e.scrollTop += d - f, a == e.scrollTop && c && (this.dialect == mxConstants.DIALECT_SVG ? (a = this.view.getDrawPane().ownerSVGElement, b = this.container.scrollHeight +
+        d - f) : (b = Math.max(e.clientHeight, e.scrollHeight) + d - f, a = this.view.getCanvas()), a.style.height = b + "px", e.scrollTop += d - f)) : (f = b - e.scrollTop, f < d && (e.scrollTop -=
+        d - f))
+    }
+  } else this.allowAutoPanning && !this.panningHandler.active && (null == this.panningManager && (this.panningManager = this.createPanningManager()), this.panningManager.panTo(a + this.panDx, b +
+    this.panDy))
+};
+mxGraph.prototype.createPanningManager = function() {
+  return new mxPanningManager(this)
+};
+mxGraph.prototype.getBorderSizes = function() {
+  function a(a) {
+    var b = 0,
+      b = "thin" == a ? 2 : "medium" == a ? 4 : "thick" == a ? 6 : parseInt(a);
+    isNaN(b) && (b = 0);
+    return b
+  }
+  var b = mxUtils.getCurrentStyle(this.container),
+    c = new mxRectangle;
+  c.x = a(b.borderLeftWidth) + parseInt(b.paddingLeft || 0);
+  c.y = a(b.borderTopWidth) + parseInt(b.paddingTop || 0);
+  c.width = a(b.borderRightWidth) + parseInt(b.paddingRight || 0);
+  c.height = a(b.borderBottomWidth) + parseInt(b.paddingBottom || 0);
+  return c
+};
+mxGraph.prototype.getPreferredPageSize = function(a, b, c) {
+  a = this.view.scale;
+  var d = this.view.translate,
+    e = this.pageFormat,
+    f = a * this.pageScale,
+    e = new mxRectangle(0, 0, e.width * f, e.height * f);
+  b = this.pageBreaksVisible ? Math.ceil(b / e.width) : 1;
+  c = this.pageBreaksVisible ? Math.ceil(c / e.height) : 1;
+  return new mxRectangle(0, 0, b * e.width + 2 + d.x / a, c * e.height + 2 + d.y / a)
+};
+mxGraph.prototype.sizeDidChange = function() {
+  var a = this.getGraphBounds();
+  if (null != this.container) {
+    var b = this.getBorder(),
+      c = Math.max(0, a.x + a.width + 1 + b),
+      b = Math.max(0, a.y + a.height + 1 + b);
+    null != this.minimumContainerSize && (c = Math.max(c, this.minimumContainerSize.width), b = Math.max(b, this.minimumContainerSize.height));
+    this.resizeContainer && this.doResizeContainer(c, b);
+    if (this.preferPageSize || !mxClient.IS_IE && this.pageVisible) {
+      var d = this.getPreferredPageSize(a, c, b);
+      null != d && (c = d.width, b = d.height)
+    }
+    null != this.minimumGraphSize &&
+      (c = Math.max(c, this.minimumGraphSize.width * this.view.scale), b = Math.max(b, this.minimumGraphSize.height * this.view.scale));
+    c = Math.ceil(c - 1);
+    b = Math.ceil(b - 1);
+    this.dialect == mxConstants.DIALECT_SVG ? (d = this.view.getDrawPane().ownerSVGElement, d.style.minWidth = Math.max(1, c) + "px", d.style.minHeight = Math.max(1, b) + "px", d.style.width =
+      "100%", d.style.height = "100%") : mxClient.IS_QUIRKS ? this.view.updateHtmlCanvasSize(Math.max(1, c), Math.max(1, b)) : (this.view.canvas.style.minWidth = Math.max(1, c) + "px", this.view.canvas
+      .style.minHeight =
+      Math.max(1, b) + "px");
+    this.updatePageBreaks(this.pageBreaksVisible, c - 1, b - 1)
+  }
+  this.fireEvent(new mxEventObject(mxEvent.SIZE, "bounds", a))
+};
+mxGraph.prototype.doResizeContainer = function(a, b) {
+  if (mxClient.IS_IE)
+    if (mxClient.IS_QUIRKS) {
+      var c = this.getBorderSizes();
+      a += Math.max(2, c.x + c.width + 1);
+      b += Math.max(2, c.y + c.height + 1)
+    } else 9 <= document.documentMode ? (a += 3, b += 5) : (a += 1, b += 1);
+  else b += 1;
+  null != this.maximumContainerSize && (a = Math.min(this.maximumContainerSize.width, a), b = Math.min(this.maximumContainerSize.height, b));
+  this.container.style.width = Math.ceil(a) + "px";
+  this.container.style.height = Math.ceil(b) + "px"
+};
+mxGraph.prototype.updatePageBreaks = function(a, b, c) {
+  var d = this.view.scale,
+    e = this.view.translate,
+    f = this.pageFormat,
+    g = d * this.pageScale,
+    e = new mxRectangle(d * e.x, d * e.y, f.width * g, f.height * g);
+  a = a && Math.min(e.width, e.height) > this.minPageBreakDist;
+  e.x = mxUtils.mod(e.x, e.width);
+  e.y = mxUtils.mod(e.y, e.height);
+  f = a ? Math.ceil((b - e.x) / e.width) : 0;
+  a = a ? Math.ceil((c - e.y) / e.height) : 0;
+  null == this.horizontalPageBreaks && 0 < f && (this.horizontalPageBreaks = []);
+  if (null != this.horizontalPageBreaks) {
+    for (g = 0; g <= f; g++) {
+      var h = [new mxPoint(e.x +
+        g * e.width, 1), new mxPoint(e.x + g * e.width, c)];
+      null != this.horizontalPageBreaks[g] ? (this.horizontalPageBreaks[g].scale = 1, this.horizontalPageBreaks[g].points = h, this.horizontalPageBreaks[g].redraw()) : (h = new mxPolyline(h, this.pageBreakColor,
+        this.scale), h.dialect = this.dialect, h.isDashed = this.pageBreakDashed, h.scale = d, h.init(this.view.backgroundPane), h.redraw(), this.horizontalPageBreaks[g] = h)
+    }
+    for (g = f; g < this.horizontalPageBreaks.length; g++) this.horizontalPageBreaks[g].destroy();
+    this.horizontalPageBreaks.splice(f,
+      this.horizontalPageBreaks.length - f)
+  }
+  null == this.verticalPageBreaks && 0 < a && (this.verticalPageBreaks = []);
+  if (null != this.verticalPageBreaks) {
+    for (g = 0; g <= a; g++) h = [new mxPoint(1, e.y + g * e.height), new mxPoint(b, e.y + g * e.height)], null != this.verticalPageBreaks[g] ? (this.verticalPageBreaks[g].scale = 1, this.verticalPageBreaks[
+      g].points = h, this.verticalPageBreaks[g].redraw()) : (h = new mxPolyline(h, this.pageBreakColor, d), h.dialect = this.dialect, h.isDashed = this.pageBreakDashed, h.scale = d, h.init(this.view
+        .backgroundPane), h.redraw(),
+      this.verticalPageBreaks[g] = h);
+    for (g = a; g < this.verticalPageBreaks.length; g++) this.verticalPageBreaks[g].destroy();
+    this.verticalPageBreaks.splice(a, this.verticalPageBreaks.length - a)
+  }
+};
+mxGraph.prototype.getCellStyle = function(a) {
+  var b = this.model.getStyle(a),
+    c = null,
+    c = this.model.isEdge(a) ? this.stylesheet.getDefaultEdgeStyle() : this.stylesheet.getDefaultVertexStyle();
+  null != b && (c = this.postProcessCellStyle(this.stylesheet.getCellStyle(b, c)));
+  null == c && (c = mxGraph.prototype.EMPTY_ARRAY);
+  return c
+};
+mxGraph.prototype.postProcessCellStyle = function(a) {
+  if (null != a) {
+    var b = a[mxConstants.STYLE_IMAGE],
+      c = this.getImageFromBundles(b);
+    null != c ? a[mxConstants.STYLE_IMAGE] = c : c = b;
+    null != c && "data:image/" == c.substring(0, 11) && (b = c.indexOf(","), 0 < b && (c = c.substring(0, b) + ";base64," + c.substring(b + 1)), a[mxConstants.STYLE_IMAGE] = c)
+  }
+  return a
+};
+mxGraph.prototype.setCellStyle = function(a, b) {
+  b = b || this.getSelectionCells();
+  if (null != b) {
+    this.model.beginUpdate();
+    try {
+      for (var c = 0; c < b.length; c++) this.model.setStyle(b[c], a)
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.toggleCellStyle = function(a, b, c) {
+  c = c || this.getSelectionCell();
+  this.toggleCellStyles(a, b, [c])
+};
+mxGraph.prototype.toggleCellStyles = function(a, b, c) {
+  b = null != b ? b : !1;
+  c = c || this.getSelectionCells();
+  if (null != c && 0 < c.length) {
+    var d = this.view.getState(c[0]),
+      d = null != d ? d.style : this.getCellStyle(c[0]);
+    null != d && (b = mxUtils.getValue(d, a, b) ? 0 : 1, this.setCellStyles(a, b, c))
+  }
+};
+mxGraph.prototype.setCellStyles = function(a, b, c) {
+  c = c || this.getSelectionCells();
+  mxUtils.setCellStyles(this.model, c, a, b)
+};
+mxGraph.prototype.toggleCellStyleFlags = function(a, b, c) {
+  this.setCellStyleFlags(a, b, null, c)
+};
+mxGraph.prototype.setCellStyleFlags = function(a, b, c, d) {
+  d = d || this.getSelectionCells();
+  if (null != d && 0 < d.length) {
+    if (null == c) {
+      var e = this.view.getState(d[0]),
+        e = null != e ? e.style : this.getCellStyle(d[0]);
+      null != e && (c = (parseInt(e[a] || 0) & b) != b)
+    }
+    mxUtils.setCellStyleFlags(this.model, d, a, b, c)
+  }
+};
+mxGraph.prototype.alignCells = function(a, b, c) {
+  null == b && (b = this.getSelectionCells());
+  if (null != b && 1 < b.length) {
+    if (null == c)
+      for (var d = 0; d < b.length; d++) {
+        var e = this.getCellGeometry(b[d]);
+        if (null != e && !this.model.isEdge(b[d]))
+          if (null == c)
+            if (a == mxConstants.ALIGN_CENTER) {
+              c = e.x + e.width / 2;
+              break
+            } else if (a == mxConstants.ALIGN_RIGHT) c = e.x + e.width;
+        else if (a == mxConstants.ALIGN_TOP) c = e.y;
+        else if (a == mxConstants.ALIGN_MIDDLE) {
+          c = e.y + e.height / 2;
+          break
+        } else c = a == mxConstants.ALIGN_BOTTOM ? e.y + e.height : e.x;
+        else c = a == mxConstants.ALIGN_RIGHT ?
+          Math.max(c, e.x + e.width) : a == mxConstants.ALIGN_TOP ? Math.min(c, e.y) : a == mxConstants.ALIGN_BOTTOM ? Math.max(c, e.y + e.height) : Math.min(c, e.x)
+      }
+    if (null != c) {
+      this.model.beginUpdate();
+      try {
+        for (d = 0; d < b.length; d++) e = this.getCellGeometry(b[d]), null != e && !this.model.isEdge(b[d]) && (e = e.clone(), a == mxConstants.ALIGN_CENTER ? e.x = c - e.width / 2 : a ==
+          mxConstants.ALIGN_RIGHT ? e.x = c - e.width : a == mxConstants.ALIGN_TOP ? e.y = c : a == mxConstants.ALIGN_MIDDLE ? e.y = c - e.height / 2 : a == mxConstants.ALIGN_BOTTOM ? e.y = c - e
+          .height : e.x = c, this.model.setGeometry(b[d],
+            e));
+        this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, "align", a, "cells", b))
+      } finally {
+        this.model.endUpdate()
+      }
+    }
+  }
+  return b
+};
+mxGraph.prototype.flipEdge = function(a) {
+  if (null != a && null != this.alternateEdgeStyle) {
+    this.model.beginUpdate();
+    try {
+      var b = this.model.getStyle(a);
+      null == b || 0 == b.length ? this.model.setStyle(a, this.alternateEdgeStyle) : this.model.setStyle(a, null);
+      this.resetEdge(a);
+      this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, "edge", a))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+  return a
+};
+mxGraph.prototype.addImageBundle = function(a) {
+  this.imageBundles.push(a)
+};
+mxGraph.prototype.removeImageBundle = function(a) {
+  for (var b = [], c = 0; c < this.imageBundles.length; c++) this.imageBundles[c] != a && b.push(this.imageBundles[c]);
+  this.imageBundles = b
+};
+mxGraph.prototype.getImageFromBundles = function(a) {
+  if (null != a)
+    for (var b = 0; b < this.imageBundles.length; b++) {
+      var c = this.imageBundles[b].getImage(a);
+      if (null != c) return c
+    }
+  return null
+};
+mxGraph.prototype.orderCells = function(a, b) {
+  null == b && (b = mxUtils.sortCells(this.getSelectionCells(), !0));
+  this.model.beginUpdate();
+  try {
+    this.cellsOrdered(b, a), this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS, "back", a, "cells", b))
+  } finally {
+    this.model.endUpdate()
+  }
+  return b
+};
+mxGraph.prototype.cellsOrdered = function(a, b) {
+  if (null != a) {
+    this.model.beginUpdate();
+    try {
+      for (var c = 0; c < a.length; c++) {
+        var d = this.model.getParent(a[c]);
+        b ? this.model.add(d, a[c], c) : this.model.add(d, a[c], this.model.getChildCount(d) - 1)
+      }
+      this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED, "back", b, "cells", a))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.groupCells = function(a, b, c) {
+  null == c && (c = mxUtils.sortCells(this.getSelectionCells(), !0));
+  c = this.getCellsForGroup(c);
+  null == a && (a = this.createGroupCell(c));
+  var d = this.getBoundsForGroup(a, c, b);
+  if (0 < c.length && null != d) {
+    var e = this.model.getParent(a);
+    null == e && (e = this.model.getParent(c[0]));
+    this.model.beginUpdate();
+    try {
+      null == this.getCellGeometry(a) && this.model.setGeometry(a, new mxGeometry);
+      var f = this.model.getChildCount(e);
+      this.cellsAdded([a], e, f, null, null, !1);
+      f = this.model.getChildCount(a);
+      this.cellsAdded(c, a, f, null, null, !1, !1);
+      this.cellsMoved(c, -d.x, -d.y, !1, !0);
+      this.cellsResized([a], [d]);
+      this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS, "group", a, "border", b, "cells", c))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+  return a
+};
+mxGraph.prototype.getCellsForGroup = function(a) {
+  var b = [];
+  if (null != a && 0 < a.length) {
+    var c = this.model.getParent(a[0]);
+    b.push(a[0]);
+    for (var d = 1; d < a.length; d++) this.model.getParent(a[d]) == c && b.push(a[d])
+  }
+  return b
+};
+mxGraph.prototype.getBoundsForGroup = function(a, b, c) {
+  b = this.getBoundingBoxFromGeometry(b);
+  null != b && (this.isSwimlane(a) && (a = this.getStartSize(a), b.x -= a.width, b.y -= a.height, b.width += a.width, b.height += a.height), b.x -= c, b.y -= c, b.width += 2 * c, b.height += 2 * c);
+  return b
+};
+mxGraph.prototype.createGroupCell = function(a) {
+  a = new mxCell("");
+  a.setVertex(!0);
+  a.setConnectable(!1);
+  return a
+};
+mxGraph.prototype.ungroupCells = function(a) {
+  var b = [];
+  if (null == a) {
+    a = this.getSelectionCells();
+    for (var c = [], d = 0; d < a.length; d++) 0 < this.model.getChildCount(a[d]) && c.push(a[d]);
+    a = c
+  }
+  if (null != a && 0 < a.length) {
+    this.model.beginUpdate();
+    try {
+      for (d = 0; d < a.length; d++) {
+        var e = this.model.getChildren(a[d]);
+        if (null != e && 0 < e.length) {
+          var e = e.slice(),
+            f = this.model.getParent(a[d]),
+            g = this.model.getChildCount(f);
+          this.cellsAdded(e, f, g, null, null, !0);
+          b = b.concat(e)
+        }
+      }
+      this.cellsRemoved(this.addAllEdges(a));
+      this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS,
+        "cells", a))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+  return b
+};
+mxGraph.prototype.removeCellsFromParent = function(a) {
+  null == a && (a = this.getSelectionCells());
+  this.model.beginUpdate();
+  try {
+    var b = this.getDefaultParent(),
+      c = this.model.getChildCount(b);
+    this.cellsAdded(a, b, c, null, null, !0);
+    this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT, "cells", a))
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.updateGroupBounds = function(a, b, c) {
+  null == a && (a = this.getSelectionCells());
+  b = null != b ? b : 0;
+  c = null != c ? c : !1;
+  this.model.beginUpdate();
+  try {
+    for (var d = 0; d < a.length; d++) {
+      var e = this.getCellGeometry(a[d]);
+      if (null != e) {
+        var f = this.getChildCells(a[d]);
+        if (null != f && 0 < f.length) {
+          var g = this.getBoundingBoxFromGeometry(f);
+          if (0 < g.width && 0 < g.height) {
+            var h = this.isSwimlane(a[d]) ? this.getStartSize(a[d]) : new mxRectangle,
+              e = e.clone();
+            c && (e.x += g.x - h.width - b, e.y += g.y - h.height - b);
+            e.width = g.width + h.width + 2 * b;
+            e.height =
+              g.height + h.height + 2 * b;
+            this.model.setGeometry(a[d], e);
+            this.moveCells(f, -g.x + h.width + b, -g.y + h.height + b)
+          }
+        }
+      }
+    }
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.cloneCells = function(a, b) {
+  b = null != b ? b : !0;
+  var c = null;
+  if (null != a) {
+    for (var d = {}, c = [], e = 0; e < a.length; e++) {
+      var f = mxCellPath.create(a[e]);
+      d[f] = a[e];
+      c.push(a[e])
+    }
+    if (0 < c.length)
+      for (var f = this.view.scale, g = this.view.translate, c = this.model.cloneCells(a, !0), e = 0; e < a.length; e++)
+        if (!b && this.model.isEdge(c[e]) && null != this.getEdgeValidationError(c[e], this.model.getTerminal(c[e], !0), this.model.getTerminal(c[e], !1))) c[e] = null;
+        else {
+          var h = this.model.getGeometry(c[e]);
+          if (null != h) {
+            var k = this.view.getState(a[e]),
+              l = this.view.getState(this.model.getParent(a[e]));
+            if (null != k && null != l) {
+              var m = l.origin.x,
+                l = l.origin.y;
+              if (this.model.isEdge(c[e])) {
+                for (var k = k.absolutePoints, n = this.model.getTerminal(a[e], !0), p = mxCellPath.create(n); null != n && null == d[p];) n = this.model.getParent(n), p = mxCellPath.create(n);
+                null == n && h.setTerminalPoint(new mxPoint(k[0].x / f - g.x, k[0].y / f - g.y), !0);
+                n = this.model.getTerminal(a[e], !1);
+                for (p = mxCellPath.create(n); null != n && null == d[p];) n = this.model.getParent(n), p = mxCellPath.create(n);
+                null == n && (n = k.length -
+                  1, h.setTerminalPoint(new mxPoint(k[n].x / f - g.x, k[n].y / f - g.y), !1));
+                h = h.points;
+                if (null != h)
+                  for (k = 0; k < h.length; k++) h[k].x += m, h[k].y += l
+              } else h.x += m, h.y += l
+            }
+          }
+        } else c = []
+  }
+  return c
+};
+mxGraph.prototype.insertVertex = function(a, b, c, d, e, f, g, h, k) {
+  b = this.createVertex(a, b, c, d, e, f, g, h, k);
+  return this.addCell(b, a)
+};
+mxGraph.prototype.createVertex = function(a, b, c, d, e, f, g, h, k) {
+  a = new mxGeometry(d, e, f, g);
+  a.relative = null != k ? k : !1;
+  c = new mxCell(c, a, h);
+  c.setId(b);
+  c.setVertex(!0);
+  c.setConnectable(!0);
+  return c
+};
+mxGraph.prototype.insertEdge = function(a, b, c, d, e, f) {
+  b = this.createEdge(a, b, c, d, e, f);
+  return this.addEdge(b, a, d, e)
+};
+mxGraph.prototype.createEdge = function(a, b, c, d, e, f) {
+  a = new mxCell(c, new mxGeometry, f);
+  a.setId(b);
+  a.setEdge(!0);
+  a.geometry.relative = !0;
+  return a
+};
+mxGraph.prototype.addEdge = function(a, b, c, d, e) {
+  return this.addCell(a, b, e, c, d)
+};
+mxGraph.prototype.addCell = function(a, b, c, d, e) {
+  return this.addCells([a], b, c, d, e)[0]
+};
+mxGraph.prototype.addCells = function(a, b, c, d, e) {
+  null == b && (b = this.getDefaultParent());
+  null == c && (c = this.model.getChildCount(b));
+  this.model.beginUpdate();
+  try {
+    this.cellsAdded(a, b, c, d, e, !1, !0), this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, "cells", a, "parent", b, "index", c, "source", d, "target", e))
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.cellsAdded = function(a, b, c, d, e, f, g) {
+  if (null != a && null != b && null != c) {
+    this.model.beginUpdate();
+    try {
+      for (var h = f ? this.view.getState(b) : null, k = null != h ? h.origin : null, l = new mxPoint(0, 0), h = 0; h < a.length; h++)
+        if (null == a[h]) c--;
+        else {
+          var m = this.model.getParent(a[h]);
+          if (null != k && a[h] != b && b != m) {
+            var n = this.view.getState(m),
+              p = null != n ? n.origin : l,
+              q = this.model.getGeometry(a[h]);
+            if (null != q) {
+              var s = p.x - k.x,
+                r = p.y - k.y,
+                q = q.clone();
+              q.translate(s, r);
+              !q.relative && (this.model.isVertex(a[h]) && !this.isAllowNegativeCoordinates()) &&
+                (q.x = Math.max(0, q.x), q.y = Math.max(0, q.y));
+              this.model.setGeometry(a[h], q)
+            }
+          }
+          b == m && c + h > this.model.getChildCount(b) && c--;
+          this.model.add(b, a[h], c + h);
+          this.isExtendParentsOnAdd() && this.isExtendParent(a[h]) && this.extendParent(a[h]);
+          (null == g || g) && this.constrainChild(a[h]);
+          null != d && this.cellConnected(a[h], d, !0);
+          null != e && this.cellConnected(a[h], e, !1)
+        }
+      this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, "cells", a, "parent", b, "index", c, "source", d, "target", e, "absolute", f))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.removeCells = function(a, b) {
+  b = null != b ? b : !0;
+  null == a && (a = this.getDeletableCells(this.getSelectionCells()));
+  b && (a = this.getDeletableCells(this.addAllEdges(a)));
+  this.model.beginUpdate();
+  try {
+    this.cellsRemoved(a), this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, "cells", a, "includeEdges", b))
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.cellsRemoved = function(a) {
+  if (null != a && 0 < a.length) {
+    var b = this.view.scale,
+      c = this.view.translate;
+    this.model.beginUpdate();
+    try {
+      for (var d = {}, e = 0; e < a.length; e++) {
+        var f = mxCellPath.create(a[e]);
+        d[f] = a[e]
+      }
+      for (e = 0; e < a.length; e++) {
+        for (var g = this.getConnections(a[e]), h = 0; h < g.length; h++)
+          if (f = mxCellPath.create(g[h]), null == d[f]) {
+            var k = this.model.getGeometry(g[h]);
+            if (null != k) {
+              var l = this.view.getState(g[h]);
+              if (null != l) {
+                var k = k.clone(),
+                  m = l.getVisibleTerminal(!0) == a[e],
+                  n = l.absolutePoints,
+                  p = m ? 0 : n.length -
+                  1;
+                k.setTerminalPoint(new mxPoint(n[p].x / b - c.x, n[p].y / b - c.y), m);
+                this.model.setTerminal(g[h], null, m);
+                this.model.setGeometry(g[h], k)
+              }
+            }
+          }
+        this.model.remove(a[e])
+      }
+      this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, "cells", a))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.splitEdge = function(a, b, c, d, e) {
+  d = d || 0;
+  e = e || 0;
+  null == c && (c = this.cloneCells([a])[0]);
+  var f = this.model.getParent(a),
+    g = this.model.getTerminal(a, !0);
+  this.model.beginUpdate();
+  try {
+    this.cellsMoved(b, d, e, !1, !1), this.cellsAdded(b, f, this.model.getChildCount(f), null, null, !0), this.cellsAdded([c], f, this.model.getChildCount(f), g, b[0], !1), this.cellConnected(a, b[
+      0], !0), this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, "edge", a, "cells", b, "newEdge", c, "dx", d, "dy", e))
+  } finally {
+    this.model.endUpdate()
+  }
+  return c
+};
+mxGraph.prototype.toggleCells = function(a, b, c) {
+  null == b && (b = this.getSelectionCells());
+  c && (b = this.addAllEdges(b));
+  this.model.beginUpdate();
+  try {
+    this.cellsToggled(b, a), this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS, "show", a, "cells", b, "includeEdges", c))
+  } finally {
+    this.model.endUpdate()
+  }
+  return b
+};
+mxGraph.prototype.cellsToggled = function(a, b) {
+  if (null != a && 0 < a.length) {
+    this.model.beginUpdate();
+    try {
+      for (var c = 0; c < a.length; c++) this.model.setVisible(a[c], b)
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.foldCells = function(a, b, c, d) {
+  b = null != b ? b : !1;
+  null == c && (c = this.getFoldableCells(this.getSelectionCells(), a));
+  this.stopEditing(!1);
+  this.model.beginUpdate();
+  try {
+    this.cellsFolded(c, a, b, d), this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS, "collapse", a, "recurse", b, "cells", c))
+  } finally {
+    this.model.endUpdate()
+  }
+  return c
+};
+mxGraph.prototype.cellsFolded = function(a, b, c, d) {
+  if (null != a && 0 < a.length) {
+    this.model.beginUpdate();
+    try {
+      for (var e = 0; e < a.length; e++)
+        if ((!d || this.isCellFoldable(a[e], b)) && b != this.isCellCollapsed(a[e]))
+          if (this.model.setCollapsed(a[e], b), this.swapBounds(a[e], b), this.isExtendParent(a[e]) && this.extendParent(a[e]), c) {
+            var f = this.model.getChildren(a[e]);
+            this.foldCells(f, b, c)
+          }
+      this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED, "cells", a, "collapse", b, "recurse", c))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.swapBounds = function(a, b) {
+  if (null != a) {
+    var c = this.model.getGeometry(a);
+    null != c && (c = c.clone(), this.updateAlternateBounds(a, c, b), c.swap(), this.model.setGeometry(a, c))
+  }
+};
+mxGraph.prototype.updateAlternateBounds = function(a, b, c) {
+  if (null != a && null != b) {
+    c = this.view.getState(a);
+    c = null != c ? c.style : this.getCellStyle(a);
+    if (null == b.alternateBounds) {
+      var d = b;
+      this.collapseToPreferredSize && (a = this.getPreferredSizeForCell(a), null != a && (d = a, a = mxUtils.getValue(c, mxConstants.STYLE_STARTSIZE), 0 < a && (d.height = Math.max(d.height, a))));
+      b.alternateBounds = new mxRectangle(0, 0, d.width, d.height)
+    }
+    if (null != b.alternateBounds) {
+      b.alternateBounds.x = b.x;
+      b.alternateBounds.y = b.y;
+      var e = mxUtils.toRadians(c[mxConstants.STYLE_ROTATION] ||
+        "0");
+      0 != e && (a = b.alternateBounds.getCenterX() - b.getCenterX(), c = b.alternateBounds.getCenterY() - b.getCenterY(), d = Math.cos(e), e = Math.sin(e), b.alternateBounds.x += d * a - e * c - a,
+        b.alternateBounds.y += e * a + d * c - c)
+    }
+  }
+};
+mxGraph.prototype.addAllEdges = function(a) {
+  var b = a.slice();
+  return b = b.concat(this.getAllEdges(a))
+};
+mxGraph.prototype.getAllEdges = function(a) {
+  var b = [];
+  if (null != a)
+    for (var c = 0; c < a.length; c++) {
+      for (var d = this.model.getEdgeCount(a[c]), e = 0; e < d; e++) b.push(this.model.getEdgeAt(a[c], e));
+      d = this.model.getChildren(a[c]);
+      b = b.concat(this.getAllEdges(d))
+    }
+  return b
+};
+mxGraph.prototype.updateCellSize = function(a, b) {
+  b = null != b ? b : !1;
+  this.model.beginUpdate();
+  try {
+    this.cellSizeUpdated(a, b), this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE, "cell", a, "ignoreChildren", b))
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.cellSizeUpdated = function(a, b) {
+  if (null != a) {
+    this.model.beginUpdate();
+    try {
+      var c = this.getPreferredSizeForCell(a),
+        d = this.model.getGeometry(a);
+      if (null != c && null != d) {
+        var e = this.isCellCollapsed(a),
+          d = d.clone();
+        if (this.isSwimlane(a)) {
+          var f = this.view.getState(a),
+            g = null != f ? f.style : this.getCellStyle(a),
+            h = this.model.getStyle(a);
+          null == h && (h = "");
+          mxUtils.getValue(g, mxConstants.STYLE_HORIZONTAL, !0) ? (h = mxUtils.setStyle(h, mxConstants.STYLE_STARTSIZE, c.height + 8), e && (d.height = c.height + 8), d.width = c.width) :
+            (h = mxUtils.setStyle(h, mxConstants.STYLE_STARTSIZE, c.width + 8), e && (d.width = c.width + 8), d.height = c.height);
+          this.model.setStyle(a, h)
+        } else d.width = c.width, d.height = c.height;
+        if (!b && !e) {
+          var k = this.view.getBounds(this.model.getChildren(a));
+          if (null != k) {
+            var l = this.view.translate,
+              m = this.view.scale,
+              n = (k.y + k.height) / m - d.y - l.y;
+            d.width = Math.max(d.width, (k.x + k.width) / m - d.x - l.x);
+            d.height = Math.max(d.height, n)
+          }
+        }
+        this.cellsResized([a], [d])
+      }
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.getPreferredSizeForCell = function(a) {
+  var b = null;
+  if (null != a) {
+    var c = this.view.getState(a),
+      d = null != c ? c.style : this.getCellStyle(a);
+    if (null != d && !this.model.isEdge(a)) {
+      var e = d[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE,
+        f = 0,
+        b = 0;
+      if ((null != this.getImage(c) || null != d[mxConstants.STYLE_IMAGE]) && d[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL) d[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE &&
+        (f += parseFloat(d[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize),
+        d[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER && (b += parseFloat(d[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize);
+      f += 2 * (d[mxConstants.STYLE_SPACING] || 0);
+      f += d[mxConstants.STYLE_SPACING_LEFT] || 0;
+      f += d[mxConstants.STYLE_SPACING_RIGHT] || 0;
+      b += 2 * (d[mxConstants.STYLE_SPACING] || 0);
+      b += d[mxConstants.STYLE_SPACING_TOP] || 0;
+      b += d[mxConstants.STYLE_SPACING_BOTTOM] || 0;
+      c = this.getFoldingImage(c);
+      null != c && (f += c.width + 8);
+      c = this.getLabel(a);
+      null != c && 0 < c.length ? (this.isHtmlLabel(a) || (c = c.replace(/\n/g,
+        "\x3cbr\x3e")), e = mxUtils.getSizeForString(c, e, d[mxConstants.STYLE_FONTFAMILY]), a = e.width + f, b = e.height + b, mxUtils.getValue(d, mxConstants.STYLE_HORIZONTAL, !0) || (d = b,
+        b = a, a = d), this.gridEnabled && (a = this.snap(a + this.gridSize / 2), b = this.snap(b + this.gridSize / 2)), b = new mxRectangle(0, 0, a, b)) : (d = 4 * this.gridSize, b = new mxRectangle(
+        0, 0, d, d))
+    }
+  }
+  return b
+};
+mxGraph.prototype.handleGesture = function(a, b) {
+  if (0.2 < Math.abs(1 - b.scale)) {
+    var c = this.view.scale,
+      d = this.view.translate,
+      e = a.width * b.scale,
+      f = a.height * b.scale,
+      g = a.y - (f - a.height) / 2,
+      c = new mxRectangle(this.snap((a.x - (e - a.width) / 2) / c) - d.x, this.snap(g / c) - d.y, this.snap(e / c), this.snap(f / c));
+    this.resizeCell(a.cell, c)
+  }
+};
+mxGraph.prototype.resizeCell = function(a, b) {
+  return this.resizeCells([a], [b])[0]
+};
+mxGraph.prototype.resizeCells = function(a, b) {
+  this.model.beginUpdate();
+  try {
+    this.cellsResized(a, b), this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS, "cells", a, "bounds", b))
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.cellsResized = function(a, b) {
+  if (null != a && null != b && a.length == b.length) {
+    this.model.beginUpdate();
+    try {
+      for (var c = 0; c < a.length; c++) {
+        var d = b[c],
+          e = this.model.getGeometry(a[c]);
+        if (null != e && (e.x != d.x || e.y != d.y || e.width != d.width || e.height != d.height)) {
+          e = e.clone();
+          if (e.relative) {
+            var f = e.offset;
+            null != f && (f.x += d.x - e.x, f.y += d.y - e.y)
+          } else e.x = d.x, e.y = d.y;
+          e.width = d.width;
+          e.height = d.height;
+          !e.relative && (this.model.isVertex(a[c]) && !this.isAllowNegativeCoordinates()) && (e.x = Math.max(0, e.x), e.y = Math.max(0,
+            e.y));
+          this.model.setGeometry(a[c], e);
+          this.isExtendParent(a[c]) && this.extendParent(a[c])
+        }
+      }
+      this.resetEdgesOnResize && this.resetEdges(a);
+      this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED, "cells", a, "bounds", b))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.extendParent = function(a) {
+  if (null != a) {
+    var b = this.model.getParent(a),
+      c = this.model.getGeometry(b);
+    if (null != b && (null != c && !this.isCellCollapsed(b)) && (a = this.model.getGeometry(a), null != a && (c.width < a.x + a.width || c.height < a.y + a.height))) c = c.clone(), c.width = Math.max(
+      c.width, a.x + a.width), c.height = Math.max(c.height, a.y + a.height), this.cellsResized([b], [c])
+  }
+};
+mxGraph.prototype.importCells = function(a, b, c, d, e) {
+  return this.moveCells(a, b, c, !0, d, e)
+};
+mxGraph.prototype.moveCells = function(a, b, c, d, e, f) {
+  b = null != b ? b : 0;
+  c = null != c ? c : 0;
+  d = null != d ? d : !1;
+  if (null != a && (0 != b || 0 != c || d || null != e)) {
+    this.model.beginUpdate();
+    try {
+      d && (a = this.cloneCells(a, this.isCloneInvalidEdges()), null == e && (e = this.getDefaultParent()));
+      var g = this.isAllowNegativeCoordinates();
+      null != e && this.setAllowNegativeCoordinates(!0);
+      this.cellsMoved(a, b, c, !d && this.isDisconnectOnMove() && this.isAllowDanglingEdges(), null == e);
+      this.setAllowNegativeCoordinates(g);
+      if (null != e) {
+        var h = this.model.getChildCount(e);
+        this.cellsAdded(a, e, h, null, null, !0)
+      }
+      this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, "cells", a, "dx", b, "dy", c, "clone", d, "target", e, "event", f))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+  return a
+};
+mxGraph.prototype.cellsMoved = function(a, b, c, d, e) {
+  if (null != a && (0 != b || 0 != c)) {
+    this.model.beginUpdate();
+    try {
+      d && this.disconnectGraph(a);
+      for (var f = 0; f < a.length; f++) this.translateCell(a[f], b, c), e && this.constrainChild(a[f]);
+      this.resetEdgesOnMove && this.resetEdges(a);
+      this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED, "cells", a, "dx", c, "dy", c, "disconnect", d))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.translateCell = function(a, b, c) {
+  var d = this.model.getGeometry(a);
+  null != d && (d = d.clone(), d.translate(b, c), !d.relative && (this.model.isVertex(a) && !this.isAllowNegativeCoordinates()) && (d.x = Math.max(0, d.x), d.y = Math.max(0, d.y)), d.relative && !
+    this.model.isEdge(a) && (null == d.offset ? d.offset = new mxPoint(b, c) : (d.offset.x += b, d.offset.y += c)), this.model.setGeometry(a, d))
+};
+mxGraph.prototype.getCellContainmentArea = function(a) {
+  if (null != a && !this.model.isEdge(a)) {
+    var b = this.model.getParent(a);
+    if (b == this.getDefaultParent() || b == this.getCurrentRoot()) return this.getMaximumGraphBounds();
+    if (null != b && b != this.getDefaultParent()) {
+      var c = this.model.getGeometry(b);
+      if (null != c) {
+        var d = a = 0,
+          e = c.width,
+          c = c.height;
+        this.isSwimlane(b) && (b = this.getStartSize(b), a = b.width, e -= b.width, d = b.height, c -= b.height);
+        return new mxRectangle(a, d, e, c)
+      }
+    }
+  }
+  return null
+};
+mxGraph.prototype.getMaximumGraphBounds = function() {
+  return this.maximumGraphBounds
+};
+mxGraph.prototype.constrainChild = function(a) {
+  if (null != a) {
+    var b = this.model.getGeometry(a),
+      c = this.isConstrainChild(a) ? this.getCellContainmentArea(a) : this.getMaximumGraphBounds();
+    if (null != b && null != c && !b.relative && (b.x < c.x || b.y < c.y || c.width < b.x + b.width || c.height < b.y + b.height)) a = this.getOverlap(a), 0 < c.width && (b.x = Math.min(b.x, c.x +
+      c.width - (1 - a) * b.width)), 0 < c.height && (b.y = Math.min(b.y, c.y + c.height - (1 - a) * b.height)), b.x = Math.max(b.x, c.x - b.width * a), b.y = Math.max(b.y, c.y - b.height * a)
+  }
+};
+mxGraph.prototype.resetEdges = function(a) {
+  if (null != a) {
+    for (var b = {}, c = 0; c < a.length; c++) {
+      var d = mxCellPath.create(a[c]);
+      b[d] = a[c]
+    }
+    this.model.beginUpdate();
+    try {
+      for (c = 0; c < a.length; c++) {
+        var e = this.model.getEdges(a[c]);
+        if (null != e)
+          for (d = 0; d < e.length; d++) {
+            var f = this.view.getState(e[d]),
+              g = null != f ? f.getVisibleTerminal(!0) : this.view.getVisibleTerminal(e[d], !0),
+              h = null != f ? f.getVisibleTerminal(!1) : this.view.getVisibleTerminal(e[d], !1),
+              k = mxCellPath.create(g),
+              l = mxCellPath.create(h);
+            (null == b[k] || null == b[l]) && this.resetEdge(e[d])
+          }
+        this.resetEdges(this.model.getChildren(a[c]))
+      }
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.resetEdge = function(a) {
+  var b = this.model.getGeometry(a);
+  null != b && (null != b.points && 0 < b.points.length) && (b = b.clone(), b.points = [], this.model.setGeometry(a, b));
+  return a
+};
+mxGraph.prototype.getAllConnectionConstraints = function(a, b) {
+  return null != a && null != a.shape && null != a.shape.stencil ? a.shape.stencil.constraints : null
+};
+mxGraph.prototype.getConnectionConstraint = function(a, b, c) {
+  b = null;
+  var d = a.style[c ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
+  if (null != d) {
+    var e = a.style[c ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
+    null != e && (b = new mxPoint(parseFloat(d), parseFloat(e)))
+  }
+  d = !1;
+  null != b && (d = mxUtils.getValue(a.style, c ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER, !0));
+  return new mxConnectionConstraint(b, d)
+};
+mxGraph.prototype.setConnectionConstraint = function(a, b, c, d) {
+  if (null != d) {
+    this.model.beginUpdate();
+    try {
+      null == d || null == d.point ? (this.setCellStyles(c ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X, null, [a]), this.setCellStyles(c ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y,
+        null, [a]), this.setCellStyles(c ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER, null, [a])) : null != d.point && (this.setCellStyles(c ? mxConstants.STYLE_EXIT_X :
+        mxConstants.STYLE_ENTRY_X, d.point.x, [a]), this.setCellStyles(c ?
+        mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y, d.point.y, [a]), d.perimeter ? this.setCellStyles(c ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER, null, [
+        a
+      ]) : this.setCellStyles(c ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER, "0", [a]))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.getConnectionPoint = function(a, b) {
+  var c = null;
+  if (null != a) {
+    var d = this.view.getPerimeterBounds(a),
+      e = new mxPoint(d.getCenterX(), d.getCenterY()),
+      f = a.style[mxConstants.STYLE_DIRECTION],
+      g = 0;
+    if (null != f && ("north" == f ? g += 270 : "west" == f ? g += 180 : "south" == f && (g += 90), "north" == f || "south" == f)) {
+      d.x += d.width / 2 - d.height / 2;
+      d.y += d.height / 2 - d.width / 2;
+      var h = d.width;
+      d.width = d.height;
+      d.height = h
+    }
+    if (null != b.point) {
+      var k = c = 1,
+        l = 0,
+        m = 0;
+      if (this.getModel().isVertex(a.cell)) {
+        var n = a.style[mxConstants.STYLE_FLIPH],
+          p =
+          a.style[mxConstants.STYLE_FLIPV];
+        null != a.shape.stencil && (n = 1 == mxUtils.getValue(a.style, "stencilFlipH", 0) || n, p = 1 == mxUtils.getValue(a.style, "stencilFlipV", 0) || p);
+        if ("north" == f || "south" == f) h = n, n = p, p = h;
+        n && (c = -1, l = -d.width);
+        p && (k = -1, m = -d.height)
+      }
+      c = new mxPoint(d.x + b.point.x * d.width * c - l, d.y + b.point.y * d.height * k - m)
+    }
+    f = a.style[mxConstants.STYLE_ROTATION] || 0;
+    b.perimeter ? (0 != g && null != c && (h = d = 0, 90 == g ? h = 1 : 180 == g ? d = -1 : 270 == f && (h = -1), c = mxUtils.getRotatedPoint(c, d, h, e)), null != c && b.perimeter && (c = this.view
+      .getPerimeterPoint(a,
+        c, !1))) : f += g;
+    0 != f && null != c && (g = mxUtils.toRadians(f), d = Math.cos(g), h = Math.sin(g), c = mxUtils.getRotatedPoint(c, d, h, e))
+  }
+  return c
+};
+mxGraph.prototype.connectCell = function(a, b, c, d) {
+  this.model.beginUpdate();
+  try {
+    var e = this.model.getTerminal(a, c);
+    this.cellConnected(a, b, c, d);
+    this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL, "edge", a, "terminal", b, "source", c, "previous", e))
+  } finally {
+    this.model.endUpdate()
+  }
+  return a
+};
+mxGraph.prototype.cellConnected = function(a, b, c, d) {
+  if (null != a) {
+    this.model.beginUpdate();
+    try {
+      var e = this.model.getTerminal(a, c);
+      this.setConnectionConstraint(a, b, c, d);
+      this.isPortsEnabled() && (d = null, this.isPort(b) && (d = b.getId(), b = this.getTerminalForPort(b, c)), this.setCellStyles(c ? mxConstants.STYLE_SOURCE_PORT : mxConstants.STYLE_TARGET_PORT,
+        d, [a]));
+      this.model.setTerminal(a, b, c);
+      this.resetEdgesOnConnect && this.resetEdge(a);
+      this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED, "edge", a, "terminal", b, "source",
+        c, "previous", e))
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.disconnectGraph = function(a) {
+  if (null != a) {
+    this.model.beginUpdate();
+    try {
+      for (var b = this.view.scale, c = this.view.translate, d = {}, e = 0; e < a.length; e++) {
+        var f = mxCellPath.create(a[e]);
+        d[f] = a[e]
+      }
+      for (e = 0; e < a.length; e++)
+        if (this.model.isEdge(a[e])) {
+          var g = this.model.getGeometry(a[e]);
+          if (null != g) {
+            var h = this.view.getState(a[e]),
+              k = this.view.getState(this.model.getParent(a[e]));
+            if (null != h && null != k) {
+              var g = g.clone(),
+                l = -k.origin.x,
+                m = -k.origin.y,
+                n = h.absolutePoints,
+                p = this.model.getTerminal(a[e], !0);
+              if (null !=
+                p && this.isCellDisconnectable(a[e], p, !0)) {
+                for (var q = mxCellPath.create(p); null != p && null == d[q];) p = this.model.getParent(p), q = mxCellPath.create(p);
+                null == p && (g.setTerminalPoint(new mxPoint(n[0].x / b - c.x + l, n[0].y / b - c.y + m), !0), this.model.setTerminal(a[e], null, !0))
+              }
+              var s = this.model.getTerminal(a[e], !1);
+              if (null != s && this.isCellDisconnectable(a[e], s, !1)) {
+                for (var r = mxCellPath.create(s); null != s && null == d[r];) s = this.model.getParent(s), r = mxCellPath.create(s);
+                if (null == s) {
+                  var t = n.length - 1;
+                  g.setTerminalPoint(new mxPoint(n[t].x /
+                    b - c.x + l, n[t].y / b - c.y + m), !1);
+                  this.model.setTerminal(a[e], null, !1)
+                }
+              }
+              this.model.setGeometry(a[e], g)
+            }
+          }
+        }
+    } finally {
+      this.model.endUpdate()
+    }
+  }
+};
+mxGraph.prototype.getCurrentRoot = function() {
+  return this.view.currentRoot
+};
+mxGraph.prototype.getTranslateForRoot = function(a) {
+  return null
+};
+mxGraph.prototype.isPort = function(a) {
+  return !1
+};
+mxGraph.prototype.getTerminalForPort = function(a, b) {
+  return this.model.getParent(a)
+};
+mxGraph.prototype.getChildOffsetForCell = function(a) {
+  return null
+};
+mxGraph.prototype.enterGroup = function(a) {
+  a = a || this.getSelectionCell();
+  null != a && this.isValidRoot(a) && (this.view.setCurrentRoot(a), this.clearSelection())
+};
+mxGraph.prototype.exitGroup = function() {
+  var a = this.model.getRoot(),
+    b = this.getCurrentRoot();
+  if (null != b) {
+    for (var c = this.model.getParent(b); c != a && !this.isValidRoot(c) && this.model.getParent(c) != a;) c = this.model.getParent(c);
+    c == a || this.model.getParent(c) == a ? this.view.setCurrentRoot(null) : this.view.setCurrentRoot(c);
+    null != this.view.getState(b) && this.setSelectionCell(b)
+  }
+};
+mxGraph.prototype.home = function() {
+  var a = this.getCurrentRoot();
+  null != a && (this.view.setCurrentRoot(null), null != this.view.getState(a) && this.setSelectionCell(a))
+};
+mxGraph.prototype.isValidRoot = function(a) {
+  return null != a
+};
+mxGraph.prototype.getGraphBounds = function() {
+  return this.view.getGraphBounds()
+};
+mxGraph.prototype.getCellBounds = function(a, b, c) {
+  var d = [a];
+  b && (d = d.concat(this.model.getEdges(a)));
+  d = this.view.getBounds(d);
+  if (c) {
+    c = this.model.getChildCount(a);
+    for (var e = 0; e < c; e++) {
+      var f = this.getCellBounds(this.model.getChildAt(a, e), b, !0);
+      null != d ? d.add(f) : d = f
+    }
+  }
+  return d
+};
+mxGraph.prototype.getBoundingBoxFromGeometry = function(a, b) {
+  b = null != b ? b : !1;
+  var c = null;
+  if (null != a)
+    for (var d = 0; d < a.length; d++)
+      if (b || this.model.isVertex(a[d])) {
+        var e = this.getCellGeometry(a[d]);
+        if (null != e) {
+          var f = e.points;
+          if (null != f && 0 < f.length) {
+            for (var g = new mxRectangle(f[0].x, f[0].y, 0, 0), h = function(a) {
+                null != a && g.add(new mxRectangle(a.x, a.y, 0, 0))
+              }, k = 1; k < f.length; k++) h(f[k]);
+            h(e.getTerminalPoint(!0));
+            h(e.getTerminalPoint(!1))
+          }
+          null == c ? c = new mxRectangle(e.x, e.y, e.width, e.height) : c.add(e)
+        }
+      }
+  return c
+};
+mxGraph.prototype.refresh = function(a) {
+  this.view.clear(a, null == a);
+  this.view.validate();
+  this.sizeDidChange();
+  this.fireEvent(new mxEventObject(mxEvent.REFRESH))
+};
+mxGraph.prototype.snap = function(a) {
+  this.gridEnabled && (a = Math.round(a / this.gridSize) * this.gridSize);
+  return a
+};
+mxGraph.prototype.panGraph = function(a, b) {
+  if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container)) this.container.scrollLeft = -a, this.container.scrollTop = -b;
+  else {
+    var c = this.view.getCanvas();
+    if (this.dialect == mxConstants.DIALECT_SVG)
+      if (0 == a && 0 == b) {
+        if (mxClient.IS_IE ? c.setAttribute("transform", "translate(" + a + "," + b + ")") : c.removeAttribute("transform"), null != this.shiftPreview1) {
+          for (var d = this.shiftPreview1.firstChild; null != d;) {
+            var e = d.nextSibling;
+            this.container.appendChild(d);
+            d = e
+          }
+          this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
+          this.shiftPreview1 = null;
+          this.container.appendChild(c.parentNode);
+          for (d = this.shiftPreview2.firstChild; null != d;) e = d.nextSibling, this.container.appendChild(d), d = e;
+          this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
+          this.shiftPreview2 = null
+        }
+      } else {
+        c.setAttribute("transform", "translate(" + a + "," + b + ")");
+        if (null == this.shiftPreview1) {
+          this.shiftPreview1 = document.createElement("div");
+          this.shiftPreview1.style.position = "absolute";
+          this.shiftPreview1.style.overflow = "visible";
+          this.shiftPreview2 = document.createElement("div");
+          this.shiftPreview2.style.position = "absolute";
+          this.shiftPreview2.style.overflow = "visible";
+          for (var f = this.shiftPreview1, d = this.container.firstChild; null != d;) e = d.nextSibling, d != c.parentNode ? f.appendChild(d) : f = this.shiftPreview2, d = e;
+          this.container.insertBefore(this.shiftPreview1, c.parentNode);
+          this.container.appendChild(this.shiftPreview2)
+        }
+        this.shiftPreview1.style.left = a + "px";
+        this.shiftPreview1.style.top = b + "px";
+        this.shiftPreview2.style.left = a + "px";
+        this.shiftPreview2.style.top = b + "px"
+      } else c.style.left =
+      a + "px", c.style.top = b + "px";
+    this.panDx = a;
+    this.panDy = b;
+    this.fireEvent(new mxEventObject(mxEvent.PAN))
+  }
+};
+mxGraph.prototype.zoomIn = function() {
+  this.zoom(this.zoomFactor)
+};
+mxGraph.prototype.zoomOut = function() {
+  this.zoom(1 / this.zoomFactor)
+};
+mxGraph.prototype.zoomActual = function() {
+  1 == this.view.scale ? this.view.setTranslate(0, 0) : (this.view.translate.x = 0, this.view.translate.y = 0, this.view.setScale(1))
+};
+mxGraph.prototype.zoomTo = function(a, b) {
+  this.zoom(a / this.view.scale, b)
+};
+mxGraph.prototype.zoom = function(a, b) {
+  b = null != b ? b : this.centerZoom;
+  var c = this.view.scale * a,
+    d = this.view.getState(this.getSelectionCell());
+  if (this.keepSelectionVisibleOnZoom && null != d) d = new mxRectangle(d.x * a, d.y * a, d.width * a, d.height * a), this.view.scale = c, this.scrollRectToVisible(d) || (this.view.revalidate(),
+    this.view.setScale(c));
+  else if (b && !mxUtils.hasScrollbars(this.container)) {
+    var d = this.container.offsetWidth,
+      e = this.container.offsetHeight;
+    if (1 < a) var f = (a - 1) / (2 * c),
+      d = d * -f,
+      e = e * -f;
+    else f = (1 / a - 1) / (2 * this.view.scale),
+      d *= f, e *= f;
+    this.view.scaleAndTranslate(c, this.view.translate.x + d, this.view.translate.y + e)
+  } else this.view.setScale(c), mxUtils.hasScrollbars(this.container) && (e = d = 0, b && (d = this.container.offsetWidth * (a - 1) / 2, e = this.container.offsetHeight * (a - 1) / 2), this.container
+    .scrollLeft = Math.round(this.container.scrollLeft * a + d), this.container.scrollTop = Math.round(this.container.scrollTop * a + e))
+};
+mxGraph.prototype.zoomToRect = function(a) {
+  var b = this.container.clientWidth / a.width / (this.container.clientHeight / a.height);
+  a.x = Math.max(0, a.x);
+  a.y = Math.max(0, a.y);
+  var c = Math.min(this.container.scrollWidth, a.x + a.width),
+    d = Math.min(this.container.scrollHeight, a.y + a.height);
+  a.width = c - a.x;
+  a.height = d - a.y;
+  1 > b ? (b = a.height / b, c = (b - a.height) / 2, a.height = b, b = Math.min(a.y, c), a.y -= b, d = Math.min(this.container.scrollHeight, a.y + a.height), a.height = d - a.y) : (b *= a.width, c =
+    (b - a.width) / 2, a.width = b, b = Math.min(a.x, c), a.x -= b,
+    c = Math.min(this.container.scrollWidth, a.x + a.width), a.width = c - a.x);
+  b = this.container.clientWidth / a.width;
+  c = this.view.scale * b;
+  mxUtils.hasScrollbars(this.container) ? (this.view.setScale(c), this.container.scrollLeft = Math.round(a.x * b), this.container.scrollTop = Math.round(a.y * b)) : this.view.scaleAndTranslate(c,
+    this.view.translate.x - a.x / this.view.scale, this.view.translate.y - a.y / this.view.scale)
+};
+mxGraph.prototype.fit = function(a, b) {
+  if (null != this.container) {
+    a = null != a ? a : 0;
+    b = null != b ? b : !1;
+    var c = this.container.clientWidth,
+      d = this.container.clientHeight,
+      e = this.view.getGraphBounds();
+    b && (null != e.x && null != e.y) && (e.width += e.x, e.height += e.y, e.x = 0, e.y = 0);
+    var f = this.view.scale,
+      g = e.width / f,
+      h = e.height / f;
+    null != this.backgroundImage && (g = Math.max(g, this.backgroundImage.width - e.x / f), h = Math.max(h, this.backgroundImage.height - e.y / f));
+    var k = b ? a : 2 * a,
+      c = Math.floor(100 * Math.min(c / (g + k), d / (h + k))) / 100;
+    null != this.minFitScale &&
+      (c = Math.max(c, this.minFitScale));
+    null != this.maxFitScale && (c = Math.min(c, this.maxFitScale));
+    b ? this.view.scale != c && this.view.setScale(c) : mxUtils.hasScrollbars(this.container) ? (this.view.setScale(c), null != e.x && (this.container.scrollLeft = Math.round(e.x / f) * c - a -
+        Math.max(0, (this.container.clientWidth - g * c) / 2)), null != e.y && (this.container.scrollTop = Math.round(e.y / f) * c - a - Math.max(0, (this.container.clientHeight - h * c) / 2))) :
+      this.view.scaleAndTranslate(c, null != e.x ? Math.floor(this.view.translate.x - e.x / f + a + 1) : a, null !=
+        e.y ? Math.floor(this.view.translate.y - e.y / f + a + 1) : a)
+  }
+  return this.view.scale
+};
+mxGraph.prototype.scrollCellToVisible = function(a, b) {
+  var c = -this.view.translate.x,
+    d = -this.view.translate.y,
+    e = this.view.getState(a);
+  null != e && (c = new mxRectangle(c + e.x, d + e.y, e.width, e.height), b && null != this.container && (d = this.container.clientWidth, e = this.container.clientHeight, c.x = c.getCenterX() - d /
+    2, c.width = d, c.y = c.getCenterY() - e / 2, c.height = e), this.scrollRectToVisible(c) && this.view.setTranslate(this.view.translate.x, this.view.translate.y))
+};
+mxGraph.prototype.scrollRectToVisible = function(a) {
+  var b = !1;
+  if (null != a) {
+    var c = this.container.offsetWidth,
+      d = this.container.offsetHeight,
+      e = Math.min(c, a.width),
+      f = Math.min(d, a.height);
+    if (mxUtils.hasScrollbars(this.container)) {
+      c = this.container;
+      a.x += this.view.translate.x;
+      a.y += this.view.translate.y;
+      var g = c.scrollLeft - a.x,
+        d = Math.max(g - c.scrollLeft, 0);
+      0 < g ? c.scrollLeft -= g + 2 : (g = a.x + e - c.scrollLeft - c.clientWidth, 0 < g && (c.scrollLeft += g + 2));
+      e = c.scrollTop - a.y;
+      g = Math.max(0, e - c.scrollTop);
+      0 < e ? c.scrollTop -= e + 2 : (e = a.y +
+        f - c.scrollTop - c.clientHeight, 0 < e && (c.scrollTop += e + 2));
+      !this.useScrollbarsForPanning && (0 != d || 0 != g) && this.view.setTranslate(d, g)
+    } else {
+      var g = -this.view.translate.x,
+        h = -this.view.translate.y,
+        k = this.view.scale;
+      a.x + e > g + c && (this.view.translate.x -= (a.x + e - c - g) / k, b = !0);
+      a.y + f > h + d && (this.view.translate.y -= (a.y + f - d - h) / k, b = !0);
+      a.x < g && (this.view.translate.x += (g - a.x) / k, b = !0);
+      a.y < h && (this.view.translate.y += (h - a.y) / k, b = !0);
+      b && (this.view.refresh(), null != this.selectionCellsHandler && this.selectionCellsHandler.refresh())
+    }
+  }
+  return b
+};
+mxGraph.prototype.getCellGeometry = function(a) {
+  return this.model.getGeometry(a)
+};
+mxGraph.prototype.isCellVisible = function(a) {
+  return this.model.isVisible(a)
+};
+mxGraph.prototype.isCellCollapsed = function(a) {
+  return this.model.isCollapsed(a)
+};
+mxGraph.prototype.isCellConnectable = function(a) {
+  return this.model.isConnectable(a)
+};
+mxGraph.prototype.isOrthogonal = function(a) {
+  var b = a.style[mxConstants.STYLE_ORTHOGONAL];
+  if (null != b) return b;
+  a = this.view.getEdgeStyle(a);
+  return a == mxEdgeStyle.SegmentConnector || a == mxEdgeStyle.ElbowConnector || a == mxEdgeStyle.SideToSide || a == mxEdgeStyle.TopToBottom || a == mxEdgeStyle.EntityRelation || a == mxEdgeStyle.OrthConnector
+};
+mxGraph.prototype.isLoop = function(a) {
+  var b = a.getVisibleTerminalState(!0);
+  a = a.getVisibleTerminalState(!1);
+  return null != b && b == a
+};
+mxGraph.prototype.isCloneEvent = function(a) {
+  return mxEvent.isControlDown(a)
+};
+mxGraph.prototype.isToggleEvent = function(a) {
+  return mxClient.IS_MAC ? mxEvent.isMetaDown(a) : mxEvent.isControlDown(a)
+};
+mxGraph.prototype.isGridEnabledEvent = function(a) {
+  return null != a && !mxEvent.isAltDown(a)
+};
+mxGraph.prototype.isConstrainedEvent = function(a) {
+  return mxEvent.isShiftDown(a)
+};
+mxGraph.prototype.isForceMarqueeEvent = function(a) {
+  return mxEvent.isAltDown(a)
+};
+mxGraph.prototype.validationAlert = function(a) {
+  mxUtils.alert(a)
+};
+mxGraph.prototype.isEdgeValid = function(a, b, c) {
+  return null == this.getEdgeValidationError(a, b, c)
+};
+mxGraph.prototype.getEdgeValidationError = function(a, b, c) {
+  if (null != a && !this.isAllowDanglingEdges() && (null == b || null == c)) return "";
+  if (null != a && null == this.model.getTerminal(a, !0) && null == this.model.getTerminal(a, !1)) return null;
+  if (!this.allowLoops && b == c && null != b || !this.isValidConnection(b, c)) return "";
+  if (null != b && null != c) {
+    var d = "";
+    if (!this.multigraph) {
+      var e = this.model.getEdgesBetween(b, c, !0);
+      if (1 < e.length || 1 == e.length && e[0] != a) d += (mxResources.get(this.alreadyConnectedResource) || this.alreadyConnectedResource) +
+        "\n"
+    }
+    var e = this.model.getDirectedEdgeCount(b, !0, a),
+      f = this.model.getDirectedEdgeCount(c, !1, a);
+    if (null != this.multiplicities)
+      for (var g = 0; g < this.multiplicities.length; g++) {
+        var h = this.multiplicities[g].check(this, a, b, c, e, f);
+        null != h && (d += h)
+      }
+    h = this.validateEdge(a, b, c);
+    null != h && (d += h);
+    return 0 < d.length ? d : null
+  }
+  return this.allowDanglingEdges ? null : ""
+};
+mxGraph.prototype.validateEdge = function(a, b, c) {
+  return null
+};
+mxGraph.prototype.validateGraph = function(a, b) {
+  a = null != a ? a : this.model.getRoot();
+  b = null != b ? b : {};
+  for (var c = !0, d = this.model.getChildCount(a), e = 0; e < d; e++) {
+    var f = this.model.getChildAt(a, e),
+      g = b;
+    this.isValidRoot(f) && (g = {});
+    g = this.validateGraph(f, g);
+    null != g ? this.setCellWarning(f, g.replace(/\n/g, "\x3cbr\x3e")) : this.setCellWarning(f, null);
+    c = c && null == g
+  }
+  d = "";
+  this.isCellCollapsed(a) && !c && (d += (mxResources.get(this.containsValidationErrorsResource) || this.containsValidationErrorsResource) + "\n");
+  d = this.model.isEdge(a) ?
+    d + (this.getEdgeValidationError(a, this.model.getTerminal(a, !0), this.model.getTerminal(a, !1)) || "") : d + (this.getCellValidationError(a) || "");
+  e = this.validateCell(a, b);
+  null != e && (d += e);
+  null == this.model.getParent(a) && this.view.validate();
+  return 0 < d.length || !c ? d : null
+};
+mxGraph.prototype.getCellValidationError = function(a) {
+  var b = this.model.getDirectedEdgeCount(a, !0),
+    c = this.model.getDirectedEdgeCount(a, !1);
+  a = this.model.getValue(a);
+  var d = "";
+  if (null != this.multiplicities)
+    for (var e = 0; e < this.multiplicities.length; e++) {
+      var f = this.multiplicities[e];
+      if (f.source && mxUtils.isNode(a, f.type, f.attr, f.value) && (0 == f.max && 0 < b || 1 == f.min && 0 == b || 1 == f.max && 1 < b)) d += f.countError + "\n";
+      else if (!f.source && mxUtils.isNode(a, f.type, f.attr, f.value) && (0 == f.max && 0 < c || 1 == f.min && 0 == c || 1 == f.max && 1 <
+          c)) d += f.countError + "\n"
+    }
+  return 0 < d.length ? d : null
+};
+mxGraph.prototype.validateCell = function(a, b) {
+  return null
+};
+mxGraph.prototype.getBackgroundImage = function() {
+  return this.backgroundImage
+};
+mxGraph.prototype.setBackgroundImage = function(a) {
+  this.backgroundImage = a
+};
+mxGraph.prototype.getFoldingImage = function(a) {
+  if (null != a && this.foldingEnabled && !this.getModel().isEdge(a.cell)) {
+    var b = this.isCellCollapsed(a.cell);
+    if (this.isCellFoldable(a.cell, !b)) return b ? this.collapsedImage : this.expandedImage
+  }
+  return null
+};
+mxGraph.prototype.convertValueToString = function(a) {
+  a = this.model.getValue(a);
+  if (null != a) {
+    if (mxUtils.isNode(a)) return a.nodeName;
+    if ("function" == typeof a.toString) return a.toString()
+  }
+  return ""
+};
+mxGraph.prototype.getLabel = function(a) {
+  var b = "";
+  if (this.labelsVisible && null != a) {
+    var c = this.view.getState(a),
+      c = null != c ? c.style : this.getCellStyle(a);
+    mxUtils.getValue(c, mxConstants.STYLE_NOLABEL, !1) || (b = this.convertValueToString(a))
+  }
+  return b
+};
+mxGraph.prototype.isHtmlLabel = function(a) {
+  return this.isHtmlLabels()
+};
+mxGraph.prototype.isHtmlLabels = function() {
+  return this.htmlLabels
+};
+mxGraph.prototype.setHtmlLabels = function(a) {
+  this.htmlLabels = a
+};
+mxGraph.prototype.isWrapping = function(a) {
+  var b = this.view.getState(a);
+  a = null != b ? b.style : this.getCellStyle(a);
+  return null != a ? "wrap" == a[mxConstants.STYLE_WHITE_SPACE] : !1
+};
+mxGraph.prototype.isLabelClipped = function(a) {
+  var b = this.view.getState(a);
+  a = null != b ? b.style : this.getCellStyle(a);
+  return null != a ? "hidden" == a[mxConstants.STYLE_OVERFLOW] : !1
+};
+mxGraph.prototype.getTooltip = function(a, b, c, d) {
+  var e = null;
+  if (null != a) {
+    if (null != a.control && (b == a.control.node || b.parentNode == a.control.node)) e = this.collapseExpandResource, e = mxResources.get(e) || e;
+    null == e && null != a.overlays && a.overlays.visit(function(a, c) {
+      if (null == e && (b == c.node || b.parentNode == c.node)) e = c.overlay.toString()
+    });
+    null == e && (c = this.selectionCellsHandler.getHandler(a.cell), null != c && "function" == typeof c.getTooltipForNode && (e = c.getTooltipForNode(b)));
+    null == e && (e = this.getTooltipForCell(a.cell))
+  }
+  return e
+};
+mxGraph.prototype.getTooltipForCell = function(a) {
+  var b = null;
+  return b = null != a && null != a.getTooltip ? a.getTooltip() : this.convertValueToString(a)
+};
+mxGraph.prototype.getCursorForCell = function(a) {
+  return null
+};
+mxGraph.prototype.getStartSize = function(a) {
+  var b = new mxRectangle,
+    c = this.view.getState(a);
+  a = null != c ? c.style : this.getCellStyle(a);
+  null != a && (c = parseInt(mxUtils.getValue(a, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE)), mxUtils.getValue(a, mxConstants.STYLE_HORIZONTAL, !0) ? b.height = c : b.width = c);
+  return b
+};
+mxGraph.prototype.getImage = function(a) {
+  return null != a && null != a.style ? a.style[mxConstants.STYLE_IMAGE] : null
+};
+mxGraph.prototype.getVerticalAlign = function(a) {
+  return null != a && null != a.style ? a.style[mxConstants.STYLE_VERTICAL_ALIGN] || mxConstants.ALIGN_MIDDLE : null
+};
+mxGraph.prototype.getIndicatorColor = function(a) {
+  return null != a && null != a.style ? a.style[mxConstants.STYLE_INDICATOR_COLOR] : null
+};
+mxGraph.prototype.getIndicatorGradientColor = function(a) {
+  return null != a && null != a.style ? a.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null
+};
+mxGraph.prototype.getIndicatorShape = function(a) {
+  return null != a && null != a.style ? a.style[mxConstants.STYLE_INDICATOR_SHAPE] : null
+};
+mxGraph.prototype.getIndicatorImage = function(a) {
+  return null != a && null != a.style ? a.style[mxConstants.STYLE_INDICATOR_IMAGE] : null
+};
+mxGraph.prototype.getBorder = function() {
+  return this.border
+};
+mxGraph.prototype.setBorder = function(a) {
+  this.border = a
+};
+mxGraph.prototype.isSwimlane = function(a) {
+  if (null != a && this.model.getParent(a) != this.model.getRoot()) {
+    var b = this.view.getState(a),
+      b = null != b ? b.style : this.getCellStyle(a);
+    if (null != b && !this.model.isEdge(a)) return b[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE
+  }
+  return !1
+};
+mxGraph.prototype.isResizeContainer = function() {
+  return this.resizeContainer
+};
+mxGraph.prototype.setResizeContainer = function(a) {
+  this.resizeContainer = a
+};
+mxGraph.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxGraph.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxGraph.prototype.isEscapeEnabled = function() {
+  return this.escapeEnabled
+};
+mxGraph.prototype.setEscapeEnabled = function(a) {
+  this.escapeEnabled = a
+};
+mxGraph.prototype.isInvokesStopCellEditing = function() {
+  return this.invokesStopCellEditing
+};
+mxGraph.prototype.setInvokesStopCellEditing = function(a) {
+  this.invokesStopCellEditing = a
+};
+mxGraph.prototype.isEnterStopsCellEditing = function() {
+  return this.enterStopsCellEditing
+};
+mxGraph.prototype.setEnterStopsCellEditing = function(a) {
+  this.enterStopsCellEditing = a
+};
+mxGraph.prototype.isCellLocked = function(a) {
+  var b = this.model.getGeometry(a);
+  return this.isCellsLocked() || null != b && this.model.isVertex(a) && b.relative
+};
+mxGraph.prototype.isCellsLocked = function() {
+  return this.cellsLocked
+};
+mxGraph.prototype.setCellsLocked = function(a) {
+  this.cellsLocked = a
+};
+mxGraph.prototype.getCloneableCells = function(a) {
+  return this.model.filterCells(a, mxUtils.bind(this, function(a) {
+    return this.isCellCloneable(a)
+  }))
+};
+mxGraph.prototype.isCellCloneable = function(a) {
+  var b = this.view.getState(a);
+  a = null != b ? b.style : this.getCellStyle(a);
+  return this.isCellsCloneable() && 0 != a[mxConstants.STYLE_CLONEABLE]
+};
+mxGraph.prototype.isCellsCloneable = function() {
+  return this.cellsCloneable
+};
+mxGraph.prototype.setCellsCloneable = function(a) {
+  this.cellsCloneable = a
+};
+mxGraph.prototype.getExportableCells = function(a) {
+  return this.model.filterCells(a, mxUtils.bind(this, function(a) {
+    return this.canExportCell(a)
+  }))
+};
+mxGraph.prototype.canExportCell = function(a) {
+  return this.exportEnabled
+};
+mxGraph.prototype.getImportableCells = function(a) {
+  return this.model.filterCells(a, mxUtils.bind(this, function(a) {
+    return this.canImportCell(a)
+  }))
+};
+mxGraph.prototype.canImportCell = function(a) {
+  return this.importEnabled
+};
+mxGraph.prototype.isCellSelectable = function(a) {
+  return this.isCellsSelectable()
+};
+mxGraph.prototype.isCellsSelectable = function() {
+  return this.cellsSelectable
+};
+mxGraph.prototype.setCellsSelectable = function(a) {
+  this.cellsSelectable = a
+};
+mxGraph.prototype.getDeletableCells = function(a) {
+  return this.model.filterCells(a, mxUtils.bind(this, function(a) {
+    return this.isCellDeletable(a)
+  }))
+};
+mxGraph.prototype.isCellDeletable = function(a) {
+  var b = this.view.getState(a);
+  a = null != b ? b.style : this.getCellStyle(a);
+  return this.isCellsDeletable() && 0 != a[mxConstants.STYLE_DELETABLE]
+};
+mxGraph.prototype.isCellsDeletable = function() {
+  return this.cellsDeletable
+};
+mxGraph.prototype.setCellsDeletable = function(a) {
+  this.cellsDeletable = a
+};
+mxGraph.prototype.isLabelMovable = function(a) {
+  return !this.isCellLocked(a) && (this.model.isEdge(a) && this.edgeLabelsMovable || this.model.isVertex(a) && this.vertexLabelsMovable)
+};
+mxGraph.prototype.getMovableCells = function(a) {
+  return this.model.filterCells(a, mxUtils.bind(this, function(a) {
+    return this.isCellMovable(a)
+  }))
+};
+mxGraph.prototype.isCellMovable = function(a) {
+  var b = this.view.getState(a),
+    b = null != b ? b.style : this.getCellStyle(a);
+  return this.isCellsMovable() && !this.isCellLocked(a) && 0 != b[mxConstants.STYLE_MOVABLE]
+};
+mxGraph.prototype.isCellsMovable = function() {
+  return this.cellsMovable
+};
+mxGraph.prototype.setCellsMovable = function(a) {
+  this.cellsMovable = a
+};
+mxGraph.prototype.isGridEnabled = function() {
+  return this.gridEnabled
+};
+mxGraph.prototype.setGridEnabled = function(a) {
+  this.gridEnabled = a
+};
+mxGraph.prototype.isPortsEnabled = function() {
+  return this.portsEnabled
+};
+mxGraph.prototype.setPortsEnabled = function(a) {
+  this.portsEnabled = a
+};
+mxGraph.prototype.getGridSize = function() {
+  return this.gridSize
+};
+mxGraph.prototype.setGridSize = function(a) {
+  this.gridSize = a
+};
+mxGraph.prototype.getTolerance = function() {
+  return this.tolerance
+};
+mxGraph.prototype.setTolerance = function(a) {
+  this.tolerance = a
+};
+mxGraph.prototype.isVertexLabelsMovable = function() {
+  return this.vertexLabelsMovable
+};
+mxGraph.prototype.setVertexLabelsMovable = function(a) {
+  this.vertexLabelsMovable = a
+};
+mxGraph.prototype.isEdgeLabelsMovable = function() {
+  return this.edgeLabelsMovable
+};
+mxGraph.prototype.setEdgeLabelsMovable = function(a) {
+  this.edgeLabelsMovable = a
+};
+mxGraph.prototype.isSwimlaneNesting = function() {
+  return this.swimlaneNesting
+};
+mxGraph.prototype.setSwimlaneNesting = function(a) {
+  this.swimlaneNesting = a
+};
+mxGraph.prototype.isSwimlaneSelectionEnabled = function() {
+  return this.swimlaneSelectionEnabled
+};
+mxGraph.prototype.setSwimlaneSelectionEnabled = function(a) {
+  this.swimlaneSelectionEnabled = a
+};
+mxGraph.prototype.isMultigraph = function() {
+  return this.multigraph
+};
+mxGraph.prototype.setMultigraph = function(a) {
+  this.multigraph = a
+};
+mxGraph.prototype.isAllowLoops = function() {
+  return this.allowLoops
+};
+mxGraph.prototype.setAllowDanglingEdges = function(a) {
+  this.allowDanglingEdges = a
+};
+mxGraph.prototype.isAllowDanglingEdges = function() {
+  return this.allowDanglingEdges
+};
+mxGraph.prototype.setConnectableEdges = function(a) {
+  this.connectableEdges = a
+};
+mxGraph.prototype.isConnectableEdges = function() {
+  return this.connectableEdges
+};
+mxGraph.prototype.setCloneInvalidEdges = function(a) {
+  this.cloneInvalidEdges = a
+};
+mxGraph.prototype.isCloneInvalidEdges = function() {
+  return this.cloneInvalidEdges
+};
+mxGraph.prototype.setAllowLoops = function(a) {
+  this.allowLoops = a
+};
+mxGraph.prototype.isDisconnectOnMove = function() {
+  return this.disconnectOnMove
+};
+mxGraph.prototype.setDisconnectOnMove = function(a) {
+  this.disconnectOnMove = a
+};
+mxGraph.prototype.isDropEnabled = function() {
+  return this.dropEnabled
+};
+mxGraph.prototype.setDropEnabled = function(a) {
+  this.dropEnabled = a
+};
+mxGraph.prototype.isSplitEnabled = function() {
+  return this.splitEnabled
+};
+mxGraph.prototype.setSplitEnabled = function(a) {
+  this.splitEnabled = a
+};
+mxGraph.prototype.isCellResizable = function(a) {
+  var b = this.view.getState(a),
+    b = null != b ? b.style : this.getCellStyle(a);
+  return this.isCellsResizable() && !this.isCellLocked(a) && 0 != b[mxConstants.STYLE_RESIZABLE]
+};
+mxGraph.prototype.isCellsResizable = function() {
+  return this.cellsResizable
+};
+mxGraph.prototype.setCellsResizable = function(a) {
+  this.cellsResizable = a
+};
+mxGraph.prototype.isTerminalPointMovable = function(a, b) {
+  return !0
+};
+mxGraph.prototype.isCellBendable = function(a) {
+  var b = this.view.getState(a),
+    b = null != b ? b.style : this.getCellStyle(a);
+  return this.isCellsBendable() && !this.isCellLocked(a) && 0 != b[mxConstants.STYLE_BENDABLE]
+};
+mxGraph.prototype.isCellsBendable = function() {
+  return this.cellsBendable
+};
+mxGraph.prototype.setCellsBendable = function(a) {
+  this.cellsBendable = a
+};
+mxGraph.prototype.isCellEditable = function(a) {
+  var b = this.view.getState(a),
+    b = null != b ? b.style : this.getCellStyle(a);
+  return this.isCellsEditable() && !this.isCellLocked(a) && 0 != b[mxConstants.STYLE_EDITABLE]
+};
+mxGraph.prototype.isCellsEditable = function() {
+  return this.cellsEditable
+};
+mxGraph.prototype.setCellsEditable = function(a) {
+  this.cellsEditable = a
+};
+mxGraph.prototype.isCellDisconnectable = function(a, b, c) {
+  return this.isCellsDisconnectable() && !this.isCellLocked(a)
+};
+mxGraph.prototype.isCellsDisconnectable = function() {
+  return this.cellsDisconnectable
+};
+mxGraph.prototype.setCellsDisconnectable = function(a) {
+  this.cellsDisconnectable = a
+};
+mxGraph.prototype.isValidSource = function(a) {
+  return null == a && this.allowDanglingEdges || null != a && (!this.model.isEdge(a) || this.connectableEdges) && this.isCellConnectable(a)
+};
+mxGraph.prototype.isValidTarget = function(a) {
+  return this.isValidSource(a)
+};
+mxGraph.prototype.isValidConnection = function(a, b) {
+  return this.isValidSource(a) && this.isValidTarget(b)
+};
+mxGraph.prototype.setConnectable = function(a) {
+  this.connectionHandler.setEnabled(a)
+};
+mxGraph.prototype.isConnectable = function(a) {
+  return this.connectionHandler.isEnabled()
+};
+mxGraph.prototype.setTooltips = function(a) {
+  this.tooltipHandler.setEnabled(a)
+};
+mxGraph.prototype.setPanning = function(a) {
+  this.panningHandler.panningEnabled = a
+};
+mxGraph.prototype.isEditing = function(a) {
+  if (null != this.cellEditor) {
+    var b = this.cellEditor.getEditingCell();
+    return null == a ? null != b : a == b
+  }
+  return !1
+};
+mxGraph.prototype.isAutoSizeCell = function(a) {
+  var b = this.view.getState(a);
+  a = null != b ? b.style : this.getCellStyle(a);
+  return this.isAutoSizeCells() || 1 == a[mxConstants.STYLE_AUTOSIZE]
+};
+mxGraph.prototype.isAutoSizeCells = function() {
+  return this.autoSizeCells
+};
+mxGraph.prototype.setAutoSizeCells = function(a) {
+  this.autoSizeCells = a
+};
+mxGraph.prototype.isExtendParent = function(a) {
+  return !this.getModel().isEdge(a) && this.isExtendParents()
+};
+mxGraph.prototype.isExtendParents = function() {
+  return this.extendParents
+};
+mxGraph.prototype.setExtendParents = function(a) {
+  this.extendParents = a
+};
+mxGraph.prototype.isExtendParentsOnAdd = function() {
+  return this.extendParentsOnAdd
+};
+mxGraph.prototype.setExtendParentsOnAdd = function(a) {
+  this.extendParentsOnAdd = a
+};
+mxGraph.prototype.isConstrainChild = function(a) {
+  return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(a))
+};
+mxGraph.prototype.isConstrainChildren = function() {
+  return this.constrainChildren
+};
+mxGraph.prototype.setConstrainChildren = function(a) {
+  this.constrainChildren = a
+};
+mxGraph.prototype.isAllowNegativeCoordinates = function() {
+  return this.allowNegativeCoordinates
+};
+mxGraph.prototype.setAllowNegativeCoordinates = function(a) {
+  this.allowNegativeCoordinates = a
+};
+mxGraph.prototype.getOverlap = function(a) {
+  return this.isAllowOverlapParent(a) ? this.defaultOverlap : 0
+};
+mxGraph.prototype.isAllowOverlapParent = function(a) {
+  return !1
+};
+mxGraph.prototype.getFoldableCells = function(a, b) {
+  return this.model.filterCells(a, mxUtils.bind(this, function(a) {
+    return this.isCellFoldable(a, b)
+  }))
+};
+mxGraph.prototype.isCellFoldable = function(a, b) {
+  var c = this.view.getState(a),
+    c = null != c ? c.style : this.getCellStyle(a);
+  return 0 < this.model.getChildCount(a) && 0 != c[mxConstants.STYLE_FOLDABLE]
+};
+mxGraph.prototype.isValidDropTarget = function(a, b, c) {
+  return null != a && (this.isSplitEnabled() && this.isSplitTarget(a, b, c) || !this.model.isEdge(a) && (this.isSwimlane(a) || 0 < this.model.getChildCount(a) && !this.isCellCollapsed(a)))
+};
+mxGraph.prototype.isSplitTarget = function(a, b, c) {
+  return this.model.isEdge(a) && null != b && 1 == b.length && this.isCellConnectable(b[0]) && null == this.getEdgeValidationError(a, this.model.getTerminal(a, !0), b[0]) ? (c = this.model.getTerminal(
+    a, !0), a = this.model.getTerminal(a, !1), !this.model.isAncestor(b[0], c) && !this.model.isAncestor(b[0], a)) : !1
+};
+mxGraph.prototype.getDropTarget = function(a, b, c) {
+  if (!this.isSwimlaneNesting())
+    for (var d = 0; d < a.length; d++)
+      if (this.isSwimlane(a[d])) return null;
+  d = mxUtils.convertPoint(this.container, mxEvent.getClientX(b), mxEvent.getClientY(b));
+  d.x -= this.panDx;
+  d.y -= this.panDy;
+  d = this.getSwimlaneAt(d.x, d.y);
+  if (null == c) c = d;
+  else if (null != d) {
+    for (var e = this.model.getParent(d); null != e && this.isSwimlane(e) && e != c;) e = this.model.getParent(e);
+    e == c && (c = d)
+  }
+  for (; null != c && !this.isValidDropTarget(c, a, b) && !this.model.isLayer(c);) c = this.model.getParent(c);
+  return !this.model.isLayer(c) && 0 > mxUtils.indexOf(a, c) ? c : null
+};
+mxGraph.prototype.getDefaultParent = function() {
+  var a = this.defaultParent;
+  null == a && (a = this.getCurrentRoot(), null == a && (a = this.model.getRoot(), a = this.model.getChildAt(a, 0)));
+  return a
+};
+mxGraph.prototype.setDefaultParent = function(a) {
+  this.defaultParent = a
+};
+mxGraph.prototype.getSwimlane = function(a) {
+  for (; null != a && !this.isSwimlane(a);) a = this.model.getParent(a);
+  return a
+};
+mxGraph.prototype.getSwimlaneAt = function(a, b, c) {
+  c = c || this.getDefaultParent();
+  if (null != c)
+    for (var d = this.model.getChildCount(c), e = 0; e < d; e++) {
+      var f = this.model.getChildAt(c, e),
+        g = this.getSwimlaneAt(a, b, f);
+      if (null != g) return g;
+      if (this.isSwimlane(f) && (g = this.view.getState(f), this.intersects(g, a, b))) return f
+    }
+  return null
+};
+mxGraph.prototype.getCellAt = function(a, b, c, d, e) {
+  d = null != d ? d : !0;
+  e = null != e ? e : !0;
+  c = null != c ? c : this.getDefaultParent();
+  if (null != c)
+    for (var f = this.model.getChildCount(c) - 1; 0 <= f; f--) {
+      var g = this.model.getChildAt(c, f),
+        h = this.getCellAt(a, b, g, d, e);
+      if (null != h) return h;
+      if (this.isCellVisible(g) && (e && this.model.isEdge(g) || d && this.model.isVertex(g)))
+        if (h = this.view.getState(g), this.intersects(h, a, b)) return g
+    }
+  return null
+};
+mxGraph.prototype.intersects = function(a, b, c) {
+  if (null != a) {
+    var d = a.absolutePoints;
+    if (null != d) {
+      a = this.tolerance * this.tolerance;
+      for (var e = d[0], f = 1; f < d.length; f++) {
+        var g = d[f];
+        if (mxUtils.ptSegDistSq(e.x, e.y, g.x, g.y, b, c) <= a) return !0;
+        e = g
+      }
+    } else if (e = mxUtils.toRadians(mxUtils.getValue(a.style, mxConstants.STYLE_ROTATION) || 0), 0 != e && (d = Math.cos(-e), e = Math.sin(-e), f = new mxPoint(a.getCenterX(), a.getCenterY()), e =
+        mxUtils.getRotatedPoint(new mxPoint(b, c), d, e, f), b = e.x, c = e.y), mxUtils.contains(a, b, c)) return !0
+  }
+  return !1
+};
+mxGraph.prototype.hitsSwimlaneContent = function(a, b, c) {
+  var d = this.getView().getState(a);
+  a = this.getStartSize(a);
+  if (null != d) {
+    var e = this.getView().getScale();
+    b -= d.x;
+    c -= d.y;
+    if (0 < a.width && 0 < b && b > a.width * e || 0 < a.height && 0 < c && c > a.height * e) return !0
+  }
+  return !1
+};
+mxGraph.prototype.getChildVertices = function(a) {
+  return this.getChildCells(a, !0, !1)
+};
+mxGraph.prototype.getChildEdges = function(a) {
+  return this.getChildCells(a, !1, !0)
+};
+mxGraph.prototype.getChildCells = function(a, b, c) {
+  a = null != a ? a : this.getDefaultParent();
+  a = this.model.getChildCells(a, null != b ? b : !1, null != c ? c : !1);
+  b = [];
+  for (c = 0; c < a.length; c++) this.isCellVisible(a[c]) && b.push(a[c]);
+  return b
+};
+mxGraph.prototype.getConnections = function(a, b) {
+  return this.getEdges(a, b, !0, !0, !1)
+};
+mxGraph.prototype.getIncomingEdges = function(a, b) {
+  return this.getEdges(a, b, !0, !1, !1)
+};
+mxGraph.prototype.getOutgoingEdges = function(a, b) {
+  return this.getEdges(a, b, !1, !0, !1)
+};
+mxGraph.prototype.getEdges = function(a, b, c, d, e, f) {
+  c = null != c ? c : !0;
+  d = null != d ? d : !0;
+  e = null != e ? e : !0;
+  f = null != f ? f : !1;
+  for (var g = [], h = this.isCellCollapsed(a), k = this.model.getChildCount(a), l = 0; l < k; l++) {
+    var m = this.model.getChildAt(a, l);
+    if (h || !this.isCellVisible(m)) g = g.concat(this.model.getEdges(m, c, d))
+  }
+  g = g.concat(this.model.getEdges(a, c, d));
+  h = [];
+  for (l = 0; l < g.length; l++) m = this.view.getState(g[l]), k = null != m ? m.getVisibleTerminal(!0) : this.view.getVisibleTerminal(g[l], !0), m = null != m ? m.getVisibleTerminal(!1) : this.view
+    .getVisibleTerminal(g[l], !1), (e && k == m || k != m && (c && m == a && (null == b || this.isValidAncestor(k, b, f)) || d && k == a && (null == b || this.isValidAncestor(m, b, f)))) && h.push(
+      g[l]);
+  return h
+};
+mxGraph.prototype.isValidAncestor = function(a, b, c) {
+  return c ? this.model.isAncestor(b, a) : this.model.getParent(a) == b
+};
+mxGraph.prototype.getOpposites = function(a, b, c, d) {
+  c = null != c ? c : !0;
+  d = null != d ? d : !0;
+  var e = [],
+    f = {};
+  if (null != a)
+    for (var g = 0; g < a.length; g++) {
+      var h = this.view.getState(a[g]),
+        k = null != h ? h.getVisibleTerminal(!0) : this.view.getVisibleTerminal(a[g], !0),
+        h = null != h ? h.getVisibleTerminal(!1) : this.view.getVisibleTerminal(a[g], !1);
+      if (k == b && null != h && h != b && d) {
+        var l = mxCellPath.create(h);
+        null == f[l] && (f[l] = h, e.push(h))
+      } else h == b && (null != k && k != b && c) && (l = mxCellPath.create(k), null == f[l] && (f[l] = k, e.push(k)))
+    }
+  return e
+};
+mxGraph.prototype.getEdgesBetween = function(a, b, c) {
+  c = null != c ? c : !1;
+  for (var d = this.getEdges(a), e = [], f = 0; f < d.length; f++) {
+    var g = this.view.getState(d[f]),
+      h = null != g ? g.getVisibleTerminal(!0) : this.view.getVisibleTerminal(d[f], !0),
+      g = null != g ? g.getVisibleTerminal(!1) : this.view.getVisibleTerminal(d[f], !1);
+    (h == a && g == b || !c && h == b && g == a) && e.push(d[f])
+  }
+  return e
+};
+mxGraph.prototype.getPointForEvent = function(a, b) {
+  var c = mxUtils.convertPoint(this.container, mxEvent.getClientX(a), mxEvent.getClientY(a)),
+    d = this.view.scale,
+    e = this.view.translate,
+    f = !1 != b ? this.gridSize / 2 : 0;
+  c.x = this.snap(c.x / d - e.x - f);
+  c.y = this.snap(c.y / d - e.y - f);
+  return c
+};
+mxGraph.prototype.getCells = function(a, b, c, d, e, f) {
+  f = null != f ? f : [];
+  if (0 < c || 0 < d) {
+    var g = a + c,
+      h = b + d;
+    e = e || this.getDefaultParent();
+    if (null != e)
+      for (var k = this.model.getChildCount(e), l = 0; l < k; l++) {
+        var m = this.model.getChildAt(e, l),
+          n = this.view.getState(m);
+        if (this.isCellVisible(m) && null != n) {
+          var p = n,
+            n = mxUtils.getValue(n.style, mxConstants.STYLE_ROTATION) || 0;
+          0 != n && (p = mxUtils.getBoundingBox(p, n));
+          p.x >= a && p.y + p.height <= h && p.y >= b && p.x + p.width <= g ? f.push(m) : this.getCells(a, b, c, d, m, f)
+        }
+      }
+  }
+  return f
+};
+mxGraph.prototype.getCellsBeyond = function(a, b, c, d, e) {
+  var f = [];
+  if (d || e)
+    if (null == c && (c = this.getDefaultParent()), null != c)
+      for (var g = this.model.getChildCount(c), h = 0; h < g; h++) {
+        var k = this.model.getChildAt(c, h),
+          l = this.view.getState(k);
+        this.isCellVisible(k) && null != l && (!d || l.x >= a) && (!e || l.y >= b) && f.push(k)
+      }
+  return f
+};
+mxGraph.prototype.findTreeRoots = function(a, b, c) {
+  b = null != b ? b : !1;
+  c = null != c ? c : !1;
+  var d = [];
+  if (null != a) {
+    for (var e = this.getModel(), f = e.getChildCount(a), g = null, h = 0, k = 0; k < f; k++) {
+      var l = e.getChildAt(a, k);
+      if (this.model.isVertex(l) && this.isCellVisible(l)) {
+        for (var m = this.getConnections(l, b ? a : null), n = 0, p = 0, q = 0; q < m.length; q++) this.view.getVisibleTerminal(m[q], !0) == l ? n++ : p++;
+        (c && 0 == n && 0 < p || !c && 0 == p && 0 < n) && d.push(l);
+        m = c ? p - n : n - p;
+        m > h && (h = m, g = l)
+      }
+    }
+    0 == d.length && null != g && d.push(g)
+  }
+  return d
+};
+mxGraph.prototype.traverse = function(a, b, c, d, e) {
+  if (null != c && null != a) {
+    b = null != b ? b : !0;
+    e = e || [];
+    var f = mxCellPath.create(a);
+    if (null == e[f] && (e[f] = a, d = c(a, d), null == d || d))
+      if (d = this.model.getEdgeCount(a), 0 < d)
+        for (f = 0; f < d; f++) {
+          var g = this.model.getEdgeAt(a, f),
+            h = this.model.getTerminal(g, !0) == a;
+          if (!b || h) h = this.model.getTerminal(g, !h), this.traverse(h, b, c, g, e)
+        }
+  }
+};
+mxGraph.prototype.isCellSelected = function(a) {
+  return this.getSelectionModel().isSelected(a)
+};
+mxGraph.prototype.isSelectionEmpty = function() {
+  return this.getSelectionModel().isEmpty()
+};
+mxGraph.prototype.clearSelection = function() {
+  return this.getSelectionModel().clear()
+};
+mxGraph.prototype.getSelectionCount = function() {
+  return this.getSelectionModel().cells.length
+};
+mxGraph.prototype.getSelectionCell = function() {
+  return this.getSelectionModel().cells[0]
+};
+mxGraph.prototype.getSelectionCells = function() {
+  return this.getSelectionModel().cells.slice()
+};
+mxGraph.prototype.setSelectionCell = function(a) {
+  this.getSelectionModel().setCell(a)
+};
+mxGraph.prototype.setSelectionCells = function(a) {
+  this.getSelectionModel().setCells(a)
+};
+mxGraph.prototype.addSelectionCell = function(a) {
+  this.getSelectionModel().addCell(a)
+};
+mxGraph.prototype.addSelectionCells = function(a) {
+  this.getSelectionModel().addCells(a)
+};
+mxGraph.prototype.removeSelectionCell = function(a) {
+  this.getSelectionModel().removeCell(a)
+};
+mxGraph.prototype.removeSelectionCells = function(a) {
+  this.getSelectionModel().removeCells(a)
+};
+mxGraph.prototype.selectRegion = function(a, b) {
+  var c = this.getCells(a.x, a.y, a.width, a.height);
+  this.selectCellsForEvent(c, b);
+  return c
+};
+mxGraph.prototype.selectNextCell = function() {
+  this.selectCell(!0)
+};
+mxGraph.prototype.selectPreviousCell = function() {
+  this.selectCell()
+};
+mxGraph.prototype.selectParentCell = function() {
+  this.selectCell(!1, !0)
+};
+mxGraph.prototype.selectChildCell = function() {
+  this.selectCell(!1, !1, !0)
+};
+mxGraph.prototype.selectCell = function(a, b, c) {
+  var d = this.selectionModel,
+    e = 0 < d.cells.length ? d.cells[0] : null;
+  1 < d.cells.length && d.clear();
+  var d = null != e ? this.model.getParent(e) : this.getDefaultParent(),
+    f = this.model.getChildCount(d);
+  null == e && 0 < f ? (a = this.model.getChildAt(d, 0), this.setSelectionCell(a)) : (null == e || b) && null != this.view.getState(d) && null != this.model.getGeometry(d) ? this.getCurrentRoot() !=
+    d && this.setSelectionCell(d) : null != e && c ? 0 < this.model.getChildCount(e) && (a = this.model.getChildAt(e, 0), this.setSelectionCell(a)) :
+    0 < f && (b = d.getIndex(e), a ? (b++, a = this.model.getChildAt(d, b % f)) : (b--, a = this.model.getChildAt(d, 0 > b ? f - 1 : b)), this.setSelectionCell(a))
+};
+mxGraph.prototype.selectAll = function(a) {
+  a = a || this.getDefaultParent();
+  a = this.model.getChildren(a);
+  null != a && this.setSelectionCells(a)
+};
+mxGraph.prototype.selectVertices = function(a) {
+  this.selectCells(!0, !1, a)
+};
+mxGraph.prototype.selectEdges = function(a) {
+  this.selectCells(!1, !0, a)
+};
+mxGraph.prototype.selectCells = function(a, b, c) {
+  c = c || this.getDefaultParent();
+  var d = mxUtils.bind(this, function(c) {
+    return null != this.view.getState(c) && 0 == this.model.getChildCount(c) && (this.model.isVertex(c) && a || this.model.isEdge(c) && b)
+  });
+  c = this.model.filterDescendants(d, c);
+  this.setSelectionCells(c)
+};
+mxGraph.prototype.selectCellForEvent = function(a, b) {
+  var c = this.isCellSelected(a);
+  this.isToggleEvent(b) ? c ? this.removeSelectionCell(a) : this.addSelectionCell(a) : (!c || 1 != this.getSelectionCount()) && this.setSelectionCell(a)
+};
+mxGraph.prototype.selectCellsForEvent = function(a, b) {
+  this.isToggleEvent(b) ? this.addSelectionCells(a) : this.setSelectionCells(a)
+};
+mxGraph.prototype.createHandler = function(a) {
+  var b = null;
+  null != a && (this.model.isEdge(a.cell) ? (b = this.view.getEdgeStyle(a), b = this.isLoop(a) || b == mxEdgeStyle.ElbowConnector || b == mxEdgeStyle.SideToSide || b == mxEdgeStyle.TopToBottom ?
+    new mxElbowEdgeHandler(a) : b == mxEdgeStyle.SegmentConnector || b == mxEdgeStyle.OrthConnector ? new mxEdgeSegmentHandler(a) : new mxEdgeHandler(a)) : b = new mxVertexHandler(a));
+  return b
+};
+mxGraph.prototype.addMouseListener = function(a) {
+  null == this.mouseListeners && (this.mouseListeners = []);
+  this.mouseListeners.push(a)
+};
+mxGraph.prototype.removeMouseListener = function(a) {
+  if (null != this.mouseListeners)
+    for (var b = 0; b < this.mouseListeners.length; b++)
+      if (this.mouseListeners[b] == a) {
+        this.mouseListeners.splice(b, 1);
+        break
+      }
+};
+mxGraph.prototype.updateMouseEvent = function(a) {
+  if (null == a.graphX || null == a.graphY) {
+    var b = mxUtils.convertPoint(this.container, a.getX(), a.getY());
+    a.graphX = b.x - this.panDx;
+    a.graphY = b.y - this.panDy
+  }
+};
+mxGraph.prototype.fireMouseEvent = function(a, b, c) {
+  null == c && (c = this);
+  this.updateMouseEvent(b);
+  a == mxEvent.MOUSE_DOWN && (this.isMouseDown = !0);
+  if (mxClient.IS_TOUCH && this.doubleTapEnabled && a == mxEvent.MOUSE_DOWN) {
+    var d = (new Date).getTime();
+    d - this.lastTouchTime < this.doubleTapTimeout && Math.abs(this.lastTouchX - b.getX()) < this.doubleTapTolerance && Math.abs(this.lastTouchY - b.getY()) < this.doubleTapTolerance ? (this.lastTouchTime =
+      0, this.dblClick(b.getEvent(), b.getCell()), b.getEvent().cancelBubble = !0) : (this.lastTouchX =
+      b.getX(), this.lastTouchY = b.getY(), this.lastTouchTime = d)
+  }
+  d = 2 != b.getEvent().detail;
+  if (mxClient.IS_IE && "CSS1Compat" == document.compatMode) {
+    if (null != this.lastMouseX && Math.abs(this.lastMouseX - b.getX()) > this.doubleTapTolerance || null != this.lastMouseY && Math.abs(this.lastMouseY - b.getY()) > this.doubleTapTolerance) d = !
+      0;
+    a == mxEvent.MOUSE_UP && (this.lastMouseX = b.getX(), this.lastMouseY = b.getY())
+  }
+  if ((a != mxEvent.MOUSE_UP || this.isMouseDown) && d) {
+    if (a == mxEvent.MOUSE_UP && (this.isMouseDown = !1), !this.isEditing() && (mxClient.IS_OP ||
+        mxClient.IS_SF || mxClient.IS_GC || mxClient.IS_IE && mxClient.IS_SVG || b.getEvent().target != this.container)) {
+      a == mxEvent.MOUSE_MOVE && (this.isMouseDown && this.autoScroll) && this.scrollPointToVisible(b.getGraphX(), b.getGraphY(), this.autoExtend);
+      if (null != this.mouseListeners) {
+        c = [c, b];
+        b.getEvent().returnValue = !0;
+        for (d = 0; d < this.mouseListeners.length; d++) {
+          var e = this.mouseListeners[d];
+          a == mxEvent.MOUSE_DOWN ? e.mouseDown.apply(e, c) : a == mxEvent.MOUSE_MOVE ? e.mouseMove.apply(e, c) : a == mxEvent.MOUSE_UP && e.mouseUp.apply(e, c)
+        }
+      }
+      a ==
+        mxEvent.MOUSE_UP && this.click(b)
+    }
+  } else a == mxEvent.MOUSE_UP && (this.isMouseDown = !1)
+};
+mxGraph.prototype.destroy = function() {
+  this.destroyed || (this.destroyed = !0, null != this.tooltipHandler && this.tooltipHandler.destroy(), null != this.selectionCellsHandler && this.selectionCellsHandler.destroy(), null != this.panningHandler &&
+    this.panningHandler.destroy(), null != this.connectionHandler && this.connectionHandler.destroy(), null != this.graphHandler && this.graphHandler.destroy(), null != this.cellEditor && this.cellEditor
+    .destroy(), null != this.view && this.view.destroy(), null != this.model && null != this.graphModelChangeListener &&
+    (this.model.removeListener(this.graphModelChangeListener), this.graphModelChangeListener = null), this.container = null)
+};
+
+function mxCellOverlay(a, b, c, d, e, f) {
+  this.image = a;
+  this.tooltip = b;
+  this.align = null != c ? c : this.align;
+  this.verticalAlign = null != d ? d : this.verticalAlign;
+  this.offset = null != e ? e : new mxPoint;
+  this.cursor = null != f ? f : "help"
+}
+mxCellOverlay.prototype = new mxEventSource;
+mxCellOverlay.prototype.constructor = mxCellOverlay;
+mxCellOverlay.prototype.image = null;
+mxCellOverlay.prototype.tooltip = null;
+mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;
+mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;
+mxCellOverlay.prototype.offset = null;
+mxCellOverlay.prototype.cursor = null;
+mxCellOverlay.prototype.defaultOverlap = 0.5;
+mxCellOverlay.prototype.getBounds = function(a) {
+  var b = a.view.graph.getModel().isEdge(a.cell),
+    c = a.view.scale,
+    d = null,
+    e = this.image.width,
+    f = this.image.height;
+  b ? (b = a.absolutePoints, 1 == b.length % 2 ? d = b[Math.floor(b.length / 2)] : (d = b.length / 2, a = b[d - 1], b = b[d], d = new mxPoint(a.x + (b.x - a.x) / 2, a.y + (b.y - a.y) / 2))) : (d =
+    new mxPoint, d.x = this.align == mxConstants.ALIGN_LEFT ? a.x : this.align == mxConstants.ALIGN_CENTER ? a.x + a.width / 2 : a.x + a.width, d.y = this.verticalAlign == mxConstants.ALIGN_TOP ?
+    a.y : this.verticalAlign == mxConstants.ALIGN_MIDDLE ?
+    a.y + a.height / 2 : a.y + a.height);
+  return new mxRectangle(Math.round(d.x - (e * this.defaultOverlap - this.offset.x) * c), Math.round(d.y - (f * this.defaultOverlap - this.offset.y) * c), e * c, f * c)
+};
+mxCellOverlay.prototype.toString = function() {
+  return this.tooltip
+};
+
+function mxOutline(a, b) {
+  this.source = a;
+  null != b && this.init(b)
+}
+mxOutline.prototype.source = null;
+mxOutline.prototype.outline = null;
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+mxOutline.prototype.enabled = !0;
+mxOutline.prototype.showViewport = !0;
+mxOutline.prototype.border = 10;
+mxOutline.prototype.sizerSize = 8;
+mxOutline.prototype.updateOnPan = !1;
+mxOutline.prototype.sizerImage = null;
+mxOutline.prototype.suspended = !1;
+mxOutline.prototype.init = function(a) {
+  this.outline = new mxGraph(a, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+  this.outline.foldingEnabled = !1;
+  this.outline.autoScroll = !1;
+  var b = this.outline.graphModelChanged;
+  this.outline.graphModelChanged = mxUtils.bind(this, function(a) {
+    !this.suspended && null != this.outline && b.apply(this.outline, arguments)
+  });
+  mxClient.IS_SVG && (a = this.outline.getView().getCanvas().parentNode, a.setAttribute("shape-rendering", "optimizeSpeed"), a.setAttribute("image-rendering",
+    "optimizeSpeed"));
+  this.outline.labelsVisible = !1;
+  this.outline.setEnabled(!1);
+  this.updateHandler = mxUtils.bind(this, function(a, b) {
+    !this.suspended && !this.active && this.update()
+  });
+  this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+  this.outline.addMouseListener(this);
+  a = this.source.getView();
+  a.addListener(mxEvent.SCALE, this.updateHandler);
+  a.addListener(mxEvent.TRANSLATE, this.updateHandler);
+  a.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+  a.addListener(mxEvent.DOWN, this.updateHandler);
+  a.addListener(mxEvent.UP, this.updateHandler);
+  mxEvent.addListener(this.source.container, "scroll", this.updateHandler);
+  this.panHandler = mxUtils.bind(this, function(a) {
+    this.updateOnPan && this.updateHandler.apply(this, arguments)
+  });
+  this.source.addListener(mxEvent.PAN, this.panHandler);
+  this.refreshHandler = mxUtils.bind(this, function(a) {
+    this.outline.setStylesheet(this.source.getStylesheet());
+    this.outline.refresh()
+  });
+  this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+  this.bounds = new mxRectangle(0, 0, 0,
+    0);
+  this.selectionBorder = new mxRectangleShape(this.bounds, null, mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+  this.selectionBorder.dialect = this.outline.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+  this.selectionBorder.init(this.outline.getView().getOverlayPane());
+  mxEvent.redirectMouseEvents(this.selectionBorder.node, this.outline);
+  this.selectionBorder.node.style.background = "";
+  this.sizer = this.createSizer();
+  this.sizer.init(this.outline.getView().getOverlayPane());
+  this.enabled && (this.sizer.node.style.cursor = "pointer");
+  mxEvent.addListener(this.sizer.node, mxClient.IS_TOUCH ? "touchstart" : "mousedown", mxUtils.bind(this, function(a) {
+    this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(a))
+  }));
+  this.selectionBorder.node.style.display = this.showViewport ? "" : "none";
+  this.sizer.node.style.display = this.selectionBorder.node.style.display;
+  this.selectionBorder.node.style.cursor = "move";
+  this.update(!1)
+};
+mxOutline.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxOutline.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxOutline.prototype.setZoomEnabled = function(a) {
+  this.sizer.node.style.visibility = a ? "visible" : "hidden"
+};
+mxOutline.prototype.refresh = function() {
+  this.update(!0)
+};
+mxOutline.prototype.createSizer = function() {
+  var a = null != this.sizerImage ? new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src) : new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize,
+    this.sizerSize), mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+  a.dialect = this.outline.dialect;
+  return a
+};
+mxOutline.prototype.getSourceContainerSize = function() {
+  return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight)
+};
+mxOutline.prototype.getOutlineOffset = function(a) {
+  return null
+};
+mxOutline.prototype.update = function(a) {
+  if (null != this.source) {
+    var b = this.source.view.scale,
+      c = this.source.getGraphBounds(),
+      c = new mxRectangle(c.x / b + this.source.panDx, c.y / b + this.source.panDy, c.width / b, c.height / b),
+      d = new mxRectangle(0, 0, this.source.container.clientWidth / b, this.source.container.clientHeight / b),
+      e = c.clone();
+    e.add(d);
+    var f = this.getSourceContainerSize(),
+      d = Math.max(f.width / b, e.width),
+      b = Math.max(f.height / b, e.height),
+      e = Math.max(0, this.outline.container.clientWidth - this.border),
+      f = Math.max(0, this.outline.container.clientHeight -
+        this.border),
+      e = Math.min(e / d, f / b);
+    if (0 < e) {
+      this.outline.getView().scale != e && (this.outline.getView().scale = e, a = !0);
+      d = this.outline.getView();
+      d.currentRoot != this.source.getView().currentRoot && d.setCurrentRoot(this.source.getView().currentRoot);
+      var b = this.source.view.translate,
+        f = b.x + this.source.panDx,
+        g = b.y + this.source.panDy,
+        e = this.getOutlineOffset(e);
+      null != e && (f += e.x, g += e.y);
+      0 > c.x && (f -= c.x);
+      0 > c.y && (g -= c.y);
+      if (d.translate.x != f || d.translate.y != g) d.translate.x = f, d.translate.y = g, a = !0;
+      var c = d.translate,
+        e = this.source.getView().scale,
+        f = e / d.scale,
+        g = 1 / d.scale,
+        h = this.source.container;
+      this.bounds = new mxRectangle((c.x - b.x - this.source.panDx) / g, (c.y - b.y - this.source.panDy) / g, h.clientWidth / f, h.clientHeight / f);
+      this.bounds.x += this.source.container.scrollLeft * d.scale / e;
+      this.bounds.y += this.source.container.scrollTop * d.scale / e;
+      c = this.selectionBorder.bounds;
+      if (c.x != this.bounds.x || c.y != this.bounds.y || c.width != this.bounds.width || c.height != this.bounds.height) this.selectionBorder.bounds = this.bounds, this.selectionBorder.redraw();
+      c = this.sizer.bounds;
+      d = new mxRectangle(this.bounds.x + this.bounds.width - c.width / 2, this.bounds.y + this.bounds.height - c.height / 2, c.width, c.height);
+      if (c.x != d.x || c.y != d.y || c.width != d.width || c.height != d.height) this.sizer.bounds = d, "hidden" != this.sizer.node.style.visibility && this.sizer.redraw();
+      a && this.outline.view.revalidate()
+    }
+  }
+};
+mxOutline.prototype.mouseDown = function(a, b) {
+  this.enabled && this.showViewport && (this.zoom = b.isSource(this.sizer), this.startX = b.getX(), this.startY = b.getY(), this.active = !0, this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(
+    this.source.container) ? (this.dx0 = this.source.container.scrollLeft, this.dy0 = this.source.container.scrollTop) : this.dy0 = this.dx0 = 0);
+  b.consume()
+};
+mxOutline.prototype.mouseMove = function(a, b) {
+  if (this.active) {
+    this.selectionBorder.node.style.display = this.showViewport ? "" : "none";
+    this.sizer.node.style.display = this.selectionBorder.node.style.display;
+    var c = b.getX() - this.startX,
+      d = b.getY() - this.startY,
+      e = null;
+    if (this.zoom) e = this.source.container, d = c / (e.clientWidth / e.clientHeight), e = new mxRectangle(this.bounds.x, this.bounds.y, Math.max(1, this.bounds.width + c), Math.max(1, this.bounds
+      .height + d)), this.selectionBorder.bounds = e, this.selectionBorder.redraw();
+    else {
+      var f =
+        this.outline.getView().scale,
+        e = new mxRectangle(this.bounds.x + c, this.bounds.y + d, this.bounds.width, this.bounds.height);
+      this.selectionBorder.bounds = e;
+      this.selectionBorder.redraw();
+      c = c / f * this.source.getView().scale;
+      d = d / f * this.source.getView().scale;
+      this.source.panGraph(-c - this.dx0, -d - this.dy0)
+    }
+    c = this.sizer.bounds;
+    this.sizer.bounds = new mxRectangle(e.x + e.width - c.width / 2, e.y + e.height - c.height / 2, c.width, c.height);
+    "hidden" != this.sizer.node.style.visibility && this.sizer.redraw();
+    b.consume()
+  }
+};
+mxOutline.prototype.mouseUp = function(a, b) {
+  if (this.active) {
+    var c = b.getX() - this.startX,
+      d = b.getY() - this.startY;
+    if (0 < Math.abs(c) || 0 < Math.abs(d)) {
+      if (this.zoom) {
+        var d = this.selectionBorder.bounds.width,
+          e = this.source.getView().scale;
+        this.source.zoomTo(e - c * e / d, !1)
+      } else if (!this.source.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.source.container)) this.source.panGraph(0, 0), c /= this.outline.getView().scale, d /= this.outline.getView().scale,
+        e = this.source.getView().translate, this.source.getView().setTranslate(e.x -
+          c, e.y - d);
+      this.update();
+      b.consume()
+    }
+    this.index = null;
+    this.active = !1
+  }
+};
+mxOutline.prototype.destroy = function() {
+  null != this.source && (this.source.removeListener(this.panHandler), this.source.removeListener(this.refreshHandler), this.source.getModel().removeListener(this.updateHandler), this.source.getView()
+    .removeListener(this.updateHandler), mxEvent.addListener(this.source.container, "scroll", this.updateHandler), this.source = null);
+  null != this.outline && (this.outline.removeMouseListener(this), this.outline.destroy(), this.outline = null);
+  null != this.selectionBorder && (this.selectionBorder.destroy(),
+    this.selectionBorder = null);
+  null != this.sizer && (this.sizer.destroy(), this.sizer = null)
+};
+
+function mxMultiplicity(a, b, c, d, e, f, g, h, k, l) {
+  this.source = a;
+  this.type = b;
+  this.attr = c;
+  this.value = d;
+  this.min = null != e ? e : 0;
+  this.max = null != f ? f : "n";
+  this.validNeighbors = g;
+  this.countError = mxResources.get(h) || h;
+  this.typeError = mxResources.get(k) || k;
+  this.validNeighborsAllowed = null != l ? l : !0
+}
+mxMultiplicity.prototype.type = null;
+mxMultiplicity.prototype.attr = null;
+mxMultiplicity.prototype.value = null;
+mxMultiplicity.prototype.source = null;
+mxMultiplicity.prototype.min = null;
+mxMultiplicity.prototype.max = null;
+mxMultiplicity.prototype.validNeighbors = null;
+mxMultiplicity.prototype.validNeighborsAllowed = !0;
+mxMultiplicity.prototype.countError = null;
+mxMultiplicity.prototype.typeError = null;
+mxMultiplicity.prototype.check = function(a, b, c, d, e, f) {
+  var g = "";
+  if (this.source && this.checkTerminal(a, c, b) || !this.source && this.checkTerminal(a, d, b)) {
+    if (null != this.countError && (this.source && (0 == this.max || e >= this.max) || !this.source && (0 == this.max || f >= this.max))) g += this.countError + "\n";
+    null != this.validNeighbors && (null != this.typeError && 0 < this.validNeighbors.length) && (this.checkNeighbors(a, b, c, d) || (g += this.typeError + "\n"))
+  }
+  return 0 < g.length ? g : null
+};
+mxMultiplicity.prototype.checkNeighbors = function(a, b, c, d) {
+  b = a.model.getValue(c);
+  d = a.model.getValue(d);
+  c = !this.validNeighborsAllowed;
+  for (var e = this.validNeighbors, f = 0; f < e.length; f++)
+    if (this.source && this.checkType(a, d, e[f])) {
+      c = this.validNeighborsAllowed;
+      break
+    } else if (!this.source && this.checkType(a, b, e[f])) {
+    c = this.validNeighborsAllowed;
+    break
+  }
+  return c
+};
+mxMultiplicity.prototype.checkTerminal = function(a, b, c) {
+  b = a.model.getValue(b);
+  return this.checkType(a, b, this.type, this.attr, this.value)
+};
+mxMultiplicity.prototype.checkType = function(a, b, c, d, e) {
+  return null != b ? isNaN(b.nodeType) ? b == c : mxUtils.isNode(b, c, d, e) : !1
+};
+
+function mxLayoutManager(a) {
+  this.undoHandler = mxUtils.bind(this, function(a, c) {
+    this.isEnabled() && this.beforeUndo(c.getProperty("edit"))
+  });
+  this.moveHandler = mxUtils.bind(this, function(a, c) {
+    this.isEnabled() && this.cellsMoved(c.getProperty("cells"), c.getProperty("event"))
+  });
+  this.setGraph(a)
+}
+mxLayoutManager.prototype = new mxEventSource;
+mxLayoutManager.prototype.constructor = mxLayoutManager;
+mxLayoutManager.prototype.graph = null;
+mxLayoutManager.prototype.bubbling = !0;
+mxLayoutManager.prototype.enabled = !0;
+mxLayoutManager.prototype.updateHandler = null;
+mxLayoutManager.prototype.moveHandler = null;
+mxLayoutManager.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxLayoutManager.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxLayoutManager.prototype.isBubbling = function() {
+  return this.bubbling
+};
+mxLayoutManager.prototype.setBubbling = function(a) {
+  this.bubbling = a
+};
+mxLayoutManager.prototype.getGraph = function() {
+  return this.graph
+};
+mxLayoutManager.prototype.setGraph = function(a) {
+  if (null != this.graph) {
+    var b = this.graph.getModel();
+    b.removeListener(this.undoHandler);
+    this.graph.removeListener(this.moveHandler)
+  }
+  this.graph = a;
+  null != this.graph && (b = this.graph.getModel(), b.addListener(mxEvent.BEFORE_UNDO, this.undoHandler), this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler))
+};
+mxLayoutManager.prototype.getLayout = function(a) {
+  return null
+};
+mxLayoutManager.prototype.beforeUndo = function(a) {
+  a = this.getCellsForChanges(a.changes);
+  var b = this.getGraph().getModel();
+  if (this.isBubbling())
+    for (var c = b.getParents(a); 0 < c.length;) a = a.concat(c), c = b.getParents(c);
+  this.layoutCells(mxUtils.sortCells(a, !1))
+};
+mxLayoutManager.prototype.cellsMoved = function(a, b) {
+  if (null != a && null != b)
+    for (var c = mxUtils.convertPoint(this.getGraph().container, mxEvent.getClientX(b), mxEvent.getClientY(b)), d = this.getGraph().getModel(), e = 0; e < a.length; e++) {
+      var f = this.getLayout(d.getParent(a[e]));
+      null != f && f.moveCell(a[e], c.x, c.y)
+    }
+};
+mxLayoutManager.prototype.getCellsForChanges = function(a) {
+  for (var b = [], c = {}, d = 0; d < a.length; d++) {
+    var e = a[d];
+    if (e instanceof mxRootChange) return [];
+    for (var e = this.getCellsForChange(e), f = 0; f < e.length; f++)
+      if (null != e[f]) {
+        var g = mxCellPath.create(e[f]);
+        null == c[g] && (c[g] = e[f], b.push(e[f]))
+      }
+  }
+  return b
+};
+mxLayoutManager.prototype.getCellsForChange = function(a) {
+  var b = this.getGraph().getModel();
+  return a instanceof mxChildChange ? [a.child, a.previous, b.getParent(a.child)] : a instanceof mxTerminalChange || a instanceof mxGeometryChange ? [a.cell, b.getParent(a.cell)] : []
+};
+mxLayoutManager.prototype.layoutCells = function(a) {
+  if (0 < a.length) {
+    var b = this.getGraph().getModel();
+    b.beginUpdate();
+    try {
+      for (var c = null, d = 0; d < a.length; d++) a[d] != b.getRoot() && a[d] != c && (c = a[d], this.executeLayout(this.getLayout(c), c));
+      this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, "cells", a))
+    } finally {
+      b.endUpdate()
+    }
+  }
+};
+mxLayoutManager.prototype.executeLayout = function(a, b) {
+  null != a && null != b && a.execute(b)
+};
+mxLayoutManager.prototype.destroy = function() {
+  this.setGraph(null)
+};
+
+function mxSpaceManager(a, b, c, d) {
+  this.resizeHandler = mxUtils.bind(this, function(a, b) {
+    this.isEnabled() && this.cellsResized(b.getProperty("cells"))
+  });
+  this.foldHandler = mxUtils.bind(this, function(a, b) {
+    this.isEnabled() && this.cellsResized(b.getProperty("cells"))
+  });
+  this.shiftRightwards = null != b ? b : !0;
+  this.shiftDownwards = null != c ? c : !0;
+  this.extendParents = null != d ? d : !0;
+  this.setGraph(a)
+}
+mxSpaceManager.prototype = new mxEventSource;
+mxSpaceManager.prototype.constructor = mxSpaceManager;
+mxSpaceManager.prototype.graph = null;
+mxSpaceManager.prototype.enabled = !0;
+mxSpaceManager.prototype.shiftRightwards = !0;
+mxSpaceManager.prototype.shiftDownwards = !0;
+mxSpaceManager.prototype.extendParents = !0;
+mxSpaceManager.prototype.resizeHandler = null;
+mxSpaceManager.prototype.foldHandler = null;
+mxSpaceManager.prototype.isCellIgnored = function(a) {
+  return !this.getGraph().getModel().isVertex(a)
+};
+mxSpaceManager.prototype.isCellShiftable = function(a) {
+  return this.getGraph().getModel().isVertex(a) && this.getGraph().isCellMovable(a)
+};
+mxSpaceManager.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxSpaceManager.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxSpaceManager.prototype.isShiftRightwards = function() {
+  return this.shiftRightwards
+};
+mxSpaceManager.prototype.setShiftRightwards = function(a) {
+  this.shiftRightwards = a
+};
+mxSpaceManager.prototype.isShiftDownwards = function() {
+  return this.shiftDownwards
+};
+mxSpaceManager.prototype.setShiftDownwards = function(a) {
+  this.shiftDownwards = a
+};
+mxSpaceManager.prototype.isExtendParents = function() {
+  return this.extendParents
+};
+mxSpaceManager.prototype.setExtendParents = function(a) {
+  this.extendParents = a
+};
+mxSpaceManager.prototype.getGraph = function() {
+  return this.graph
+};
+mxSpaceManager.prototype.setGraph = function(a) {
+  null != this.graph && (this.graph.removeListener(this.resizeHandler), this.graph.removeListener(this.foldHandler));
+  this.graph = a;
+  null != this.graph && (this.graph.addListener(mxEvent.RESIZE_CELLS, this.resizeHandler), this.graph.addListener(mxEvent.FOLD_CELLS, this.foldHandler))
+};
+mxSpaceManager.prototype.cellsResized = function(a) {
+  if (null != a) {
+    var b = this.graph.getModel();
+    b.beginUpdate();
+    try {
+      for (var c = 0; c < a.length; c++)
+        if (!this.isCellIgnored(a[c])) {
+          this.cellResized(a[c]);
+          break
+        }
+    } finally {
+      b.endUpdate()
+    }
+  }
+};
+mxSpaceManager.prototype.cellResized = function(a) {
+  var b = this.getGraph(),
+    c = b.getView(),
+    d = b.getModel(),
+    e = c.getState(a),
+    f = c.getState(d.getParent(a));
+  if (null != e && null != f) {
+    var g = this.getCellsToShift(e),
+      h = d.getGeometry(a);
+    if (null != g && null != h) {
+      var k = c.translate,
+        l = c.scale,
+        c = e.x - f.origin.x - k.x * l,
+        f = e.y - f.origin.y - k.y * l,
+        k = e.x + e.width,
+        m = e.y + e.height,
+        n = e.width - h.width * l + c - h.x * l,
+        p = e.height - h.height * l + f - h.y * l,
+        q = 1 - h.width * l / e.width,
+        e = 1 - h.height * l / e.height;
+      d.beginUpdate();
+      try {
+        for (h = 0; h < g.length; h++) g[h] != a && this.isCellShiftable(g[h]) &&
+          this.shiftCell(g[h], n, p, c, f, k, m, q, e, this.isExtendParents() && b.isExtendParent(g[h]))
+      } finally {
+        d.endUpdate()
+      }
+    }
+  }
+};
+mxSpaceManager.prototype.shiftCell = function(a, b, c, d, e, f, g, h, k, l) {
+  d = this.getGraph();
+  var m = d.getView().getState(a);
+  if (null != m) {
+    var n = d.getModel(),
+      p = n.getGeometry(a);
+    if (null != p) {
+      n.beginUpdate();
+      try {
+        if (this.isShiftRightwards())
+          if (m.x >= f) p = p.clone(), p.translate(-b, 0);
+          else {
+            var q = Math.max(0, m.x - x0),
+              p = p.clone();
+            p.translate(-h * q, 0)
+          }
+        if (this.isShiftDownwards())
+          if (m.y >= g) p = p.clone(), p.translate(0, -c);
+          else {
+            var s = Math.max(0, m.y - e),
+              p = p.clone();
+            p.translate(0, -k * s)
+          }
+        p != n.getGeometry(a) && (n.setGeometry(a, p), l && d.extendParent(a))
+      } finally {
+        n.endUpdate()
+      }
+    }
+  }
+};
+mxSpaceManager.prototype.getCellsToShift = function(a) {
+  var b = this.getGraph(),
+    c = b.getModel().getParent(a.cell),
+    d = this.isShiftDownwards(),
+    e = this.isShiftRightwards();
+  return b.getCellsBeyond(a.x + (d ? 0 : a.width), a.y + (d && e ? 0 : a.height), c, e, d)
+};
+mxSpaceManager.prototype.destroy = function() {
+  this.setGraph(null)
+};
+
+function mxSwimlaneManager(a, b, c, d) {
+  this.horizontal = null != b ? b : !0;
+  this.addEnabled = null != c ? c : !0;
+  this.resizeEnabled = null != d ? d : !0;
+  this.addHandler = mxUtils.bind(this, function(a, b) {
+    this.isEnabled() && this.isAddEnabled() && this.cellsAdded(b.getProperty("cells"))
+  });
+  this.resizeHandler = mxUtils.bind(this, function(a, b) {
+    this.isEnabled() && this.isResizeEnabled() && this.cellsResized(b.getProperty("cells"))
+  });
+  this.setGraph(a)
+}
+mxSwimlaneManager.prototype = new mxEventSource;
+mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;
+mxSwimlaneManager.prototype.graph = null;
+mxSwimlaneManager.prototype.enabled = !0;
+mxSwimlaneManager.prototype.horizontal = !0;
+mxSwimlaneManager.prototype.addEnabled = !0;
+mxSwimlaneManager.prototype.resizeEnabled = !0;
+mxSwimlaneManager.prototype.addHandler = null;
+mxSwimlaneManager.prototype.resizeHandler = null;
+mxSwimlaneManager.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxSwimlaneManager.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxSwimlaneManager.prototype.isHorizontal = function() {
+  return this.horizontal
+};
+mxSwimlaneManager.prototype.setHorizontal = function(a) {
+  this.horizontal = a
+};
+mxSwimlaneManager.prototype.isAddEnabled = function() {
+  return this.addEnabled
+};
+mxSwimlaneManager.prototype.setAddEnabled = function(a) {
+  this.addEnabled = a
+};
+mxSwimlaneManager.prototype.isResizeEnabled = function() {
+  return this.resizeEnabled
+};
+mxSwimlaneManager.prototype.setResizeEnabled = function(a) {
+  this.resizeEnabled = a
+};
+mxSwimlaneManager.prototype.getGraph = function() {
+  return this.graph
+};
+mxSwimlaneManager.prototype.setGraph = function(a) {
+  null != this.graph && (this.graph.removeListener(this.addHandler), this.graph.removeListener(this.resizeHandler));
+  this.graph = a;
+  null != this.graph && (this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler), this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler))
+};
+mxSwimlaneManager.prototype.isSwimlaneIgnored = function(a) {
+  return !this.getGraph().isSwimlane(a)
+};
+mxSwimlaneManager.prototype.isCellHorizontal = function(a) {
+  if (this.graph.isSwimlane(a)) {
+    var b = this.graph.view.getState(a);
+    a = null != b ? b.style : this.graph.getCellStyle(a);
+    return 1 == mxUtils.getValue(a, mxConstants.STYLE_HORIZONTAL, 1)
+  }
+  return !this.isHorizontal()
+};
+mxSwimlaneManager.prototype.cellsAdded = function(a) {
+  if (null != a) {
+    var b = this.getGraph().getModel();
+    b.beginUpdate();
+    try {
+      for (var c = 0; c < a.length; c++) this.isSwimlaneIgnored(a[c]) || this.swimlaneAdded(a[c])
+    } finally {
+      b.endUpdate()
+    }
+  }
+};
+mxSwimlaneManager.prototype.swimlaneAdded = function(a) {
+  for (var b = this.getGraph().getModel(), c = b.getParent(a), d = b.getChildCount(c), e = null, f = 0; f < d; f++) {
+    var g = b.getChildAt(c, f);
+    if (g != a && !this.isSwimlaneIgnored(g) && (e = b.getGeometry(g), null != e)) break
+  }
+  null != e && this.resizeSwimlane(a, e.width, e.height)
+};
+mxSwimlaneManager.prototype.cellsResized = function(a) {
+  if (null != a) {
+    var b = this.getGraph().getModel();
+    b.beginUpdate();
+    try {
+      for (var c = 0; c < a.length; c++)
+        if (!this.isSwimlaneIgnored(a[c])) {
+          var d = b.getGeometry(a[c]);
+          if (null != d) {
+            for (var e = new mxRectangle(0, 0, d.width, d.height), f = a[c], g = f; null != g;) {
+              var f = g,
+                g = b.getParent(g),
+                h = this.graph.isSwimlane(g) ? this.graph.getStartSize(g) : new mxRectangle;
+              e.width += h.width;
+              e.height += h.height
+            }
+            this.resizeSwimlane(f, e.width, e.height)
+          }
+        }
+    } finally {
+      b.endUpdate()
+    }
+  }
+};
+mxSwimlaneManager.prototype.resizeSwimlane = function(a, b, c) {
+  var d = this.getGraph().getModel();
+  d.beginUpdate();
+  try {
+    if (!this.isSwimlaneIgnored(a)) {
+      var e = d.getGeometry(a);
+      if (null != e) {
+        var f = this.isCellHorizontal(a);
+        if (f && e.height != c || !f && e.width != b) e = e.clone(), f ? e.height = c : e.width = b, d.setGeometry(a, e)
+      }
+    }
+    var g = this.graph.isSwimlane(a) ? this.graph.getStartSize(a) : new mxRectangle;
+    b -= g.width;
+    c -= g.height;
+    for (var h = d.getChildCount(a), e = 0; e < h; e++) {
+      var k = d.getChildAt(a, e);
+      this.resizeSwimlane(k, b, c)
+    }
+  } finally {
+    d.endUpdate()
+  }
+};
+mxSwimlaneManager.prototype.destroy = function() {
+  this.setGraph(null)
+};
+
+function mxTemporaryCellStates(a, b, c) {
+  this.view = a;
+  b = null != b ? b : 1;
+  this.oldBounds = a.getGraphBounds();
+  this.oldStates = a.getStates();
+  this.oldScale = a.getScale();
+  a.setStates(new mxDictionary);
+  a.setScale(b);
+  if (null != c) {
+    b = a.createState(new mxCell);
+    for (var d = 0; d < c.length; d++) a.validateBounds(b, c[d]);
+    for (var e = null, d = 0; d < c.length; d++) {
+      var f = a.validatePoints(b, c[d]);
+      null == e ? e = f : e.add(f)
+    }
+    null == e && (e = new mxRectangle);
+    a.setGraphBounds(e)
+  }
+}
+mxTemporaryCellStates.prototype.view = null;
+mxTemporaryCellStates.prototype.oldStates = null;
+mxTemporaryCellStates.prototype.oldBounds = null;
+mxTemporaryCellStates.prototype.oldScale = null;
+mxTemporaryCellStates.prototype.destroy = function() {
+  this.view.setScale(this.oldScale);
+  this.view.setStates(this.oldStates);
+  this.view.setGraphBounds(this.oldBounds)
+};
+
+function mxCellStatePreview(a) {
+  this.graph = a;
+  this.deltas = {}
+}
+mxCellStatePreview.prototype.graph = null;
+mxCellStatePreview.prototype.deltas = null;
+mxCellStatePreview.prototype.count = 0;
+mxCellStatePreview.prototype.isEmpty = function() {
+  return 0 == this.count
+};
+mxCellStatePreview.prototype.moveState = function(a, b, c, d, e) {
+  d = null != d ? d : !0;
+  e = null != e ? e : !0;
+  var f = mxCellPath.create(a.cell),
+    g = this.deltas[f];
+  null == g ? (g = new mxPoint(b, c), this.deltas[f] = g, this.count++) : d ? (g.X += b, g.Y += c) : (g.X = b, g.Y = c);
+  e && this.addEdges(a);
+  return g
+};
+mxCellStatePreview.prototype.show = function(a) {
+  var b = this.graph.getModel(),
+    c = b.getRoot(),
+    d;
+  for (d in this.deltas) {
+    var e = mxCellPath.resolve(c, d),
+      f = this.graph.view.getState(e),
+      g = this.deltas[d],
+      e = this.graph.view.getState(b.getParent(e));
+    this.translateState(e, f, g.x, g.y)
+  }
+  for (d in this.deltas) e = mxCellPath.resolve(c, d), f = this.graph.view.getState(e), g = this.deltas[d], e = this.graph.view.getState(b.getParent(e)), this.revalidateState(e, f, g.x, g.y, a)
+};
+mxCellStatePreview.prototype.translateState = function(a, b, c, d) {
+  if (null != b) {
+    var e = this.graph.getModel();
+    if (e.isVertex(b.cell)) {
+      b.invalid = !0;
+      this.graph.view.validateBounds(a, b.cell);
+      a = e.getGeometry(b.cell);
+      var f = mxCellPath.create(b.cell);
+      if ((0 != c || 0 != d) && null != a && (!a.relative || null != this.deltas[f])) b.x += c, b.y += d
+    }
+    a = e.getChildCount(b.cell);
+    for (f = 0; f < a; f++) this.translateState(b, this.graph.view.getState(e.getChildAt(b.cell, f)), c, d)
+  }
+};
+mxCellStatePreview.prototype.revalidateState = function(a, b, c, d, e) {
+  if (null != b) {
+    b.invalid = !0;
+    this.graph.view.validatePoints(a, b.cell);
+    var f = mxCellPath.create(b.cell),
+      g = this.graph.getModel(),
+      h = this.graph.getCellGeometry(b.cell);
+    if ((0 != c || 0 != d) && null != h && h.relative && g.isVertex(b.cell) && (null == a || g.isVertex(a.cell) || null != this.deltas[f])) b.x += c, b.y += d, this.graph.cellRenderer.redraw(b);
+    null != e && e(b);
+    a = g.getChildCount(b.cell);
+    for (f = 0; f < a; f++) this.revalidateState(b, this.graph.view.getState(g.getChildAt(b.cell,
+      f)), c, d, e)
+  }
+};
+mxCellStatePreview.prototype.addEdges = function(a) {
+  for (var b = this.graph.getModel(), c = b.getEdgeCount(a.cell), d = 0; d < c; d++) {
+    var e = this.graph.view.getState(b.getEdgeAt(a.cell, d));
+    null != e && this.moveState(e, 0, 0)
+  }
+};
+
+function mxConnectionConstraint(a, b) {
+  this.point = a;
+  this.perimeter = null != b ? b : !0
+}
+mxConnectionConstraint.prototype.point = null;
+mxConnectionConstraint.prototype.perimeter = null;
+
+function mxGraphHandler(a) {
+  this.graph = a;
+  this.graph.addMouseListener(this);
+  this.panHandler = mxUtils.bind(this, function() {
+    this.updatePreviewShape()
+  });
+  this.graph.addListener(mxEvent.PAN, this.panHandler)
+}
+mxGraphHandler.prototype.graph = null;
+mxGraphHandler.prototype.maxCells = mxClient.IS_IE ? 20 : 50;
+mxGraphHandler.prototype.enabled = !0;
+mxGraphHandler.prototype.highlightEnabled = !0;
+mxGraphHandler.prototype.cloneEnabled = !0;
+mxGraphHandler.prototype.moveEnabled = !0;
+mxGraphHandler.prototype.guidesEnabled = !1;
+mxGraphHandler.prototype.guide = null;
+mxGraphHandler.prototype.currentDx = null;
+mxGraphHandler.prototype.currentDy = null;
+mxGraphHandler.prototype.updateCursor = !0;
+mxGraphHandler.prototype.selectEnabled = !0;
+mxGraphHandler.prototype.removeCellsFromParent = !0;
+mxGraphHandler.prototype.connectOnDrop = !1;
+mxGraphHandler.prototype.scrollOnMove = !0;
+mxGraphHandler.prototype.minimumSize = 6;
+mxGraphHandler.prototype.previewColor = "black";
+mxGraphHandler.prototype.htmlPreview = !1;
+mxGraphHandler.prototype.shape = null;
+mxGraphHandler.prototype.scaleGrid = !1;
+mxGraphHandler.prototype.rotationEnabled = !0;
+mxGraphHandler.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxGraphHandler.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxGraphHandler.prototype.isCloneEnabled = function() {
+  return this.cloneEnabled
+};
+mxGraphHandler.prototype.setCloneEnabled = function(a) {
+  this.cloneEnabled = a
+};
+mxGraphHandler.prototype.isMoveEnabled = function() {
+  return this.moveEnabled
+};
+mxGraphHandler.prototype.setMoveEnabled = function(a) {
+  this.moveEnabled = a
+};
+mxGraphHandler.prototype.isSelectEnabled = function() {
+  return this.selectEnabled
+};
+mxGraphHandler.prototype.setSelectEnabled = function(a) {
+  this.selectEnabled = a
+};
+mxGraphHandler.prototype.isRemoveCellsFromParent = function() {
+  return this.removeCellsFromParent
+};
+mxGraphHandler.prototype.setRemoveCellsFromParent = function(a) {
+  this.removeCellsFromParent = a
+};
+mxGraphHandler.prototype.getInitialCellForEvent = function(a) {
+  return a.getCell()
+};
+mxGraphHandler.prototype.isDelayedSelection = function(a) {
+  return this.graph.isCellSelected(a)
+};
+mxGraphHandler.prototype.mouseDown = function(a, b) {
+  if (!b.isConsumed() && this.isEnabled() && this.graph.isEnabled() && !this.graph.isForceMarqueeEvent(b.getEvent()) && null != b.getState()) {
+    var c = this.getInitialCellForEvent(b);
+    this.cell = null;
+    this.delayedSelection = this.isDelayedSelection(c);
+    this.isSelectEnabled() && !this.delayedSelection && this.graph.selectCellForEvent(c, b.getEvent());
+    if (this.isMoveEnabled()) {
+      var d = this.graph.model,
+        e = d.getGeometry(c);
+      this.graph.isCellMovable(c) && (!d.isEdge(c) || 1 < this.graph.getSelectionCount() ||
+        null != e.points && 0 < e.points.length || null == d.getTerminal(c, !0) || null == d.getTerminal(c, !1) || this.graph.allowDanglingEdges || this.graph.isCloneEvent(b.getEvent()) && this.graph
+        .isCellsCloneable()) && this.start(c, b.getX(), b.getY());
+      this.cellWasClicked = !0;
+      !mxClient.IS_SF && !mxClient.IS_GC || "SELECT" != b.getSource().nodeName ? b.consume() : mxClient.IS_SF && "SELECT" == b.getSource().nodeName && (this.cellWasClicked = !1, this.first = null)
+    }
+  }
+};
+mxGraphHandler.prototype.getGuideStates = function() {
+  var a = this.graph.getDefaultParent(),
+    b = this.graph.getModel(),
+    c = mxUtils.bind(this, function(a) {
+      return null != this.graph.view.getState(a) && b.isVertex(a) && null != b.getGeometry(a) && !b.getGeometry(a).relative
+    });
+  return this.graph.view.getCellStates(b.filterDescendants(c, a))
+};
+mxGraphHandler.prototype.getCells = function(a) {
+  return !this.delayedSelection && this.graph.isCellMovable(a) ? [a] : this.graph.getMovableCells(this.graph.getSelectionCells())
+};
+mxGraphHandler.prototype.getPreviewBounds = function(a) {
+  a = this.getBoundingBox(a);
+  null != a && (a.grow(-1, -1), a.width < this.minimumSize && (a.x -= (this.minimumSize - a.width) / 2, a.width = this.minimumSize), a.height < this.minimumSize && (a.y -= (this.minimumSize - a.height) /
+    2, a.height = this.minimumSize));
+  return a
+};
+mxGraphHandler.prototype.getBoundingBox = function(a) {
+  var b = null;
+  if (null != a && 0 < a.length)
+    for (var c = this.graph.getModel(), d = 0; d < a.length; d++)
+      if (c.isVertex(a[d]) || c.isEdge(a[d])) {
+        var e = this.graph.view.getState(a[d]);
+        if (null != e) {
+          var f = e;
+          c.isVertex(a[d]) && (null != e.shape && null != e.shape.boundingBox) && (f = e.shape.boundingBox);
+          null == b ? b = new mxRectangle(f.x, f.y, f.width, f.height) : b.add(f)
+        }
+      }
+  return b
+};
+mxGraphHandler.prototype.createPreviewShape = function(a) {
+  a = new mxRectangleShape(a, null, this.previewColor);
+  a.isDashed = !0;
+  this.htmlPreview ? (a.dialect = mxConstants.DIALECT_STRICTHTML, a.init(this.graph.container)) : (a.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG,
+    a.init(this.graph.getView().getOverlayPane()), a.pointerEvents = !1);
+  return a
+};
+mxGraphHandler.prototype.start = function(a, b, c) {
+  this.cell = a;
+  this.first = mxUtils.convertPoint(this.graph.container, b, c);
+  this.cells = this.getCells(this.cell);
+  this.bounds = this.graph.getView().getBounds(this.cells);
+  this.pBounds = this.getPreviewBounds(this.cells);
+  this.guidesEnabled && (this.guide = new mxGuide(this.graph, this.getGuideStates()))
+};
+mxGraphHandler.prototype.useGuidesForEvent = function(a) {
+  return null != this.guide ? this.guide.isEnabledForEvent(a.getEvent()) : !0
+};
+mxGraphHandler.prototype.snap = function(a) {
+  var b = this.scaleGrid ? this.graph.view.scale : 1;
+  a.x = this.graph.snap(a.x / b) * b;
+  a.y = this.graph.snap(a.y / b) * b;
+  return a
+};
+mxGraphHandler.prototype.mouseMove = function(a, b) {
+  var c = this.graph;
+  if (!b.isConsumed() && c.isMouseDown && null != this.cell && null != this.first && null != this.bounds) {
+    var d = mxUtils.convertPoint(c.container, b.getX(), b.getY()),
+      e = d.x - this.first.x,
+      f = d.y - this.first.y,
+      d = c.tolerance;
+    if (null != this.shape || Math.abs(e) > d || Math.abs(f) > d) {
+      null == this.highlight && (this.highlight = new mxCellHighlight(this.graph, mxConstants.DROP_TARGET_COLOR, 3));
+      null == this.shape && (this.shape = this.createPreviewShape(this.bounds));
+      var g = c.isGridEnabledEvent(b.getEvent()),
+        d = !0;
+      if (null != this.guide && this.useGuidesForEvent(b)) f = this.guide.move(this.bounds, new mxPoint(e, f), g), d = !1, e = f.x, f = f.y;
+      else if (g) var h = c.getView().translate,
+        k = c.getView().scale,
+        g = this.bounds.x - (c.snap(this.bounds.x / k - h.x) + h.x) * k,
+        h = this.bounds.y - (c.snap(this.bounds.y / k - h.y) + h.y) * k,
+        f = this.snap(new mxPoint(e, f)),
+        e = f.x - g,
+        f = f.y - h;
+      null != this.guide && d && this.guide.hide();
+      c.isConstrainedEvent(b.getEvent()) && (Math.abs(e) > Math.abs(f) ? f = 0 : e = 0);
+      this.currentDx = e;
+      this.currentDy = f;
+      this.updatePreviewShape();
+      d = null;
+      f = b.getCell();
+      c.isDropEnabled() && this.highlightEnabled && (d = c.getDropTarget(this.cells, b.getEvent(), f));
+      g = d;
+      for (h = c.getModel(); null != g && g != this.cells[0];) g = h.getParent(g);
+      var k = c.isCloneEvent(b.getEvent()) && c.isCellsCloneable() && this.isCloneEnabled(),
+        e = c.getView().getState(d),
+        l = !1;
+      null != e && null == g && (h.getParent(this.cell) != d || k) ? (this.target != d && (this.target = d, this.setHighlightColor(mxConstants.DROP_TARGET_COLOR)), l = !0) : (this.target = null,
+        this.connectOnDrop && (null != f && 1 == this.cells.length && c.getModel().isVertex(f) &&
+          c.isCellConnectable(f)) && (e = c.getView().getState(f), null != e && (c = null == c.getEdgeValidationError(null, this.cell, f) ? mxConstants.VALID_COLOR : mxConstants.INVALID_CONNECT_TARGET_COLOR,
+          this.setHighlightColor(c), l = !0)));
+      null != e && l ? this.highlight.highlight(e) : this.highlight.hide()
+    }
+    b.consume();
+    mxEvent.consume(b.getEvent())
+  } else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor && !b.isConsumed() && null != b.getState() && !c.isMouseDown) e = c.getCursorForCell(b.getCell()), null == e && (c.isEnabled() &&
+      c.isCellMovable(b.getCell())) &&
+    (e = c.getModel().isEdge(b.getCell()) ? mxConstants.CURSOR_MOVABLE_EDGE : mxConstants.CURSOR_MOVABLE_VERTEX), b.getState().setCursor(e), b.consume()
+};
+mxGraphHandler.prototype.updatePreviewShape = function() {
+  null != this.shape && (this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx - this.graph.panDx), Math.round(this.pBounds.y + this.currentDy - this.graph.panDy), this.pBounds
+    .width, this.pBounds.height), this.shape.redraw())
+};
+mxGraphHandler.prototype.setHighlightColor = function(a) {
+  null != this.highlight && this.highlight.setHighlightColor(a)
+};
+mxGraphHandler.prototype.mouseUp = function(a, b) {
+  if (!b.isConsumed()) {
+    var c = this.graph;
+    if (null != this.cell && null != this.first && null != this.shape && null != this.currentDx && null != this.currentDy) {
+      var d = c.getView().scale,
+        e = c.isCloneEvent(b.getEvent()) && c.isCellsCloneable() && this.isCloneEnabled(),
+        f = this.currentDx / d,
+        d = this.currentDy / d,
+        g = b.getCell();
+      this.connectOnDrop && null == this.target && null != g && c.getModel().isVertex(g) && c.isCellConnectable(g) && c.isEdgeValid(null, this.cell, g) ? c.connectionHandler.connect(this.cell,
+        g, b.getEvent()) : (g = this.target, c.isSplitEnabled() && c.isSplitTarget(g, this.cells, b.getEvent()) ? c.splitEdge(g, this.cells, null, f, d) : this.moveCells(this.cells, f, d, e, this
+        .target, b.getEvent()))
+    } else this.isSelectEnabled() && (this.delayedSelection && null != this.cell) && this.selectDelayed(b)
+  }
+  this.cellWasClicked && b.consume();
+  this.reset()
+};
+mxGraphHandler.prototype.selectDelayed = function(a) {
+  this.graph.selectCellForEvent(this.cell, a.getEvent())
+};
+mxGraphHandler.prototype.reset = function() {
+  this.destroyShapes();
+  this.delayedSelection = this.cellWasClicked = !1;
+  this.target = this.cell = this.first = this.guides = this.currentDy = this.currentDx = null
+};
+mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(a, b, c) {
+  if (this.graph.getModel().isVertex(a)) {
+    a = this.graph.getView().getState(a);
+    c = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(c), mxEvent.getClientY(c));
+    var d = mxUtils.toRadians(mxUtils.getValue(a.style, mxConstants.STYLE_ROTATION) || 0);
+    if (0 != d) {
+      b = Math.cos(-d);
+      var d = Math.sin(-d),
+        e = new mxPoint(a.getCenterX(), a.getCenterY());
+      c = mxUtils.getRotatedPoint(c, b, d, e)
+    }
+    return null != a && !mxUtils.contains(a, c.x, c.y)
+  }
+  return !1
+};
+mxGraphHandler.prototype.moveCells = function(a, b, c, d, e, f) {
+  d && (a = this.graph.getCloneableCells(a));
+  null == e && (this.isRemoveCellsFromParent() && this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), a, f)) && (e = this.graph.getDefaultParent());
+  a = this.graph.moveCells(a, b - this.graph.panDx / this.graph.view.scale, c - this.graph.panDy / this.graph.view.scale, d, e, f);
+  this.isSelectEnabled() && this.scrollOnMove && this.graph.scrollCellToVisible(a[0]);
+  d && this.graph.setSelectionCells(a)
+};
+mxGraphHandler.prototype.destroyShapes = function() {
+  null != this.shape && (this.shape.destroy(), this.shape = null);
+  null != this.guide && (this.guide.destroy(), this.guide = null);
+  null != this.highlight && (this.highlight.destroy(), this.highlight = null)
+};
+mxGraphHandler.prototype.destroy = function() {
+  this.graph.removeMouseListener(this);
+  this.graph.removeListener(this.panHandler);
+  this.destroyShapes()
+};
+
+function mxPanningHandler(a, b) {
+  null != a && (this.graph = a, this.factoryMethod = b, this.graph.addMouseListener(this), this.init())
+}
+mxPanningHandler.prototype = new mxPopupMenu;
+mxPanningHandler.prototype.constructor = mxPanningHandler;
+mxPanningHandler.prototype.graph = null;
+mxPanningHandler.prototype.triggerX = null;
+mxPanningHandler.prototype.triggerY = null;
+mxPanningHandler.prototype.usePopupTrigger = !0;
+mxPanningHandler.prototype.useLeftButtonForPanning = !1;
+mxPanningHandler.prototype.selectOnPopup = !0;
+mxPanningHandler.prototype.clearSelectionOnBackground = !0;
+mxPanningHandler.prototype.ignoreCell = !1;
+mxPanningHandler.prototype.previewEnabled = !0;
+mxPanningHandler.prototype.useGrid = !1;
+mxPanningHandler.prototype.panningEnabled = !0;
+mxPanningHandler.prototype.isPanningEnabled = function() {
+  return this.panningEnabled
+};
+mxPanningHandler.prototype.setPanningEnabled = function(a) {
+  this.panningEnabled = a
+};
+mxPanningHandler.prototype.init = function() {
+  mxPopupMenu.prototype.init.apply(this);
+  mxEvent.addListener(this.div, mxClient.IS_TOUCH ? "touchmove" : "mousemove", mxUtils.bind(this, function(a) {
+    this.graph.tooltipHandler.hide()
+  }))
+};
+mxPanningHandler.prototype.isPanningTrigger = function(a) {
+  var b = a.getEvent();
+  return this.useLeftButtonForPanning && (this.ignoreCell || null == a.getState()) && mxEvent.isLeftMouseButton(b) || mxEvent.isControlDown(b) && mxEvent.isShiftDown(b) || this.usePopupTrigger &&
+    mxEvent.isPopupTrigger(b)
+};
+mxPanningHandler.prototype.mouseDown = function(a, b) {
+  if (!b.isConsumed() && this.isEnabled()) {
+    this.hideMenu();
+    this.dx0 = -this.graph.container.scrollLeft;
+    this.dy0 = -this.graph.container.scrollTop;
+    var c = mxUtils.convertPoint(this.graph.container, b.getX(), b.getY());
+    this.triggerX = c.x;
+    this.triggerY = c.y;
+    this.popupTrigger = this.isPopupTrigger(b);
+    this.panningTrigger = this.isPanningEnabled() && this.isPanningTrigger(b);
+    this.startX = b.getX();
+    this.startY = b.getY();
+    this.panningTrigger && this.consumePanningTrigger(b)
+  }
+};
+mxPanningHandler.prototype.consumePanningTrigger = function(a) {
+  a.consume()
+};
+mxPanningHandler.prototype.mouseMove = function(a, b) {
+  var c = b.getX() - this.startX,
+    d = b.getY() - this.startY;
+  if (this.active) this.previewEnabled && (this.useGrid && (c = this.graph.snap(c), d = this.graph.snap(d)), this.graph.panGraph(c + this.dx0, d + this.dy0)), this.fireEvent(new mxEventObject(
+    mxEvent.PAN, "event", b)), b.consume();
+  else if (this.panningTrigger) {
+    var e = this.active;
+    this.active = Math.abs(c) > this.graph.tolerance || Math.abs(d) > this.graph.tolerance;
+    !e && this.active && this.fireEvent(new mxEventObject(mxEvent.PAN_START,
+      "event", b))
+  }
+};
+mxPanningHandler.prototype.mouseUp = function(a, b) {
+  var c = Math.abs(b.getX() - this.startX),
+    d = Math.abs(b.getY() - this.startY);
+  if (this.active) {
+    if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container)) {
+      c = b.getX() - this.startX;
+      d = b.getY() - this.startY;
+      this.useGrid && (c = this.graph.snap(c), d = this.graph.snap(d));
+      var e = this.graph.getView().scale,
+        f = this.graph.getView().translate;
+      this.graph.panGraph(0, 0);
+      this.panGraph(f.x + c / e, f.y + d / e)
+    }
+    this.active = !1;
+    this.fireEvent(new mxEventObject(mxEvent.PAN_END, "event",
+      b));
+    b.consume()
+  } else this.popupTrigger && (c < this.graph.tolerance && d < this.graph.tolerance) && (c = this.getCellForPopupEvent(b), this.graph.isEnabled() && this.selectOnPopup && null != c && !this.graph.isCellSelected(
+    c) ? this.graph.setSelectionCell(c) : this.clearSelectionOnBackground && null == c && this.graph.clearSelection(), this.graph.tooltipHandler.hide(), d = mxUtils.getScrollOrigin(), d = new mxPoint(
+    b.getX() + d.x, b.getY() + d.y), this.popup(d.x + 1, d.y + 1, c, b.getEvent()), b.consume());
+  this.popupTrigger = this.panningTrigger = !1
+};
+mxPanningHandler.prototype.getCellForPopupEvent = function(a) {
+  return a.getCell()
+};
+mxPanningHandler.prototype.panGraph = function(a, b) {
+  this.graph.getView().setTranslate(a, b)
+};
+mxPanningHandler.prototype.destroy = function() {
+  this.graph.removeMouseListener(this);
+  mxPopupMenu.prototype.destroy.apply(this)
+};
+
+function mxCellMarker(a, b, c, d) {
+  mxEventSource.call(this);
+  null != a && (this.graph = a, this.validColor = null != b ? b : mxConstants.DEFAULT_VALID_COLOR, this.invalidColor = null != b ? c : mxConstants.DEFAULT_INVALID_COLOR, this.hotspot = null != d ? d :
+    mxConstants.DEFAULT_HOTSPOT, this.highlight = new mxCellHighlight(a))
+}
+mxUtils.extend(mxCellMarker, mxEventSource);
+mxCellMarker.prototype.graph = null;
+mxCellMarker.prototype.enabled = !0;
+mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT;
+mxCellMarker.prototype.hotspotEnabled = !1;
+mxCellMarker.prototype.validColor = null;
+mxCellMarker.prototype.invalidColor = null;
+mxCellMarker.prototype.currentColor = null;
+mxCellMarker.prototype.validState = null;
+mxCellMarker.prototype.markedState = null;
+mxCellMarker.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxCellMarker.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxCellMarker.prototype.setHotspot = function(a) {
+  this.hotspot = a
+};
+mxCellMarker.prototype.getHotspot = function() {
+  return this.hotspot
+};
+mxCellMarker.prototype.setHotspotEnabled = function(a) {
+  this.hotspotEnabled = a
+};
+mxCellMarker.prototype.isHotspotEnabled = function() {
+  return this.hotspotEnabled
+};
+mxCellMarker.prototype.hasValidState = function() {
+  return null != this.validState
+};
+mxCellMarker.prototype.getValidState = function() {
+  return this.validState
+};
+mxCellMarker.prototype.getMarkedState = function() {
+  return this.markedState
+};
+mxCellMarker.prototype.reset = function() {
+  this.validState = null;
+  null != this.markedState && (this.markedState = null, this.unmark())
+};
+mxCellMarker.prototype.process = function(a) {
+  var b = null;
+  if (this.isEnabled()) {
+    var b = this.getState(a),
+      c = null != b ? this.isValidState(b) : !1;
+    a = this.getMarkerColor(a.getEvent(), b, c);
+    this.validState = c ? b : null;
+    if (b != this.markedState || a != this.currentColor) this.currentColor = a, null != b && null != this.currentColor ? (this.markedState = b, this.mark()) : null != this.markedState && (this.markedState =
+      null, this.unmark())
+  }
+  return b
+};
+mxCellMarker.prototype.markCell = function(a, b) {
+  var c = this.graph.getView().getState(a);
+  null != c && (this.currentColor = null != b ? b : this.validColor, this.markedState = c, this.mark())
+};
+mxCellMarker.prototype.mark = function() {
+  this.highlight.setHighlightColor(this.currentColor);
+  this.highlight.highlight(this.markedState);
+  this.fireEvent(new mxEventObject(mxEvent.MARK, "state", this.markedState))
+};
+mxCellMarker.prototype.unmark = function() {
+  this.mark()
+};
+mxCellMarker.prototype.isValidState = function(a) {
+  return !0
+};
+mxCellMarker.prototype.getMarkerColor = function(a, b, c) {
+  return c ? this.validColor : this.invalidColor
+};
+mxCellMarker.prototype.getState = function(a) {
+  var b = this.graph.getView();
+  cell = this.getCell(a);
+  b = this.getStateToMark(b.getState(cell));
+  return null != b && this.intersects(b, a) ? b : null
+};
+mxCellMarker.prototype.getCell = function(a) {
+  return a.getCell()
+};
+mxCellMarker.prototype.getStateToMark = function(a) {
+  return a
+};
+mxCellMarker.prototype.intersects = function(a, b) {
+  return this.hotspotEnabled ? mxUtils.intersectsHotspot(a, b.getGraphX(), b.getGraphY(), this.hotspot, mxConstants.MIN_HOTSPOT_SIZE, mxConstants.MAX_HOTSPOT_SIZE) : !0
+};
+mxCellMarker.prototype.destroy = function() {
+  this.graph.getView().removeListener(this.resetHandler);
+  this.graph.getModel().removeListener(this.resetHandler);
+  this.highlight.destroy()
+};
+
+function mxSelectionCellsHandler(a) {
+  mxEventSource.call(this);
+  this.graph = a;
+  this.handlers = new mxDictionary;
+  this.graph.addMouseListener(this);
+  this.refreshHandler = mxUtils.bind(this, function(a, c) {
+    this.isEnabled() && this.refresh()
+  });
+  this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+  this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+  this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);
+  this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);
+  this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);
+  this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);
+  this.graph.getView().addListener(mxEvent.UP, this.refreshHandler)
+}
+mxUtils.extend(mxSelectionCellsHandler, mxEventSource);
+mxSelectionCellsHandler.prototype.graph = null;
+mxSelectionCellsHandler.prototype.enabled = !0;
+mxSelectionCellsHandler.prototype.refreshHandler = null;
+mxSelectionCellsHandler.prototype.maxHandlers = 100;
+mxSelectionCellsHandler.prototype.handlers = null;
+mxSelectionCellsHandler.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxSelectionCellsHandler.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxSelectionCellsHandler.prototype.getHandler = function(a) {
+  return this.handlers.get(a)
+};
+mxSelectionCellsHandler.prototype.reset = function() {
+  this.handlers.visit(function(a, b) {
+    b.reset.apply(b)
+  })
+};
+mxSelectionCellsHandler.prototype.refresh = function() {
+  var a = this.handlers;
+  this.handlers = new mxDictionary;
+  for (var b = this.graph.getSelectionCells(), c = 0; c < b.length; c++) {
+    var d = this.graph.view.getState(b[c]);
+    if (null != d) {
+      var e = a.remove(b[c]);
+      null != e && (e.state != d ? (e.destroy(), e = null) : e.redraw());
+      null == e && (e = this.graph.createHandler(d), this.fireEvent(new mxEventObject(mxEvent.ADD, "state", d)));
+      null != e && this.handlers.put(b[c], e)
+    }
+  }
+  a.visit(mxUtils.bind(this, function(a, b) {
+    this.fireEvent(new mxEventObject(mxEvent.REMOVE,
+      "state", b.state));
+    b.destroy()
+  }))
+};
+mxSelectionCellsHandler.prototype.mouseDown = function(a, b) {
+  if (this.graph.isEnabled() && this.isEnabled()) {
+    var c = [a, b];
+    this.handlers.visit(function(a, b) {
+      b.mouseDown.apply(b, c)
+    })
+  }
+};
+mxSelectionCellsHandler.prototype.mouseMove = function(a, b) {
+  if (this.graph.isEnabled() && this.isEnabled()) {
+    var c = [a, b];
+    this.handlers.visit(function(a, b) {
+      b.mouseMove.apply(b, c)
+    })
+  }
+};
+mxSelectionCellsHandler.prototype.mouseUp = function(a, b) {
+  if (this.graph.isEnabled() && this.isEnabled()) {
+    var c = [a, b];
+    this.handlers.visit(function(a, b) {
+      b.mouseUp.apply(b, c)
+    })
+  }
+};
+mxSelectionCellsHandler.prototype.destroy = function() {
+  this.graph.removeMouseListener(this);
+  null != this.refreshHandler && (this.graph.getSelectionModel().removeListener(this.refreshHandler), this.graph.getModel().removeListener(this.refreshHandler), this.graph.getView().removeListener(
+    this.refreshHandler), this.refreshHandler = null)
+};
+
+function mxConnectionHandler(a, b) {
+  mxEventSource.call(this);
+  null != a && (this.graph = a, this.factoryMethod = b, this.init())
+}
+mxUtils.extend(mxConnectionHandler, mxEventSource);
+mxConnectionHandler.prototype.graph = null;
+mxConnectionHandler.prototype.factoryMethod = !0;
+mxConnectionHandler.prototype.moveIconFront = !1;
+mxConnectionHandler.prototype.moveIconBack = !1;
+mxConnectionHandler.prototype.connectImage = null;
+mxConnectionHandler.prototype.targetConnectImage = !1;
+mxConnectionHandler.prototype.enabled = !0;
+mxConnectionHandler.prototype.select = !0;
+mxConnectionHandler.prototype.createTarget = !1;
+mxConnectionHandler.prototype.marker = null;
+mxConnectionHandler.prototype.constraintHandler = null;
+mxConnectionHandler.prototype.error = null;
+mxConnectionHandler.prototype.waypointsEnabled = !1;
+mxConnectionHandler.prototype.tapAndHoldEnabled = !0;
+mxConnectionHandler.prototype.tapAndHoldDelay = 500;
+mxConnectionHandler.prototype.tapAndHoldInProgress = !1;
+mxConnectionHandler.prototype.tapAndHoldValid = !1;
+mxConnectionHandler.prototype.tapAndHoldTolerance = 4;
+mxConnectionHandler.prototype.initialTouchX = 0;
+mxConnectionHandler.prototype.initialTouchY = 0;
+mxConnectionHandler.prototype.ignoreMouseDown = !1;
+mxConnectionHandler.prototype.first = null;
+mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);
+mxConnectionHandler.prototype.edgeState = null;
+mxConnectionHandler.prototype.changeHandler = null;
+mxConnectionHandler.prototype.drillHandler = null;
+mxConnectionHandler.prototype.mouseDownCounter = 0;
+mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;
+mxConnectionHandler.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxConnectionHandler.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxConnectionHandler.prototype.isCreateTarget = function() {
+  return this.createTarget
+};
+mxConnectionHandler.prototype.setCreateTarget = function(a) {
+  this.createTarget = a
+};
+mxConnectionHandler.prototype.createShape = function() {
+  var a = new mxPolyline([], mxConstants.INVALID_COLOR);
+  a.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+  a.init(this.graph.getView().getOverlayPane());
+  a.svgStrokeTolerance = 0;
+  a.pointerEvents = !1;
+  a.isDashed = !0;
+  if (this.graph.dialect == mxConstants.DIALECT_SVG) a.node.setAttribute("pointer-events", "none");
+  else {
+    var b = mxUtils.bind(this, function(a) {
+      a = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(a),
+        mxEvent.getClientY(a));
+      return this.graph.view.getState(this.graph.getCellAt(a.x, a.y))
+    });
+    mxEvent.redirectMouseEvents(a.node, this.graph, b)
+  }
+  return a
+};
+mxConnectionHandler.prototype.init = function() {
+  this.graph.addMouseListener(this);
+  this.marker = this.createMarker();
+  this.constraintHandler = new mxConstraintHandler(this.graph);
+  this.changeHandler = mxUtils.bind(this, function(a) {
+    null != this.iconState && (this.iconState = this.graph.getView().getState(this.iconState.cell));
+    null != this.iconState ? this.redrawIcons(this.icons, this.iconState) : (this.destroyIcons(this.icons), this.previous = null);
+    this.constraintHandler.reset()
+  });
+  this.graph.getModel().addListener(mxEvent.CHANGE,
+    this.changeHandler);
+  this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);
+  this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);
+  this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);
+  this.drillHandler = mxUtils.bind(this, function(a) {
+    this.destroyIcons(this.icons)
+  });
+  this.graph.addListener(mxEvent.START_EDITING, this.drillHandler);
+  this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);
+  this.graph.getView().addListener(mxEvent.UP, this.drillHandler)
+};
+mxConnectionHandler.prototype.isConnectableCell = function(a) {
+  return !0
+};
+mxConnectionHandler.prototype.createMarker = function() {
+  var a = new mxCellMarker(this.graph);
+  a.hotspotEnabled = !0;
+  a.getCell = mxUtils.bind(this, function(b, c) {
+    c = mxCellMarker.prototype.getCell.apply(a, arguments);
+    this.error = null;
+    if (!this.isConnectableCell(c)) return null;
+    null != c ? this.isConnecting() ? null != this.previous && (this.error = this.validateConnection(this.previous.cell, c), null != this.error && 0 == this.error.length && (c = null, this.isCreateTarget() &&
+        (this.error = null))) : this.isValidSource(c) || (c = null) : this.isConnecting() &&
+      (!this.isCreateTarget() && !this.graph.allowDanglingEdges) && (this.error = "");
+    return c
+  });
+  a.isValidState = mxUtils.bind(this, function(b) {
+    return this.isConnecting() ? null == this.error : mxCellMarker.prototype.isValidState.apply(a, arguments)
+  });
+  a.getMarkerColor = mxUtils.bind(this, function(b, c, d) {
+    return null == this.connectImage || this.isConnecting() ? mxCellMarker.prototype.getMarkerColor.apply(a, arguments) : null
+  });
+  a.intersects = mxUtils.bind(this, function(b, c) {
+    return null != this.connectImage || this.isConnecting() ? !0 : mxCellMarker.prototype.intersects.apply(a,
+      arguments)
+  });
+  return a
+};
+mxConnectionHandler.prototype.start = function(a, b, c, d) {
+  this.previous = a;
+  this.first = new mxPoint(b, c);
+  this.edgeState = null != d ? d : this.createEdgeState(null);
+  this.marker.currentColor = this.marker.validColor;
+  this.marker.markedState = a;
+  this.marker.mark();
+  this.fireEvent(new mxEventObject(mxEvent.START, "state", this.previous))
+};
+mxConnectionHandler.prototype.isConnecting = function() {
+  return null != this.first && null != this.shape
+};
+mxConnectionHandler.prototype.isValidSource = function(a) {
+  return this.graph.isValidSource(a)
+};
+mxConnectionHandler.prototype.isValidTarget = function(a) {
+  return !0
+};
+mxConnectionHandler.prototype.validateConnection = function(a, b) {
+  return !this.isValidTarget(b) ? "" : this.graph.getEdgeValidationError(null, a, b)
+};
+mxConnectionHandler.prototype.getConnectImage = function(a) {
+  return this.connectImage
+};
+mxConnectionHandler.prototype.isMoveIconToFrontForState = function(a) {
+  return null != a.text && a.text.node.parentNode == this.graph.container ? !0 : this.moveIconFront
+};
+mxConnectionHandler.prototype.createIcons = function(a) {
+  var b = this.getConnectImage(a);
+  if (null != b && null != a) {
+    this.iconState = a;
+    var c = [],
+      d = new mxRectangle(0, 0, b.width, b.height),
+      e = new mxImageShape(d, b.src, null, null, 0);
+    e.preserveImageAspect = !1;
+    this.isMoveIconToFrontForState(a) ? (e.dialect = mxConstants.DIALECT_STRICTHTML, e.init(this.graph.container)) : (e.dialect = this.graph.dialect == mxConstants.DIALECT_SVG ? mxConstants.DIALECT_SVG :
+      mxConstants.DIALECT_VML, e.init(this.graph.getView().getOverlayPane()), this.moveIconBack &&
+      null != e.node.previousSibling && e.node.parentNode.insertBefore(e.node, e.node.parentNode.firstChild));
+    e.node.style.cursor = mxConstants.CURSOR_CONNECT;
+    var f = mxUtils.bind(this, function() {
+        return null != this.currentState ? this.currentState : a
+      }),
+      b = mxUtils.bind(this, function(a) {
+        mxEvent.isConsumed(a) || (this.icon = e, this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(a, f())))
+      });
+    mxEvent.redirectMouseEvents(e.node, this.graph, f, b);
+    c.push(e);
+    this.redrawIcons(c, this.iconState);
+    return c
+  }
+  return null
+};
+mxConnectionHandler.prototype.redrawIcons = function(a, b) {
+  if (null != a && null != a[0] && null != b) {
+    var c = this.getIconPosition(a[0], b);
+    a[0].bounds.x = c.x;
+    a[0].bounds.y = c.y;
+    a[0].redraw()
+  }
+};
+mxConnectionHandler.prototype.getIconPosition = function(a, b) {
+  var c = this.graph.getView().scale,
+    d = b.getCenterX(),
+    e = b.getCenterY();
+  if (this.graph.isSwimlane(b.cell)) {
+    var f = this.graph.getStartSize(b.cell),
+      d = 0 != f.width ? b.x + f.width * c / 2 : d,
+      e = 0 != f.height ? b.y + f.height * c / 2 : e,
+      f = mxUtils.toRadians(mxUtils.getValue(b.style, mxConstants.STYLE_ROTATION) || 0);
+    if (0 != f) var c = Math.cos(f),
+      f = Math.sin(f),
+      g = new mxPoint(b.getCenterX(), b.getCenterY()),
+      e = mxUtils.getRotatedPoint(new mxPoint(d, e), c, f, g),
+      d = e.x,
+      e = e.y
+  }
+  return new mxPoint(d -
+    a.bounds.width / 2, e - a.bounds.height / 2)
+};
+mxConnectionHandler.prototype.destroyIcons = function(a) {
+  if (null != a) {
+    this.iconState = null;
+    for (var b = 0; b < a.length; b++) a[b].destroy()
+  }
+};
+mxConnectionHandler.prototype.isStartEvent = function(a) {
+  return !this.graph.isForceMarqueeEvent(a.getEvent()) && (null != this.constraintHandler.currentFocus && null != this.constraintHandler.currentConstraint || null != this.previous && null == this.error &&
+    (null == this.icons || null != this.icons && null != this.icon))
+};
+mxConnectionHandler.prototype.mouseDown = function(a, b) {
+  this.mouseDownCounter++;
+  if (this.isEnabled() && this.graph.isEnabled() && !b.isConsumed() && !this.isConnecting() && this.isStartEvent(b)) {
+    null != this.constraintHandler.currentConstraint && null != this.constraintHandler.currentFocus && null != this.constraintHandler.currentPoint ? (this.sourceConstraint = this.constraintHandler.currentConstraint,
+      this.previous = this.constraintHandler.currentFocus, this.first = this.constraintHandler.currentPoint.clone()) : this.first = new mxPoint(b.getGraphX(),
+      b.getGraphY());
+    this.edgeState = this.createEdgeState(b);
+    this.mouseDownCounter = 1;
+    this.waypointsEnabled && null == this.shape && (this.waypoints = null, this.shape = this.createShape());
+    if (null == this.previous && null != this.edgeState) {
+      var c = this.graph.getPointForEvent(b.getEvent());
+      this.edgeState.cell.geometry.setTerminalPoint(c, !0)
+    }
+    this.fireEvent(new mxEventObject(mxEvent.START, "state", this.previous));
+    b.consume()
+  } else if (mxClient.IS_TOUCH && this.tapAndHoldEnabled && !this.tapAndHoldInProgress && this.isEnabled() && this.graph.isEnabled() &&
+    !this.isConnecting()) {
+    this.tapAndHoldInProgress = !0;
+    this.initialTouchX = b.getX();
+    this.initialTouchY = b.getY();
+    var d = this.graph.view.getState(this.marker.getCell(b));
+    this.tapAndHoldThread && window.clearTimeout(this.tapAndHoldThread);
+    this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, function() {
+      this.tapAndHoldValid && this.tapAndHold(b, d);
+      this.tapAndHoldValid = this.tapAndHoldInProgress = !1
+    }), this.tapAndHoldDelay);
+    this.tapAndHoldValid = !0
+  }
+  this.selectedIcon = this.icon;
+  this.icon = null
+};
+mxConnectionHandler.prototype.tapAndHold = function(a, b) {
+  null != b && (this.marker.currentColor = this.marker.validColor, this.marker.markedState = b, this.marker.mark(), this.first = new mxPoint(a.getGraphX(), a.getGraphY()), this.edgeState = this.createEdgeState(
+    a), this.previous = b, this.fireEvent(new mxEventObject(mxEvent.START, "state", this.previous)))
+};
+mxConnectionHandler.prototype.isImmediateConnectSource = function(a) {
+  return !this.graph.isCellMovable(a.cell)
+};
+mxConnectionHandler.prototype.createEdgeState = function(a) {
+  return null
+};
+mxConnectionHandler.prototype.updateCurrentState = function(a) {
+  var b = this.marker.process(a);
+  this.constraintHandler.update(a, null == this.first);
+  this.currentState = b
+};
+mxConnectionHandler.prototype.convertWaypoint = function(a) {
+  var b = this.graph.getView().getScale(),
+    c = this.graph.getView().getTranslate();
+  a.x = a.x / b - c.x;
+  a.y = a.y / b - c.y
+};
+mxConnectionHandler.prototype.mouseMove = function(a, b) {
+  this.tapAndHoldValid && (this.tapAndHoldValid = Math.abs(this.initialTouchX - b.getX()) < this.tapAndHoldTolerance && Math.abs(this.initialTouchY - b.getY()) < this.tapAndHoldTolerance);
+  if (!b.isConsumed() && (this.ignoreMouseDown || null != this.first || !this.graph.isMouseDown)) {
+    !this.isEnabled() && null != this.currentState && (this.destroyIcons(this.icons), this.currentState = null);
+    (null != this.first || this.isEnabled() && this.graph.isEnabled()) && this.updateCurrentState(b);
+    if (null != this.first) {
+      var c = this.graph.getView().scale,
+        c = new mxPoint(this.graph.snap(b.getGraphX() / c) * c, this.graph.snap(b.getGraphY() / c) * c),
+        d = null,
+        e = c;
+      null != this.constraintHandler.currentConstraint && (null != this.constraintHandler.currentFocus && null != this.constraintHandler.currentPoint) && (d = this.constraintHandler.currentConstraint,
+        e = this.constraintHandler.currentPoint.clone());
+      var f = this.first;
+      if (null != this.selectedIcon) {
+        var g = this.selectedIcon.bounds.width,
+          h = this.selectedIcon.bounds.height;
+        null != this.currentState &&
+          this.targetConnectImage ? (g = this.getIconPosition(this.selectedIcon, this.currentState), this.selectedIcon.bounds.x = g.x, this.selectedIcon.bounds.y = g.y) : (g = new mxRectangle(b.getGraphX() +
+            this.connectIconOffset.x, b.getGraphY() + this.connectIconOffset.y, g, h), this.selectedIcon.bounds = g);
+        this.selectedIcon.redraw()
+      }
+      if (null != this.edgeState) {
+        this.edgeState.absolutePoints = [null, null != this.currentState ? null : e];
+        this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, !0, this.sourceConstraint);
+        null != this.currentState &&
+          (null == d && (d = this.graph.getConnectionConstraint(this.edgeState, this.previous, !1)), this.edgeState.setAbsoluteTerminalPoint(null, !1), this.graph.view.updateFixedTerminalPoint(this
+            .edgeState, this.currentState, !1, d));
+        f = null;
+        if (null != this.waypoints) {
+          f = [];
+          for (e = 0; e < this.waypoints.length; e++) d = this.waypoints[e].clone(), this.convertWaypoint(d), f[e] = d
+        }
+        this.graph.view.updatePoints(this.edgeState, f, this.previous, this.currentState);
+        this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);
+        e = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
+        f = this.edgeState.absolutePoints[0]
+      } else null != this.currentState && null == this.constraintHandler.currentConstraint && (g = this.getTargetPerimeterPoint(this.currentState, b), null != g && (e = g)), null == this.sourceConstraint &&
+        null != this.previous && (g = this.getSourcePerimeterPoint(this.previous, null != this.waypoints && 0 < this.waypoints.length ? this.waypoints[0] : e, b), null != g && (f = g));
+      if (null == this.currentState && this.movePreviewAway) {
+        g = f;
+        null != this.edgeState &&
+          2 < this.edgeState.absolutePoints.length && (d = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2], null != d && (g = d));
+        d = e.x - g.x;
+        g = e.y - g.y;
+        h = Math.sqrt(d * d + g * g);
+        if (0 == h) return;
+        e.x -= 4 * d / h;
+        e.y -= 4 * g / h
+      }
+      if (null == this.shape && (d = Math.abs(c.x - this.first.x), g = Math.abs(c.y - this.first.y), d > this.graph.tolerance || g > this.graph.tolerance)) this.shape = this.createShape(), this.updateCurrentState(
+        b);
+      null != this.shape && (null != this.edgeState ? this.shape.points = this.edgeState.absolutePoints : (c = [f], null != this.waypoints &&
+        (c = c.concat(this.waypoints)), c.push(e), this.shape.points = c), this.drawPreview());
+      mxEvent.consume(b.getEvent());
+      b.consume()
+    } else !this.isEnabled() || !this.graph.isEnabled() ? this.constraintHandler.reset() : this.previous != this.currentState && null == this.edgeState ? (this.destroyIcons(this.icons), this.icons =
+        null, null != this.currentState && null == this.error && (this.icons = this.createIcons(this.currentState), null == this.icons && (this.currentState.setCursor(mxConstants.CURSOR_CONNECT), b
+          .consume())), this.previous = this.currentState) :
+      this.previous == this.currentState && (null != this.currentState && null == this.icons && !this.graph.isMouseDown) && b.consume();
+    null != this.constraintHandler.currentConstraint && this.marker.reset();
+    if (!this.graph.isMouseDown && null != this.currentState && null != this.icons) {
+      c = !1;
+      f = b.getSource();
+      for (e = 0; e < this.icons.length && !c; e++) c = f == this.icons[e].node || f.parentNode == this.icons[e].node;
+      c || this.updateIcons(this.currentState, this.icons, b)
+    }
+  } else this.constraintHandler.reset()
+};
+mxConnectionHandler.prototype.getTargetPerimeterPoint = function(a, b) {
+  var c = null,
+    d = a.view,
+    e = d.getPerimeterFunction(a);
+  if (null != e) {
+    var f = null != this.waypoints && 0 < this.waypoints.length ? this.waypoints[this.waypoints.length - 1] : new mxPoint(this.previous.getCenterX(), this.previous.getCenterY()),
+      d = e(d.getPerimeterBounds(a), this.edgeState, f, !1);
+    null != d && (c = d)
+  } else c = new mxPoint(a.getCenterX(), a.getCenterY());
+  return c
+};
+mxConnectionHandler.prototype.getSourcePerimeterPoint = function(a, b, c) {
+  c = null;
+  var d = a.view,
+    e = d.getPerimeterFunction(a),
+    f = new mxPoint(a.getCenterX(), a.getCenterY());
+  if (null != e) {
+    var g = mxUtils.getValue(a.style, mxConstants.STYLE_ROTATION, 0),
+      h = -g * (Math.PI / 180);
+    0 != g && (b = mxUtils.getRotatedPoint(new mxPoint(b.x, b.y), Math.cos(h), Math.sin(h), f));
+    a = e(d.getPerimeterBounds(a), a, b, !1);
+    null != a && (0 != g && (a = mxUtils.getRotatedPoint(new mxPoint(a.x, a.y), Math.cos(-h), Math.sin(-h), f)), c = a)
+  } else c = f;
+  return c
+};
+mxConnectionHandler.prototype.updateIcons = function(a, b, c) {};
+mxConnectionHandler.prototype.isStopEvent = function(a) {
+  return null != a.getState()
+};
+mxConnectionHandler.prototype.addWaypointForEvent = function(a) {
+  var b = mxUtils.convertPoint(this.graph.container, a.getX(), a.getY()),
+    c = Math.abs(b.x - this.first.x),
+    b = Math.abs(b.y - this.first.y);
+  if (null != this.waypoints || 1 < this.mouseDownCounter && (c > this.graph.tolerance || b > this.graph.tolerance)) null == this.waypoints && (this.waypoints = []), c = this.graph.view.scale, b =
+    new mxPoint(this.graph.snap(a.getGraphX() / c) * c, this.graph.snap(a.getGraphY() / c) * c), this.waypoints.push(b)
+};
+mxConnectionHandler.prototype.mouseUp = function(a, b) {
+  if (!b.isConsumed() && this.isConnecting()) {
+    if (this.waypointsEnabled && !this.isStopEvent(b)) {
+      this.addWaypointForEvent(b);
+      b.consume();
+      return
+    }
+    if (null == this.error) {
+      var c = null != this.previous ? this.previous.cell : null,
+        d = null;
+      null != this.constraintHandler.currentConstraint && null != this.constraintHandler.currentFocus && (d = this.constraintHandler.currentFocus.cell);
+      null == d && this.marker.hasValidState() && (d = this.marker.validState.cell);
+      this.connect(c, d, b.getEvent(),
+        b.getCell())
+    } else null != this.previous && (null != this.marker.validState && this.previous.cell == this.marker.validState.cell) && this.graph.selectCellForEvent(this.marker.source, evt), 0 < this.error.length &&
+      this.graph.validationAlert(this.error);
+    this.destroyIcons(this.icons);
+    b.consume()
+  }
+  null != this.first && this.reset();
+  this.tapAndHoldValid = this.tapAndHoldInProgress = !1
+};
+mxConnectionHandler.prototype.reset = function() {
+  null != this.shape && (this.shape.destroy(), this.shape = null);
+  this.destroyIcons(this.icons);
+  this.icons = null;
+  this.marker.reset();
+  this.constraintHandler.reset();
+  this.sourceConstraint = this.error = this.previous = this.edgeState = this.selectedIcon = null;
+  this.mouseDownCounter = 0;
+  this.icon = this.first = null;
+  this.fireEvent(new mxEventObject(mxEvent.RESET))
+};
+mxConnectionHandler.prototype.drawPreview = function() {
+  var a = null == this.error;
+  this.shape.strokewidth = this.getEdgeWidth(a);
+  a = this.getEdgeColor(a);
+  this.shape.stroke = a;
+  this.shape.redraw()
+};
+mxConnectionHandler.prototype.getEdgeColor = function(a) {
+  return a ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR
+};
+mxConnectionHandler.prototype.getEdgeWidth = function(a) {
+  return a ? 3 : 1
+};
+mxConnectionHandler.prototype.connect = function(a, b, c, d) {
+  if (null != b || this.isCreateTarget() || this.graph.allowDanglingEdges) {
+    var e = this.graph.getModel(),
+      f = null;
+    e.beginUpdate();
+    try {
+      if (null != a && (null == b && this.isCreateTarget()) && (b = this.createTargetVertex(c, a), null != b)) {
+        d = this.graph.getDropTarget([b], c, d);
+        if (null == d || !this.graph.getModel().isEdge(d)) {
+          var g = this.graph.getView().getState(d);
+          if (null != g) {
+            var h = e.getGeometry(b);
+            h.x -= g.origin.x;
+            h.y -= g.origin.y
+          }
+        } else d = this.graph.getDefaultParent();
+        this.graph.addCell(b,
+          d)
+      }
+      var k = this.graph.getDefaultParent();
+      null != a && (null != b && e.getParent(a) == e.getParent(b) && e.getParent(e.getParent(a)) != e.getRoot()) && (k = e.getParent(a), null != a.geometry && a.geometry.relative && (null != b.geometry &&
+        b.geometry.relative) && (k = e.getParent(k)));
+      h = g = null;
+      null != this.edgeState && (g = this.edgeState.cell.value, h = this.edgeState.cell.style);
+      f = this.insertEdge(k, null, g, a, b, h);
+      if (null != f) {
+        this.graph.setConnectionConstraint(f, a, !0, this.sourceConstraint);
+        this.graph.setConnectionConstraint(f, b, !1, this.constraintHandler.currentConstraint);
+        null != this.edgeState && e.setGeometry(f, this.edgeState.cell.geometry);
+        var l = e.getGeometry(f);
+        null == l && (l = new mxGeometry, l.relative = !0, e.setGeometry(f, l));
+        if (null != this.waypoints && 0 < this.waypoints.length) {
+          var m = this.graph.view.scale,
+            n = this.graph.view.translate;
+          l.points = [];
+          for (a = 0; a < this.waypoints.length; a++) {
+            var p = this.waypoints[a];
+            l.points.push(new mxPoint(p.x / m - n.x, p.y / m - n.y))
+          }
+        }
+        null == b && (p = this.graph.getPointForEvent(c, !1), p.x -= this.graph.panDx / this.graph.view.scale, p.y -= this.graph.panDy / this.graph.view.scale,
+          l.setTerminalPoint(p, !1));
+        this.fireEvent(new mxEventObject(mxEvent.CONNECT, "cell", f, "event", c, "target", d))
+      }
+    } catch (q) {
+      mxLog.show(), mxLog.debug(q.message)
+    } finally {
+      e.endUpdate()
+    }
+    this.select && this.selectCells(f, b)
+  }
+};
+mxConnectionHandler.prototype.selectCells = function(a, b) {
+  this.graph.setSelectionCell(a)
+};
+mxConnectionHandler.prototype.insertEdge = function(a, b, c, d, e, f) {
+  if (null == this.factoryMethod) return this.graph.insertEdge(a, b, c, d, e, f);
+  b = this.createEdge(c, d, e, f);
+  return b = this.graph.addEdge(b, a, d, e)
+};
+mxConnectionHandler.prototype.createTargetVertex = function(a, b) {
+  for (var c = this.graph.getCellGeometry(b); null != c && c.relative;) b = this.graph.getModel().getParent(b), c = this.graph.getCellGeometry(b);
+  var d = this.graph.cloneCells([b])[0],
+    c = this.graph.getModel().getGeometry(d);
+  if (null != c) {
+    var e = this.graph.getPointForEvent(a);
+    c.x = this.graph.snap(e.x - c.width / 2) - this.graph.panDx / this.graph.view.scale;
+    c.y = this.graph.snap(e.y - c.height / 2) - this.graph.panDy / this.graph.view.scale;
+    if (null != this.first) {
+      var f = this.graph.view.getState(b);
+      if (null != f) {
+        var g = this.getAlignmentTolerance();
+        Math.abs(this.graph.snap(this.first.x) - this.graph.snap(e.x)) <= g ? c.x = f.x : Math.abs(this.graph.snap(this.first.y) - this.graph.snap(e.y)) <= g && (c.y = f.y)
+      }
+    }
+  }
+  return d
+};
+mxConnectionHandler.prototype.getAlignmentTolerance = function() {
+  return this.graph.isGridEnabled() ? this.graph.gridSize : this.graph.tolerance
+};
+mxConnectionHandler.prototype.createEdge = function(a, b, c, d) {
+  var e = null;
+  null != this.factoryMethod && (e = this.factoryMethod(b, c, d));
+  null == e && (e = new mxCell(a || ""), e.setEdge(!0), e.setStyle(d), a = new mxGeometry, a.relative = !0, e.setGeometry(a));
+  return e
+};
+mxConnectionHandler.prototype.destroy = function() {
+  this.graph.removeMouseListener(this);
+  null != this.shape && (this.shape.destroy(), this.shape = null);
+  null != this.marker && (this.marker.destroy(), this.marker = null);
+  null != this.constraintHandler && (this.constraintHandler.destroy(), this.constraintHandler = null);
+  null != this.changeHandler && (this.graph.getModel().removeListener(this.changeHandler), this.graph.getView().removeListener(this.changeHandler), this.changeHandler = null);
+  null != this.drillHandler && (this.graph.removeListener(this.drillHandler),
+    this.graph.getView().removeListener(this.drillHandler), this.drillHandler = null)
+};
+
+function mxConstraintHandler(a) {
+  this.graph = a
+}
+mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + "/point.gif", 5, 5);
+mxConstraintHandler.prototype.graph = null;
+mxConstraintHandler.prototype.enabled = !0;
+mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;
+mxConstraintHandler.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxConstraintHandler.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxConstraintHandler.prototype.reset = function() {
+  if (null != this.focusIcons) {
+    for (var a = 0; a < this.focusIcons.length; a++) this.focusIcons[a].destroy();
+    this.focusIcons = null
+  }
+  null != this.focusHighlight && (this.focusHighlight.destroy(), this.focusHighlight = null);
+  this.focusPoints = this.currentFocus = this.currentPoint = this.currentFocusArea = this.currentConstraint = null
+};
+mxConstraintHandler.prototype.getTolerance = function() {
+  return this.graph.getTolerance()
+};
+mxConstraintHandler.prototype.getImageForConstraint = function(a, b, c) {
+  return this.pointImage
+};
+mxConstraintHandler.prototype.isEventIgnored = function(a, b) {
+  return !1
+};
+mxConstraintHandler.prototype.update = function(a, b) {
+  if (this.isEnabled() && !this.isEventIgnored(a)) {
+    var c = this.getTolerance(),
+      d = new mxRectangle(a.getGraphX() - c, a.getGraphY() - c, 2 * c, 2 * c),
+      e = null != a.getCell() ? this.graph.isCellConnectable(a.getCell()) : !1;
+    if (null == this.currentFocusArea || !mxUtils.intersects(this.currentFocusArea, d) || null != a.getState() && null != this.currentFocus && e)
+      if (this.currentFocusArea = null, a.getState() != this.currentFocus)
+        if (this.currentFocus = null, this.constraints = null != a.getState() && e ? this.graph.getAllConnectionConstraints(a.getState(),
+            b) : null, null != this.constraints) {
+          this.currentFocus = a.getState();
+          this.currentFocusArea = new mxRectangle(a.getState().x, a.getState().y, a.getState().width, a.getState().height);
+          if (null != this.focusIcons) {
+            for (e = 0; e < this.focusIcons.length; e++) this.focusIcons[e].destroy();
+            this.focusPoints = this.focusIcons = null
+          }
+          this.focusIcons = [];
+          this.focusPoints = [];
+          for (e = 0; e < this.constraints.length; e++) {
+            var f = this.graph.getConnectionPoint(a.getState(), this.constraints[e]),
+              g = this.getImageForConstraint(a.getState(), this.constraints[e],
+                f),
+              h = g.src,
+              g = new mxRectangle(f.x - g.width / 2, f.y - g.height / 2, g.width, g.height),
+              g = new mxImageShape(g, h);
+            g.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+            g.preserveImageAspect = !1;
+            g.init(this.graph.getView().getOverlayPane());
+            null != g.node.previousSibling && g.node.parentNode.insertBefore(g.node, g.node.parentNode.firstChild);
+            h = mxUtils.bind(this, function() {
+              return null != this.currentFocus ? this.currentFocus : a.getState()
+            });
+            g.redraw();
+            mxEvent.redirectMouseEvents(g.node,
+              this.graph, h);
+            this.currentFocusArea.add(g.bounds);
+            this.focusIcons.push(g);
+            this.focusPoints.push(f)
+          }
+          this.currentFocusArea.grow(c)
+        } else if (null != this.focusIcons) {
+      null != this.focusHighlight && (this.focusHighlight.destroy(), this.focusHighlight = null);
+      for (e = 0; e < this.focusIcons.length; e++) this.focusIcons[e].destroy();
+      this.focusPoints = this.focusIcons = null
+    }
+    this.currentPoint = this.currentConstraint = null;
+    if (null != this.focusIcons && null != this.constraints && (null == a.getState() || this.currentFocus == a.getState()))
+      for (e =
+        0; e < this.focusIcons.length; e++)
+        if (mxUtils.intersects(this.focusIcons[e].bounds, d)) {
+          this.currentConstraint = this.constraints[e];
+          this.currentPoint = this.focusPoints[e];
+          c = this.focusIcons[e].bounds.clone();
+          c.grow(mxClient.IS_IE ? 3 : 2);
+          mxClient.IS_IE && (c.width -= 1, c.height -= 1);
+          null == this.focusHighlight ? (c = new mxRectangleShape(c, null, this.highlightColor, 3), c.dialect = this.graph.dialect == mxConstants.DIALECT_SVG ? mxConstants.DIALECT_SVG : mxConstants
+            .DIALECT_VML, c.init(this.graph.getView().getOverlayPane()), this.focusHighlight =
+            c, h = mxUtils.bind(this, function() {
+              return null != this.currentFocus ? this.currentFocus : a.getState()
+            }), mxEvent.redirectMouseEvents(c.node, this.graph, h)) : (this.focusHighlight.bounds = c, this.focusHighlight.redraw());
+          break
+        }
+    null == this.currentConstraint && null != this.focusHighlight && (this.focusHighlight.destroy(), this.focusHighlight = null)
+  }
+};
+mxConstraintHandler.prototype.destroy = function() {
+  this.reset()
+};
+
+function mxRubberband(a) {
+  null != a && (this.graph = a, this.graph.addMouseListener(this), this.panHandler = mxUtils.bind(this, function() {
+    this.repaint()
+  }), this.graph.addListener(mxEvent.PAN, this.panHandler), mxClient.IS_IE && mxEvent.addListener(window, "unload", mxUtils.bind(this, function() {
+    this.destroy()
+  })))
+}
+mxRubberband.prototype.defaultOpacity = 20;
+mxRubberband.prototype.enabled = !0;
+mxRubberband.prototype.div = null;
+mxRubberband.prototype.sharedDiv = null;
+mxRubberband.prototype.currentX = 0;
+mxRubberband.prototype.currentY = 0;
+mxRubberband.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxRubberband.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxRubberband.prototype.mouseDown = function(a, b) {
+  if (!b.isConsumed() && this.isEnabled() && this.graph.isEnabled() && (this.graph.isForceMarqueeEvent(b.getEvent()) || null == b.getState())) {
+    var c = mxUtils.getOffset(this.graph.container),
+      d = mxUtils.getScrollOrigin(this.graph.container);
+    d.x -= c.x;
+    d.y -= c.y;
+    this.start(b.getX() + d.x, b.getY() + d.y);
+    if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC) {
+      var e = this.graph.container,
+        f = function(a) {
+          a = new mxMouseEvent(a);
+          var b = mxUtils.convertPoint(e, a.getX(), a.getY());
+          a.graphX = b.x;
+          a.graphY = b.y;
+          return a
+        };
+      this.dragHandler = mxUtils.bind(this, function(a) {
+        this.mouseMove(this.graph, f(a))
+      });
+      this.dropHandler = mxUtils.bind(this, function(a) {
+        this.mouseUp(this.graph, f(a))
+      });
+      mxEvent.addListener(document, "mousemove", this.dragHandler);
+      mxEvent.addListener(document, "mouseup", this.dropHandler)
+    }
+    b.consume(!1)
+  }
+};
+mxRubberband.prototype.start = function(a, b) {
+  this.first = new mxPoint(a, b)
+};
+mxRubberband.prototype.mouseMove = function(a, b) {
+  if (!b.isConsumed() && null != this.first) {
+    var c = mxUtils.getScrollOrigin(this.graph.container),
+      d = mxUtils.getOffset(this.graph.container);
+    c.x -= d.x;
+    c.y -= d.y;
+    var d = b.getX() + c.x,
+      c = b.getY() + c.y,
+      e = this.first.x - d,
+      f = this.first.y - c,
+      g = this.graph.tolerance;
+    if (null != this.div || Math.abs(e) > g || Math.abs(f) > g) null == this.div && (this.div = this.createShape()), mxUtils.clearSelection(), this.update(d, c), b.consume()
+  }
+};
+mxRubberband.prototype.createShape = function() {
+  null == this.sharedDiv && (this.sharedDiv = document.createElement("div"), this.sharedDiv.className = "mxRubberband", mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity));
+  this.graph.container.appendChild(this.sharedDiv);
+  return this.sharedDiv
+};
+mxRubberband.prototype.mouseUp = function(a, b) {
+  var c = null != this.div;
+  this.reset();
+  c && (c = new mxRectangle(this.x, this.y, this.width, this.height), this.graph.selectRegion(c, b.getEvent()), b.consume())
+};
+mxRubberband.prototype.reset = function() {
+  null != this.div && this.div.parentNode.removeChild(this.div);
+  null != this.dragHandler && (mxEvent.removeListener(document, "mousemove", this.dragHandler), this.dragHandler = null);
+  null != this.dropHandler && (mxEvent.removeListener(document, "mouseup", this.dropHandler), this.dropHandler = null);
+  this.currentY = this.currentX = 0;
+  this.div = this.first = null
+};
+mxRubberband.prototype.update = function(a, b) {
+  this.currentX = a;
+  this.currentY = b;
+  this.repaint()
+};
+mxRubberband.prototype.repaint = function() {
+  if (null != this.div) {
+    var a = this.currentX - this.graph.panDx,
+      b = this.currentY - this.graph.panDy;
+    this.x = Math.min(this.first.x, a);
+    this.y = Math.min(this.first.y, b);
+    this.width = Math.max(this.first.x, a) - this.x;
+    this.height = Math.max(this.first.y, b) - this.y;
+    a = mxClient.IS_VML ? this.graph.panDy : 0;
+    this.div.style.left = this.x + (mxClient.IS_VML ? this.graph.panDx : 0) + "px";
+    this.div.style.top = this.y + a + "px";
+    this.div.style.width = Math.max(1, this.width) + "px";
+    this.div.style.height = Math.max(1,
+      this.height) + "px"
+  }
+};
+mxRubberband.prototype.destroy = function() {
+  this.destroyed || (this.destroyed = !0, this.graph.removeMouseListener(this), this.graph.removeListener(this.panHandler), this.reset(), null != this.sharedDiv && (this.sharedDiv = null))
+};
+
+function mxVertexHandler(a) {
+  null != a && (this.state = a, this.init())
+}
+mxVertexHandler.prototype.graph = null;
+mxVertexHandler.prototype.state = null;
+mxVertexHandler.prototype.singleSizer = !1;
+mxVertexHandler.prototype.index = null;
+mxVertexHandler.prototype.allowHandleBoundsCheck = !0;
+mxVertexHandler.prototype.handleImage = null;
+mxVertexHandler.prototype.tolerance = 0;
+mxVertexHandler.prototype.rotationEnabled = !1;
+mxVertexHandler.prototype.rotationRaster = !0;
+mxVertexHandler.prototype.init = function() {
+  this.graph = this.state.view.graph;
+  this.selectionBounds = this.getSelectionBounds(this.state);
+  this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
+  this.selectionBorder = this.createSelectionShape(this.bounds);
+  this.selectionBorder.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+  this.selectionBorder.pointerEvents = !1;
+  this.selectionBorder.init(this.graph.getView().getOverlayPane());
+  this.graph.isCellMovable(this.state.cell) && (this.selectionBorder.node.style.cursor = mxConstants.CURSOR_MOVABLE_VERTEX);
+  mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);
+  if (0 >= mxGraphHandler.prototype.maxCells || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) {
+    var a = this.graph.isCellResizable(this.state.cell);
+    this.sizers = [];
+    if (a || this.graph.isLabelMovable(this.state.cell) && 2 <= this.state.width && 2 <= this.state.height) {
+      var b = 0;
+      a && (this.singleSizer || (this.sizers.push(this.createSizer("nw-resize",
+        b++)), this.sizers.push(this.createSizer("n-resize", b++)), this.sizers.push(this.createSizer("ne-resize", b++)), this.sizers.push(this.createSizer("w-resize", b++)), this.sizers.push(
+        this.createSizer("e-resize", b++)), this.sizers.push(this.createSizer("sw-resize", b++)), this.sizers.push(this.createSizer("s-resize", b++))), this.sizers.push(this.createSizer(
+        "se-resize", b++)));
+      a = this.graph.model.getGeometry(this.state.cell);
+      null != a && (!a.relative && !this.graph.isSwimlane(this.state.cell) && this.graph.isLabelMovable(this.state.cell)) &&
+        (this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR), this.sizers.push(this.labelShape))
+    } else this.graph.isCellMovable(this.state.cell) && (!this.graph.isCellResizable(this.state.cell) && 2 > this.state.width && 2 > this.state.height) && (this.labelShape = this.createSizer(
+      mxConstants.CURSOR_MOVABLE_VERTEX, null, null, mxConstants.LABEL_HANDLE_FILLCOLOR), this.sizers.push(this.labelShape))
+  }
+  if (this.rotationEnabled && (0 >=
+      mxGraphHandler.prototype.maxCells || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)) this.rotationShape = this.createSizer("pointer", mxEvent.ROTATION_HANDLE, mxConstants
+    .HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR), this.sizers.push(this.rotationShape);
+  this.redraw()
+};
+mxVertexHandler.prototype.getSelectionBounds = function(a) {
+  return new mxRectangle(Math.round(a.x), Math.round(a.y), Math.round(a.width), Math.round(a.height))
+};
+mxVertexHandler.prototype.createSelectionShape = function(a) {
+  a = new mxRectangleShape(a, null, this.getSelectionColor());
+  a.strokewidth = this.getSelectionStrokeWidth();
+  a.isDashed = this.isSelectionDashed();
+  return a
+};
+mxVertexHandler.prototype.getSelectionColor = function() {
+  return mxConstants.VERTEX_SELECTION_COLOR
+};
+mxVertexHandler.prototype.getSelectionStrokeWidth = function() {
+  return mxConstants.VERTEX_SELECTION_STROKEWIDTH
+};
+mxVertexHandler.prototype.isSelectionDashed = function() {
+  return mxConstants.VERTEX_SELECTION_DASHED
+};
+mxVertexHandler.prototype.createSizer = function(a, b, c, d) {
+  c = c || mxConstants.HANDLE_SIZE;
+  c = new mxRectangle(0, 0, c, c);
+  d = this.createSizerShape(c, b, d);
+  d.isHtmlAllowed() && null != this.state.text && this.state.text.node.parentNode == this.graph.container ? (d.bounds.height -= 1, d.bounds.width -= 1, d.dialect = mxConstants.DIALECT_STRICTHTML, d
+    .init(this.graph.container)) : (d.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG, d.init(this.graph.getView().getOverlayPane()));
+  mxEvent.redirectMouseEvents(d.node, this.graph, this.state);
+  this.graph.isEnabled() && (d.node.style.cursor = a);
+  this.isSizerVisible(b) || (d.node.style.visibility = "hidden");
+  return d
+};
+mxVertexHandler.prototype.isSizerVisible = function(a) {
+  return !0
+};
+mxVertexHandler.prototype.createSizerShape = function(a, b, c) {
+  return null != this.handleImage ? (a.width = this.handleImage.width, a.height = this.handleImage.height, new mxImageShape(a, this.handleImage.src)) : b == mxEvent.ROTATION_HANDLE ? new mxEllipse(
+    a, c || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR) : new mxRectangleShape(a, c || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR)
+};
+mxVertexHandler.prototype.moveSizerTo = function(a, b, c) {
+  null != a && (a.bounds.x = Math.round(b - a.bounds.width / 2), a.bounds.y = Math.round(c - a.bounds.height / 2), a.redraw())
+};
+mxVertexHandler.prototype.getHandleForEvent = function(a) {
+  if (a.isSource(this.rotationShape)) return mxEvent.ROTATION_HANDLE;
+  if (a.isSource(this.labelShape)) return mxEvent.LABEL_HANDLE;
+  if (null != this.sizers)
+    for (var b = this.tolerance, b = this.allowHandleBoundsCheck && (mxClient.IS_IE || 0 < b) ? new mxRectangle(a.getGraphX() - b, a.getGraphY() - b, 2 * b, 2 * b) : null, c = 0; c < this.sizers.length; c++)
+      if (a.isSource(this.sizers[c]) || null != b && mxUtils.intersects(this.sizers[c].bounds, b)) return c;
+  return null
+};
+mxVertexHandler.prototype.mouseDown = function(a, b) {
+  if (!b.isConsumed() && this.graph.isEnabled() && !this.graph.isForceMarqueeEvent(b.getEvent()) && (0 < this.tolerance || b.getState() == this.state)) {
+    var c = this.getHandleForEvent(b);
+    null != c && (this.start(b.getX(), b.getY(), c), b.consume())
+  }
+};
+mxVertexHandler.prototype.start = function(a, b, c) {
+  a = mxUtils.convertPoint(this.graph.container, a, b);
+  this.startX = a.x;
+  this.startY = a.y;
+  this.index = c;
+  this.selectionBorder.node.style.display = c == mxEvent.ROTATION_HANDLE ? "inline" : "none";
+  this.preview = this.createSelectionShape(this.bounds);
+  !(mxClient.IS_SVG && 0 != Number(this.state.style[mxConstants.STYLE_ROTATION] || "0")) && null != this.state.text && this.state.text.node.parentNode == this.graph.container ? (this.preview.dialect =
+      mxConstants.DIALECT_STRICTHTML, this.preview.init(this.graph.container)) :
+    (this.preview.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG, this.preview.init(this.graph.view.getOverlayPane()))
+};
+mxVertexHandler.prototype.mouseMove = function(a, b) {
+  if (!b.isConsumed() && null != this.index) {
+    var c = new mxPoint(b.getGraphX(), b.getGraphY()),
+      d = this.graph.isGridEnabledEvent(b.getEvent()),
+      e = this.graph.getView().scale;
+    if (this.index == mxEvent.LABEL_HANDLE) d && (c.x = this.graph.snap(c.x / e) * e, c.y = this.graph.snap(c.y / e) * e), this.moveSizerTo(this.sizers[this.sizers.length - 1], c.x, c.y);
+    else if (this.index == mxEvent.ROTATION_HANDLE) {
+      var f = this.state.x + this.state.width / 2 - c.x,
+        g = this.state.y + this.state.height / 2 - c.y;
+      this.currentAlpha =
+        0 != f ? 180 * Math.atan(g / f) / Math.PI + 90 : 0 > g ? 180 : 0;
+      0 < f && (this.currentAlpha -= 180);
+      this.rotationRaster && (f = c.x - this.state.getCenterX(), g = c.y - this.state.getCenterY(), f = Math.abs(Math.sqrt(f * f + g * g) - this.state.height / 2 - 20), f = Math.max(1, 5 * Math.min(
+        3, Math.max(0, Math.round(80 / Math.abs(f))))), this.currentAlpha = Math.round(this.currentAlpha / f) * f);
+      this.selectionBorder.rotation = this.currentAlpha;
+      this.selectionBorder.redraw()
+    } else {
+      var h = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || "0"),
+        k = Math.cos(-h),
+        l = Math.sin(-h),
+        m = new mxPoint(this.state.getCenterX(), this.state.getCenterY()),
+        f = c.x - this.startX,
+        g = c.y - this.startY,
+        c = this.graph.view.translate,
+        n = l * f + k * g,
+        f = k * f - l * g;
+      this.bounds = this.union(this.selectionBounds, f, n, this.index, d, e, c);
+      k = Math.cos(h);
+      l = Math.sin(h);
+      g = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY());
+      f = g.x - m.x;
+      g = g.y - m.y;
+      m = l * f + k * g - g;
+      this.bounds.x += k * f - l * g - f;
+      this.bounds.y += m;
+      this.drawPreview()
+    }
+    b.consume()
+  } else null != this.getHandleForEvent(b) && b.consume(!1)
+};
+mxVertexHandler.prototype.mouseUp = function(a, b) {
+  if (!b.isConsumed() && null != this.index && null != this.state) {
+    var c = new mxPoint(b.getGraphX(), b.getGraphY());
+    this.graph.getModel().beginUpdate();
+    try {
+      if (this.index == mxEvent.ROTATION_HANDLE) null != this.currentAlpha && this.rotateCell(this.state.cell, this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0));
+      else {
+        var d = this.graph.isGridEnabledEvent(b.getEvent()),
+          e = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || "0"),
+          f = Math.cos(-e),
+          g = Math.sin(-e),
+          h = c.x - this.startX,
+          k = c.y - this.startY,
+          c = g * h + f * k,
+          h = f * h - g * k,
+          k = c,
+          l = this.graph.view.scale;
+        this.resizeCell(this.state.cell, h / l, k / l, this.index, d)
+      }
+    } finally {
+      this.graph.getModel().endUpdate()
+    }
+    this.reset();
+    b.consume()
+  }
+};
+mxVertexHandler.prototype.rotateCell = function(a, b) {
+  var c = this.graph.getModel();
+  if (c.isVertex(a)) {
+    var d = a == this.state ? this.state : this.graph.view.getState(a);
+    null != d && this.graph.setCellStyles(mxConstants.STYLE_ROTATION, (d.style[mxConstants.STYLE_ROTATION] || 0) + b, [a]);
+    if (this.state.cell != a && (d = this.graph.getCellGeometry(a), null != d && !d.relative))
+      if (0 != b) {
+        var e = this.graph.getModel().getParent(a),
+          f = this.graph.getCellGeometry(e);
+        if (!d.relative && null != f) {
+          var g = mxUtils.toRadians(b),
+            e = Math.cos(g),
+            g = Math.sin(g),
+            h = new mxPoint(d.getCenterX(), d.getCenterY()),
+            f = new mxPoint(f.width / 2, f.height / 2),
+            e = mxUtils.getRotatedPoint(h, e, g, f),
+            d = d.clone();
+          d.x = e.x - d.width / 2;
+          d.y = e.y - d.height / 2;
+          c.setGeometry(a, d)
+        }
+      } else d = d.clone(), d.x += dx, d.y += dy, c.setGeometry(a, d);
+    d = c.getChildCount(a);
+    for (e = 0; e < d; e++) this.rotateCell(c.getChildAt(a, e), b)
+  }
+};
+mxVertexHandler.prototype.reset = function() {
+  this.index = this.currentAlpha = null;
+  null != this.preview && (this.preview.destroy(), this.preview = null);
+  null != this.selectionBorder && (this.selectionBorder.node.style.display = "inline", this.selectionBounds = this.getSelectionBounds(this.state), this.bounds = new mxRectangle(this.selectionBounds
+    .x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height), this.drawPreview())
+};
+mxVertexHandler.prototype.resizeCell = function(a, b, c, d, e) {
+  var f = this.graph.model.getGeometry(a);
+  if (null != f)
+    if (d == mxEvent.LABEL_HANDLE) d = this.graph.view.scale, b = (this.labelShape.bounds.getCenterX() - this.startX) / d, c = (this.labelShape.bounds.getCenterY() - this.startY) / d, f = f.clone(),
+      null == f.offset ? f.offset = new mxPoint(b, c) : (f.offset.x += b, f.offset.y += c), this.graph.model.setGeometry(a, f);
+    else {
+      d = this.union(f, b, c, d, e, 1, new mxPoint(0, 0));
+      var g = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || "0");
+      if (0 != g) {
+        b = d.getCenterX() - f.getCenterX();
+        c = d.getCenterY() - f.getCenterY();
+        e = Math.cos(g);
+        var h = Math.sin(g),
+          g = e * b - h * c - b;
+        b = h * b + e * c - c;
+        c = d.x - f.x;
+        var k = d.y - f.y,
+          l = e * c - h * k;
+        e = h * c + e * k;
+        d.x += g;
+        d.y += b;
+        if (!this.graph.isCellCollapsed(a) && (0 != g || 0 != b)) c = f.x - d.x + l, k = f.y - d.y + e, this.moveChildren(a, c, k)
+      }
+      this.graph.resizeCell(a, d)
+    }
+};
+mxVertexHandler.prototype.moveChildren = function(a, b, c) {
+  for (var d = this.graph.getModel(), e = d.getChildCount(a), f = 0; f < e; f++) {
+    var g = d.getChildAt(a, f);
+    if (d.isVertex(g)) {
+      var h = this.graph.getCellGeometry(g);
+      null != h && !h.relative && (h = h.clone(), h.x += b, h.y += c, d.setGeometry(g, h))
+    }
+  }
+};
+mxVertexHandler.prototype.union = function(a, b, c, d, e, f, g) {
+  if (this.singleSizer) {
+    g = a.x + a.width + b;
+    var h = a.y + a.height + c;
+    e && (g = this.graph.snap(g / f) * f, h = this.graph.snap(h / f) * f);
+    f = new mxRectangle(a.x, a.y, 0, 0);
+    f.add(new mxRectangle(g, h, 0, 0));
+    return f
+  }
+  var h = a.x - g.x * f,
+    k = h + a.width,
+    l = a.y - g.y * f;
+  a = l + a.height;
+  4 < d ? (a += c, e && (a = this.graph.snap(a / f) * f)) : 3 > d && (l += c, e && (l = this.graph.snap(l / f) * f));
+  if (0 == d || 3 == d || 5 == d) h += b, e && (h = this.graph.snap(h / f) * f);
+  else if (2 == d || 4 == d || 7 == d) k += b, e && (k = this.graph.snap(k / f) * f);
+  e = k - h;
+  a -=
+    l;
+  0 > e && (h += e, e = Math.abs(e));
+  0 > a && (l += a, a = Math.abs(a));
+  return new mxRectangle(h + g.x * f, l + g.y * f, e, a)
+};
+mxVertexHandler.prototype.redraw = function() {
+  this.selectionBounds = this.getSelectionBounds(this.state);
+  this.bounds = new mxRectangle(this.state.x, this.state.y, this.state.width, this.state.height);
+  var a = this.state;
+  if (null != this.sizers) {
+    var b = a.x + a.width,
+      c = a.y + a.height;
+    if (this.singleSizer) this.moveSizerTo(this.sizers[0], b, c);
+    else {
+      var d = a.x + a.width / 2,
+        e = a.y + a.height / 2;
+      if (1 < this.sizers.length) {
+        var f = mxUtils.toRadians(a.style[mxConstants.STYLE_ROTATION] || "0"),
+          g = Math.cos(f),
+          f = Math.sin(f),
+          h = new mxPoint(a.getCenterX(),
+            a.getCenterY()),
+          k = mxUtils.getRotatedPoint(new mxPoint(a.x, a.y), g, f, h);
+        this.moveSizerTo(this.sizers[0], k.x, k.y);
+        k.x = d;
+        k.y = a.y;
+        k = mxUtils.getRotatedPoint(k, g, f, h);
+        this.moveSizerTo(this.sizers[1], k.x, k.y);
+        k.x = b;
+        k.y = a.y;
+        k = mxUtils.getRotatedPoint(k, g, f, h);
+        this.moveSizerTo(this.sizers[2], k.x, k.y);
+        k.x = a.x;
+        k.y = e;
+        k = mxUtils.getRotatedPoint(k, g, f, h);
+        this.moveSizerTo(this.sizers[3], k.x, k.y);
+        k.x = b;
+        k.y = e;
+        k = mxUtils.getRotatedPoint(k, g, f, h);
+        this.moveSizerTo(this.sizers[4], k.x, k.y);
+        k.x = a.x;
+        k.y = c;
+        k = mxUtils.getRotatedPoint(k,
+          g, f, h);
+        this.moveSizerTo(this.sizers[5], k.x, k.y);
+        k.x = d;
+        k.y = c;
+        k = mxUtils.getRotatedPoint(k, g, f, h);
+        this.moveSizerTo(this.sizers[6], k.x, k.y);
+        k.x = b;
+        k.y = c;
+        k = mxUtils.getRotatedPoint(k, g, f, h);
+        this.moveSizerTo(this.sizers[7], k.x, k.y);
+        this.moveSizerTo(this.sizers[8], d + a.absoluteOffset.x, e + a.absoluteOffset.y)
+      } else 2 <= this.state.width && 2 <= this.state.height ? this.moveSizerTo(this.sizers[0], d + a.absoluteOffset.x, e + a.absoluteOffset.y) : this.moveSizerTo(this.sizers[0], a.x, a.y)
+    }
+  }
+  null != this.rotationShape && (f = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] ||
+      "0"), g = Math.cos(f), f = Math.sin(f), h = new mxPoint(this.state.getCenterX(), this.state.getCenterY()), k = mxUtils.getRotatedPoint(new mxPoint(a.x + a.width / 2, a.y - 16), g, f, h),
+    this.moveSizerTo(this.rotationShape, k.x, k.y));
+  this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || "0");
+  this.drawPreview()
+};
+mxVertexHandler.prototype.drawPreview = function() {
+  null != this.preview && (this.preview.bounds = this.bounds, this.preview.node.parentNode == this.graph.container && (this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1), this.preview
+    .bounds.height = Math.max(0, this.preview.bounds.height - 1)), this.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || "0"), this.preview.redraw());
+  this.selectionBorder.bounds = this.bounds;
+  this.selectionBorder.redraw()
+};
+mxVertexHandler.prototype.destroy = function() {
+  null != this.preview && (this.preview.destroy(), this.preview = null);
+  this.selectionBorder.destroy();
+  this.labelShape = this.selectionBorder = null;
+  if (null != this.sizers)
+    for (var a = 0; a < this.sizers.length; a++) this.sizers[a].destroy(), this.sizers[a] = null
+};
+
+function mxEdgeHandler(a) {
+  null != a && (this.state = a, this.init())
+}
+mxEdgeHandler.prototype.graph = null;
+mxEdgeHandler.prototype.state = null;
+mxEdgeHandler.prototype.marker = null;
+mxEdgeHandler.prototype.constraintHandler = null;
+mxEdgeHandler.prototype.error = null;
+mxEdgeHandler.prototype.shape = null;
+mxEdgeHandler.prototype.bends = null;
+mxEdgeHandler.prototype.labelShape = null;
+mxEdgeHandler.prototype.cloneEnabled = !0;
+mxEdgeHandler.prototype.addEnabled = !1;
+mxEdgeHandler.prototype.removeEnabled = !1;
+mxEdgeHandler.prototype.preferHtml = !1;
+mxEdgeHandler.prototype.allowHandleBoundsCheck = !0;
+mxEdgeHandler.prototype.snapToTerminals = !1;
+mxEdgeHandler.prototype.handleImage = null;
+mxEdgeHandler.prototype.tolerance = 0;
+mxEdgeHandler.prototype.init = function() {
+  this.graph = this.state.view.graph;
+  this.marker = this.createMarker();
+  this.constraintHandler = new mxConstraintHandler(this.graph);
+  this.points = [];
+  this.abspoints = this.getSelectionPoints(this.state);
+  this.shape = this.createSelectionShape(this.abspoints);
+  this.shape.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+  this.shape.init(this.graph.getView().getOverlayPane());
+  this.shape.svgStrokeTolerance = 0;
+  this.shape.node.style.cursor =
+    mxConstants.CURSOR_MOVABLE_EDGE;
+  mxEvent.addListener(this.shape.node, "dblclick", mxUtils.bind(this, function(a) {
+    this.graph.dblClick(a, this.state.cell)
+  }));
+  mxEvent.addGestureListeners(this.shape.node, mxUtils.bind(this, function(a) {
+    this.addEnabled && this.isAddPointEvent(a) ? this.addPoint(this.state, a) : this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(a, this.state))
+  }), mxUtils.bind(this, function(a) {
+    var c = this.state.cell;
+    if (null != this.index) {
+      var d = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(a),
+          mxEvent.getClientY(a)),
+        c = this.graph.getCellAt(d.x, d.y);
+      this.graph.isSwimlane(c) && this.graph.hitsSwimlaneContent(c, d.x, d.y) && (c = null)
+    }
+    this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(a, this.graph.getView().getState(c)))
+  }), mxUtils.bind(this, function(a) {
+    this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(a, this.state))
+  }));
+  this.preferHtml = null != this.state.text && this.state.text.node.parentNode == this.graph.container;
+  if (!this.preferHtml) {
+    var a = this.state.getVisibleTerminalState(!0);
+    null != a && (this.preferHtml = null != a.text && a.text.node.parentNode == this.graph.container);
+    this.preferHtml || (a = this.state.getVisibleTerminalState(!1), null != a && (this.preferHtml = null != a.text && a.text.node.parentNode == this.graph.container))
+  }
+  if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells || 0 >= mxGraphHandler.prototype.maxCells) this.bends = this.createBends();
+  this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+  this.labelShape = new mxRectangleShape(new mxRectangle,
+    mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+  this.labelShape.pointerEvents = !1;
+  this.initBend(this.labelShape);
+  this.labelShape.node.style.cursor = mxConstants.CURSOR_LABEL_HANDLE;
+  mxEvent.redirectMouseEvents(this.labelShape.node, this.graph, this.state);
+  this.redraw()
+};
+mxEdgeHandler.prototype.isAddPointEvent = function(a) {
+  return mxEvent.isShiftDown(a)
+};
+mxEdgeHandler.prototype.isRemovePointEvent = function(a) {
+  return mxEvent.isShiftDown(a)
+};
+mxEdgeHandler.prototype.getSelectionPoints = function(a) {
+  return a.absolutePoints
+};
+mxEdgeHandler.prototype.createSelectionShape = function(a) {
+  a = new mxPolyline(a, this.getSelectionColor());
+  a.strokewidth = this.getSelectionStrokeWidth();
+  a.isDashed = this.isSelectionDashed();
+  return a
+};
+mxEdgeHandler.prototype.getSelectionColor = function() {
+  return mxConstants.EDGE_SELECTION_COLOR
+};
+mxEdgeHandler.prototype.getSelectionStrokeWidth = function() {
+  return mxConstants.EDGE_SELECTION_STROKEWIDTH
+};
+mxEdgeHandler.prototype.isSelectionDashed = function() {
+  return mxConstants.EDGE_SELECTION_DASHED
+};
+mxEdgeHandler.prototype.isConnectableCell = function(a) {
+  return !0
+};
+mxEdgeHandler.prototype.createMarker = function() {
+  var a = new mxCellMarker(this.graph),
+    b = this;
+  a.getCell = function(a) {
+    var d = mxCellMarker.prototype.getCell.apply(this, arguments);
+    if (!b.isConnectableCell(d)) return null;
+    var e = b.graph.getModel();
+    if (d == b.state.cell || null != d && !b.graph.connectableEdges && e.isEdge(d)) d = null;
+    return d
+  };
+  a.isValidState = function(a) {
+    var d = b.graph.getModel(),
+      d = b.graph.view.getTerminalPort(a, b.graph.view.getState(d.getTerminal(b.state.cell, !b.isSource)), !b.isSource),
+      d = null != d ? d.cell : null;
+    b.error = b.validateConnection(b.isSource ? a.cell : d, b.isSource ? d : a.cell);
+    return null == b.error
+  };
+  return a
+};
+mxEdgeHandler.prototype.validateConnection = function(a, b) {
+  return this.graph.getEdgeValidationError(this.state.cell, a, b)
+};
+mxEdgeHandler.prototype.createBends = function() {
+  for (var a = this.state.cell, b = [], c = 0; c < this.abspoints.length; c++)
+    if (this.isHandleVisible(c)) {
+      var d = c == this.abspoints.length - 1;
+      if ((d = 0 == c || d) || this.graph.isCellBendable(a)) {
+        var e = this.createHandleShape(c);
+        this.initBend(e);
+        mxClient.IS_TOUCH && e.node.setAttribute("pointer-events", "none");
+        if (this.isHandleEnabled(c))
+          if (mxClient.IS_TOUCH) {
+            var f = mxUtils.bind(this, function(a) {
+              a = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(a), mxEvent.getClientY(a));
+              return this.graph.view.getState(this.graph.getCellAt(a.x, a.y))
+            });
+            mxEvent.redirectMouseEvents(e.node, this.graph, f)
+          } else e.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE, mxEvent.redirectMouseEvents(e.node, this.graph, this.state);
+        b.push(e);
+        d || (this.points.push(new mxPoint(0, 0)), e.node.style.visibility = "hidden")
+      }
+    }
+  return b
+};
+mxEdgeHandler.prototype.isHandleEnabled = function(a) {
+  return !0
+};
+mxEdgeHandler.prototype.isHandleVisible = function(a) {
+  return !0
+};
+mxEdgeHandler.prototype.createHandleShape = function(a) {
+  if (null != this.handleImage) return new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);
+  a = mxConstants.HANDLE_SIZE;
+  this.preferHtml && (a -= 1);
+  return new mxRectangleShape(new mxRectangle(0, 0, a, a), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR)
+};
+mxEdgeHandler.prototype.initBend = function(a) {
+  this.preferHtml ? (a.dialect = mxConstants.DIALECT_STRICTHTML, a.init(this.graph.container)) : (a.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML :
+    mxConstants.DIALECT_SVG, a.init(this.graph.getView().getOverlayPane()))
+};
+mxEdgeHandler.prototype.getHandleForEvent = function(a) {
+  if (null != this.bends)
+    for (var b = this.tolerance, b = this.allowHandleBoundsCheck && (mxClient.IS_IE || 0 < b) ? new mxRectangle(a.getGraphX() - b, a.getGraphY() - b, 2 * b, 2 * b) : null, c = 0; c < this.bends.length; c++)
+      if ((a.isSource(this.bends[c]) || null != b && mxUtils.intersects(this.bends[c].bounds, b)) && "hidden" != this.bends[c].node.style.visibility) return c;
+  if (a.isSource(this.labelShape) && "hidden" != this.labelShape.node.style.visibility || a.isSource(this.state.text))
+    if (!mxClient.IS_SF &&
+      !mxClient.IS_GC || "SELECT" != a.getSource().nodeName) return mxEvent.LABEL_HANDLE;
+  return null
+};
+mxEdgeHandler.prototype.mouseDown = function(a, b) {
+  var c = null,
+    c = this.getHandleForEvent(b);
+  null != c && (!b.isConsumed() && this.graph.isEnabled() && !this.graph.isForceMarqueeEvent(b.getEvent())) && (this.removeEnabled && this.isRemovePointEvent(b.getEvent()) ? this.removePoint(this.state,
+    c) : (c != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(b.getCell())) && this.start(b.getX(), b.getY(), c), b.consume())
+};
+mxEdgeHandler.prototype.start = function(a, b, c) {
+  this.startX = a;
+  this.startY = b;
+  this.isSource = null == this.bends ? !1 : 0 == c;
+  this.isTarget = null == this.bends ? !1 : c == this.bends.length - 1;
+  this.isLabel = c == mxEvent.LABEL_HANDLE;
+  if (this.isSource || this.isTarget) {
+    if (a = this.state.cell, b = this.graph.model.getTerminal(a, this.isSource), null == b && this.graph.isTerminalPointMovable(a, this.isSource) || null != b && this.graph.isCellDisconnectable(a,
+        b, this.isSource)) this.index = c
+  } else this.index = c
+};
+mxEdgeHandler.prototype.clonePreviewState = function(a, b) {
+  return this.state.clone()
+};
+mxEdgeHandler.prototype.getSnapToTerminalTolerance = function() {
+  return this.graph.gridSize * this.graph.view.scale / 2
+};
+mxEdgeHandler.prototype.getPointForEvent = function(a) {
+  var b = new mxPoint(a.getGraphX(), a.getGraphY()),
+    c = this.getSnapToTerminalTolerance(),
+    d = this.graph.getView(),
+    e = !1,
+    f = !1;
+  if (this.snapToTerminals && 0 < c) {
+    var g = function(a) {
+        if (null != a) {
+          var d = a.x;
+          Math.abs(b.x - d) < c && (b.x = d, e = !0);
+          a = a.y;
+          Math.abs(b.y - a) < c && (b.y = a, f = !0)
+        }
+      },
+      h = function(a) {
+        null != a && g.call(this, new mxPoint(d.getRoutingCenterX(a), d.getRoutingCenterY(a)))
+      };
+    h.call(this, this.state.getVisibleTerminalState(!0));
+    h.call(this, this.state.getVisibleTerminalState(!1));
+    if (null != this.abspoints)
+      for (h = 0; h < this.abspoints; h++) h != this.index && g.call(this, this.abspoints[h])
+  }
+  this.graph.isGridEnabledEvent(a.getEvent()) && (a = d.scale, h = d.translate, e || (b.x = (this.graph.snap(b.x / a - h.x) + h.x) * a), f || (b.y = (this.graph.snap(b.y / a - h.y) + h.y) * a));
+  return b
+};
+mxEdgeHandler.prototype.getPreviewTerminalState = function(a) {
+  this.constraintHandler.update(a, this.isSource);
+  this.marker.process(a);
+  a = this.marker.getValidState();
+  var b = null;
+  null != this.constraintHandler.currentFocus && null != this.constraintHandler.currentConstraint && this.marker.reset();
+  null != a ? b = a : null != this.constraintHandler.currentConstraint && null != this.constraintHandler.currentFocus && (b = this.constraintHandler.currentFocus);
+  return b
+};
+mxEdgeHandler.prototype.getPreviewPoints = function(a) {
+  var b = this.graph.getCellGeometry(this.state.cell),
+    b = null != b.points ? b.points.slice() : null;
+  !this.isSource && !this.isTarget ? (this.convertPoint(a, !1), null == b ? b = [a] : b[this.index - 1] = a) : this.graph.resetEdgesOnConnect && (b = null);
+  return b
+};
+mxEdgeHandler.prototype.updatePreviewState = function(a, b, c) {
+  var d = this.isSource ? c : this.state.getVisibleTerminalState(!0),
+    e = this.isTarget ? c : this.state.getVisibleTerminalState(!1),
+    f = this.graph.getConnectionConstraint(a, d, !0),
+    g = this.graph.getConnectionConstraint(a, e, !1),
+    h = this.constraintHandler.currentConstraint;
+  null == h && (h = new mxConnectionConstraint);
+  this.isSource ? f = h : this.isTarget && (g = h);
+  (!this.isSource || null != d) && a.view.updateFixedTerminalPoint(a, d, !0, f);
+  (!this.isTarget || null != e) && a.view.updateFixedTerminalPoint(a,
+    e, !1, g);
+  if ((this.isSource || this.isTarget) && null == c) a.setAbsoluteTerminalPoint(b, this.isSource), null == this.marker.getMarkedState() && (this.error = this.graph.allowDanglingEdges ? null : "");
+  a.view.updatePoints(a, this.points, d, e);
+  a.view.updateFloatingTerminalPoints(a, d, e)
+};
+mxEdgeHandler.prototype.mouseMove = function(a, b) {
+  if (null != this.index && null != this.marker) {
+    var c = this.getPointForEvent(b);
+    if (this.isLabel) this.label.x = c.x, this.label.y = c.y;
+    else {
+      this.points = this.getPreviewPoints(c);
+      var d = this.isSource || this.isTarget ? this.getPreviewTerminalState(b) : null,
+        e = this.clonePreviewState(c, null != d ? d.cell : null);
+      this.updatePreviewState(e, c, d);
+      this.setPreviewColor(null == this.error ? this.marker.validColor : this.marker.invalidColor);
+      this.abspoints = e.absolutePoints;
+      this.active = !0
+    }
+    this.drawPreview();
+    mxEvent.consume(b.getEvent());
+    b.consume()
+  } else mxClient.IS_IE && null != this.getHandleForEvent(b) && b.consume(!1)
+};
+mxEdgeHandler.prototype.mouseUp = function(a, b) {
+  if (null != this.index && null != this.marker) {
+    var c = this.state.cell;
+    if (b.getX() != this.startX || b.getY() != this.startY)
+      if (null != this.error) 0 < this.error.length && this.graph.validationAlert(this.error);
+      else if (this.isLabel) this.moveLabel(this.state, this.label.x, this.label.y);
+    else if (this.isSource || this.isTarget) {
+      var d = null;
+      null != this.constraintHandler.currentConstraint && null != this.constraintHandler.currentFocus && (d = this.constraintHandler.currentFocus.cell);
+      null ==
+        d && this.marker.hasValidState() && (d = this.marker.validState.cell);
+      if (null != d) c = this.connect(c, d, this.isSource, this.graph.isCloneEvent(b.getEvent()) && this.cloneEnabled && this.graph.isCellsCloneable(), b);
+      else if (this.graph.isAllowDanglingEdges()) {
+        d = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1];
+        d.x = d.x / this.graph.view.scale - this.graph.view.translate.x;
+        d.y = d.y / this.graph.view.scale - this.graph.view.translate.y;
+        var e = this.graph.getView().getState(this.graph.getModel().getParent(c));
+        null != e && (d.x -=
+          e.origin.x, d.y -= e.origin.y);
+        d.x -= this.graph.panDx / this.graph.view.scale;
+        d.y -= this.graph.panDy / this.graph.view.scale;
+        this.changeTerminalPoint(c, d, this.isSource)
+      }
+    } else this.active ? this.changePoints(c, this.points) : (this.graph.getView().invalidate(this.state.cell), this.graph.getView().revalidate(this.state.cell));
+    null != this.marker && (this.reset(), c != this.state.cell && this.graph.setSelectionCell(c));
+    b.consume()
+  }
+};
+mxEdgeHandler.prototype.reset = function() {
+  this.points = this.label = this.index = this.error = null;
+  this.isTarget = this.isSource = this.isLabel = this.active = !1;
+  this.marker.reset();
+  this.constraintHandler.reset();
+  this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
+  this.redraw()
+};
+mxEdgeHandler.prototype.setPreviewColor = function(a) {
+  null != this.shape && (this.shape.stroke = a)
+};
+mxEdgeHandler.prototype.convertPoint = function(a, b) {
+  var c = this.graph.getView().getScale(),
+    d = this.graph.getView().getTranslate();
+  b && (a.x = this.graph.snap(a.x), a.y = this.graph.snap(a.y));
+  a.x = Math.round(a.x / c - d.x);
+  a.y = Math.round(a.y / c - d.y);
+  c = this.graph.getView().getState(this.graph.getModel().getParent(this.state.cell));
+  null != c && (a.x -= c.origin.x, a.y -= c.origin.y);
+  return a
+};
+mxEdgeHandler.prototype.moveLabel = function(a, b, c) {
+  var d = this.graph.getModel(),
+    e = d.getGeometry(a.cell);
+  if (null != e) {
+    var e = e.clone(),
+      f = this.graph.getView().getRelativePoint(a, b, c);
+    e.x = f.x;
+    e.y = f.y;
+    var g = this.graph.getView().scale;
+    e.offset = new mxPoint(0, 0);
+    f = this.graph.view.getPoint(a, e);
+    e.offset = new mxPoint((b - f.x) / g, (c - f.y) / g);
+    d.setGeometry(a.cell, e)
+  }
+};
+mxEdgeHandler.prototype.connect = function(a, b, c, d, e) {
+  e = this.graph.getModel();
+  var f = e.getParent(a);
+  e.beginUpdate();
+  try {
+    if (d) {
+      var g = a.clone();
+      e.add(f, g, e.getChildCount(f));
+      var h = e.getTerminal(a, !c);
+      this.graph.connectCell(g, h, !c);
+      a = g
+    }
+    var k = this.constraintHandler.currentConstraint;
+    null == k && (k = new mxConnectionConstraint);
+    this.graph.connectCell(a, b, c, k)
+  } finally {
+    e.endUpdate()
+  }
+  return a
+};
+mxEdgeHandler.prototype.changeTerminalPoint = function(a, b, c) {
+  var d = this.graph.getModel(),
+    e = d.getGeometry(a);
+  if (null != e) {
+    d.beginUpdate();
+    try {
+      e = e.clone(), e.setTerminalPoint(b, c), d.setGeometry(a, e), this.graph.connectCell(a, null, c, new mxConnectionConstraint)
+    } finally {
+      d.endUpdate()
+    }
+  }
+};
+mxEdgeHandler.prototype.changePoints = function(a, b) {
+  var c = this.graph.getModel(),
+    d = c.getGeometry(a);
+  null != d && (d = d.clone(), d.points = b, c.setGeometry(a, d))
+};
+mxEdgeHandler.prototype.addPoint = function(a, b) {
+  var c = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(b), mxEvent.getClientY(b)),
+    d = this.graph.isGridEnabledEvent(b);
+  this.convertPoint(c, d);
+  this.addPointAt(a, c.x, c.y);
+  mxEvent.consume(b)
+};
+mxEdgeHandler.prototype.addPointAt = function(a, b, c) {
+  var d = this.graph.getCellGeometry(a.cell);
+  b = new mxPoint(b, c);
+  if (null != d) {
+    d = d.clone();
+    c = this.graph.view.translate;
+    var e = this.graph.view.scale;
+    c = mxUtils.findNearestSegment(a, (b.x + c.x) * e, (b.y + c.y) * e);
+    null == d.points ? d.points = [b] : d.points.splice(c, 0, b);
+    this.graph.getModel().setGeometry(a.cell, d);
+    this.destroy();
+    this.init()
+  }
+};
+mxEdgeHandler.prototype.removePoint = function(a, b) {
+  if (0 < b && b < this.abspoints.length - 1) {
+    var c = this.graph.getCellGeometry(this.state.cell);
+    null != c && null != c.points && (c = c.clone(), c.points.splice(b - 1, 1), this.graph.getModel().setGeometry(a.cell, c), this.destroy(), this.init())
+  }
+};
+mxEdgeHandler.prototype.getHandleFillColor = function(a) {
+  a = 0 == a;
+  var b = this.state.cell,
+    c = this.graph.getModel().getTerminal(b, a),
+    d = mxConstants.HANDLE_FILLCOLOR;
+  null != c && !this.graph.isCellDisconnectable(b, c, a) || null == c && !this.graph.isTerminalPointMovable(b, a) ? d = mxConstants.LOCKED_HANDLE_FILLCOLOR : null != c && this.graph.isCellDisconnectable(
+    b, c, a) && (d = mxConstants.CONNECT_HANDLE_FILLCOLOR);
+  return d
+};
+mxEdgeHandler.prototype.redraw = function() {
+  this.abspoints = this.state.absolutePoints.slice();
+  var a = this.state.cell,
+    b = mxConstants.LABEL_HANDLE_SIZE;
+  this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+  this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b / 2), Math.round(this.label.y - b / 2), b, b);
+  this.labelShape.redraw();
+  b = this.graph.getLabel(a);
+  null != b && 0 < b.length && this.graph.isLabelMovable(a) ? this.labelShape.node.style.visibility = "visible" : this.labelShape.node.style.visibility =
+    "hidden";
+  if (null != this.bends && 0 < this.bends.length) {
+    var c = this.abspoints.length - 1,
+      a = this.abspoints[0],
+      d = this.abspoints[0].y,
+      b = this.bends[0].bounds;
+    this.bends[0].bounds = new mxRectangle(Math.round(this.abspoints[0].x - b.width / 2), Math.round(d - b.height / 2), b.width, b.height);
+    this.bends[0].fill = this.getHandleFillColor(0);
+    this.bends[0].redraw();
+    var d = this.abspoints[c],
+      e = this.abspoints[c].x,
+      c = this.abspoints[c].y,
+      f = this.bends.length - 1,
+      b = this.bends[f].bounds;
+    this.bends[f].bounds = new mxRectangle(Math.round(e - b.width /
+      2), Math.round(c - b.height / 2), b.width, b.height);
+    this.bends[f].fill = this.getHandleFillColor(f);
+    this.bends[f].redraw();
+    this.redrawInnerBends(a, d)
+  }
+  this.drawPreview()
+};
+mxEdgeHandler.prototype.redrawInnerBends = function(a, b) {
+  var c = this.graph.getModel().getGeometry(this.state.cell).points;
+  if (null != c) {
+    null == this.points && (this.points = []);
+    for (var d = 1; d < this.bends.length - 1; d++)
+      if (null != this.bends[d])
+        if (null != this.abspoints[d]) {
+          var e = this.abspoints[d].x,
+            f = this.abspoints[d].y,
+            g = this.bends[d].bounds;
+          this.bends[d].node.style.visibility = "visible";
+          this.bends[d].bounds = new mxRectangle(Math.round(e - g.width / 2), Math.round(f - g.height / 2), g.width, g.height);
+          this.bends[d].redraw();
+          this.points[d -
+            1] = c[d - 1]
+        } else this.bends[d].destroy(), this.bends[d] = null
+  }
+};
+mxEdgeHandler.prototype.drawPreview = function() {
+  if (this.isLabel) {
+    var a = mxConstants.LABEL_HANDLE_SIZE,
+      a = new mxRectangle(Math.round(this.label.x - a / 2), Math.round(this.label.y - a / 2), a, a);
+    this.labelShape.bounds = a;
+    this.labelShape.redraw()
+  } else this.shape.points = this.abspoints, this.shape.redraw()
+};
+mxEdgeHandler.prototype.destroy = function() {
+  null != this.marker && (this.marker.destroy(), this.marker = null);
+  null != this.shape && (this.shape.destroy(), this.shape = null);
+  null != this.labelShape && (this.labelShape.destroy(), this.labelShape = null);
+  null != this.constraintHandler && (this.constraintHandler.destroy(), this.constraintHandler = null);
+  if (null != this.bends)
+    for (var a = 0; a < this.bends.length; a++) null != this.bends[a] && (this.bends[a].destroy(), this.bends[a] = null)
+};
+
+function mxElbowEdgeHandler(a) {
+  mxEdgeHandler.call(this, a)
+}
+mxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler);
+mxElbowEdgeHandler.prototype = new mxEdgeHandler;
+mxElbowEdgeHandler.prototype.constructor = mxElbowEdgeHandler;
+mxElbowEdgeHandler.prototype.flipEnabled = !0;
+mxElbowEdgeHandler.prototype.doubleClickOrientationResource = "none" != mxClient.language ? "doubleClickOrientation" : "";
+mxElbowEdgeHandler.prototype.createBends = function() {
+  var a = [],
+    b = this.createHandleShape(0);
+  this.initBend(b);
+  b.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+  mxEvent.redirectMouseEvents(b.node, this.graph, this.state);
+  a.push(b);
+  mxClient.IS_TOUCH && b.node.setAttribute("pointer-events", "none");
+  a.push(this.createVirtualBend());
+  this.points.push(new mxPoint(0, 0));
+  b = this.createHandleShape(2);
+  this.initBend(b);
+  b.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+  mxEvent.redirectMouseEvents(b.node, this.graph, this.state);
+  a.push(b);
+  mxClient.IS_TOUCH && b.node.setAttribute("pointer-events", "none");
+  return a
+};
+mxElbowEdgeHandler.prototype.createVirtualBend = function() {
+  var a = this.createHandleShape();
+  this.initBend(a);
+  var b = this.getCursorForBend();
+  a.node.style.cursor = b;
+  b = mxUtils.bind(this, function(a) {
+    !mxEvent.isConsumed(a) && this.flipEnabled && (this.graph.flipEdge(this.state.cell, a), mxEvent.consume(a))
+  });
+  mxEvent.redirectMouseEvents(a.node, this.graph, this.state, null, null, null, b);
+  this.graph.isCellBendable(this.state.cell) || (a.node.style.display = "none");
+  return a
+};
+mxElbowEdgeHandler.prototype.getCursorForBend = function() {
+  return this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom || this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM || (this.state.style[mxConstants.STYLE_EDGE] ==
+      mxEdgeStyle.ElbowConnector || this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW) && this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL ?
+    "row-resize" : "col-resize"
+};
+mxElbowEdgeHandler.prototype.getTooltipForNode = function(a) {
+  var b = null;
+  if (null != this.bends && null != this.bends[1] && (a == this.bends[1].node || a.parentNode == this.bends[1].node)) b = this.doubleClickOrientationResource, b = mxResources.get(b) || b;
+  return b
+};
+mxElbowEdgeHandler.prototype.convertPoint = function(a, b) {
+  var c = this.graph.getView().getScale(),
+    d = this.graph.getView().getTranslate(),
+    e = this.state.origin;
+  b && (a.x = this.graph.snap(a.x), a.y = this.graph.snap(a.y));
+  a.x = Math.round(a.x / c - d.x - e.x);
+  a.y = Math.round(a.y / c - d.y - e.y)
+};
+mxElbowEdgeHandler.prototype.redrawInnerBends = function(a, b) {
+  var c = this.graph.getModel().getGeometry(this.state.cell).points,
+    c = null != c ? c[0] : null,
+    c = null == c ? new mxPoint(a.x + (b.x - a.x) / 2, a.y + (b.y - a.y) / 2) : new mxPoint(this.graph.getView().scale * (c.x + this.graph.getView().translate.x + this.state.origin.x), this.graph.getView()
+      .scale * (c.y + this.graph.getView().translate.y + this.state.origin.y)),
+    d = this.bends[1].bounds,
+    e = d.width,
+    d = d.height;
+  null == this.handleImage && (d = e = mxConstants.HANDLE_SIZE);
+  var f = new mxRectangle(Math.round(c.x -
+    e / 2), Math.round(c.y - d / 2), e, d);
+  null == this.handleImage && ("hidden" != this.labelShape.node.style.visibility && mxUtils.intersects(f, this.labelShape.bounds)) && (e += 3, d += 3, f = new mxRectangle(Math.round(c.x - e / 2),
+    Math.round(c.y - d / 2), e, d));
+  this.bends[1].bounds = f;
+  this.bends[1].redraw()
+};
+
+function mxEdgeSegmentHandler(a) {
+  mxEdgeHandler.call(this, a)
+}
+mxUtils.extend(mxEdgeSegmentHandler, mxEdgeHandler);
+mxEdgeSegmentHandler.prototype = new mxElbowEdgeHandler;
+mxEdgeSegmentHandler.prototype.constructor = mxEdgeSegmentHandler;
+mxEdgeSegmentHandler.prototype.getPreviewPoints = function(a) {
+  if (this.isSource || this.isTarget) return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
+  this.convertPoint(a, !1);
+  var b = this.state.absolutePoints,
+    c = b[0].clone();
+  this.convertPoint(c, !1);
+  for (var d = [], e = 1; e < b.length; e++) {
+    var f = b[e].clone();
+    this.convertPoint(f, !1);
+    e == this.index && (c.x == f.x ? (c.x = a.x, f.x = a.x) : (c.y = a.y, f.y = a.y));
+    e < b.length - 1 && d.push(f);
+    c = f
+  }
+  if (1 == d.length) {
+    if (c = this.state.view, e = this.state.getVisibleTerminalState(!0),
+      f = this.state.getVisibleTerminalState(!1), null != f & null != e) {
+      var g = this.state.origin.x,
+        h = this.state.origin.y;
+      mxUtils.contains(f, d[0].x + g, d[0].y + h) ? b[1].y == b[2].y ? d[0].y = c.getRoutingCenterY(e) - h : d[0].x = c.getRoutingCenterX(e) - g : mxUtils.contains(e, d[0].x + g, d[0].y + h) && (b[
+        1].y == b[0].y ? d[0].y = c.getRoutingCenterY(f) - h : d[0].x = c.getRoutingCenterX(f) - g)
+    }
+  } else 0 == d.length && (d = [a]);
+  return d
+};
+mxEdgeSegmentHandler.prototype.createBends = function() {
+  var a = [],
+    b = this.createHandleShape(0);
+  this.initBend(b);
+  b.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+  mxEvent.redirectMouseEvents(b.node, this.graph, this.state);
+  a.push(b);
+  mxClient.IS_TOUCH && b.node.setAttribute("pointer-events", "none");
+  var c = this.state.absolutePoints;
+  if (this.graph.isCellBendable(this.state.cell)) {
+    null == this.points && (this.points = []);
+    for (var d = 0; d < c.length - 1; d++) b = this.createVirtualBend(), a.push(b), b.node.style.cursor = 0 == c[d].x -
+      c[d + 1].x ? "col-resize" : "row-resize", this.points.push(new mxPoint(0, 0)), mxClient.IS_TOUCH && b.node.setAttribute("pointer-events", "none")
+  }
+  b = this.createHandleShape(c.length);
+  this.initBend(b);
+  b.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+  mxEvent.redirectMouseEvents(b.node, this.graph, this.state);
+  a.push(b);
+  mxClient.IS_TOUCH && b.node.setAttribute("pointer-events", "none");
+  return a
+};
+mxEdgeSegmentHandler.prototype.redraw = function() {
+  this.refresh();
+  mxEdgeHandler.prototype.redraw.apply(this, arguments)
+};
+mxEdgeSegmentHandler.prototype.refresh = function() {
+  if (null != this.bends) {
+    for (var a = 0; a < this.bends.length; a++) null != this.bends[a] && (this.bends[a].destroy(), this.bends[a] = null);
+    this.bends = this.createBends()
+  }
+};
+mxEdgeSegmentHandler.prototype.redrawInnerBends = function(a, b) {
+  if (this.graph.isCellBendable(this.state.cell)) {
+    var c = mxConstants.HANDLE_SIZE,
+      d = this.state.absolutePoints;
+    if (null != d && 1 < d.length)
+      for (var e = 0; e < this.state.absolutePoints.length - 1; e++)
+        if (null != this.bends[e + 1]) {
+          a = d[e];
+          b = d[e + 1];
+          var f = new mxPoint(a.x + (b.x - a.x) / 2, a.y + (b.y - a.y) / 2);
+          this.bends[e + 1].bounds = new mxRectangle(Math.round(f.x - c / 2), Math.round(f.y - c / 2), c, c);
+          this.bends[e + 1].redraw()
+        }
+  }
+};
+mxEdgeSegmentHandler.prototype.changePoints = function(a, b) {
+  b = [];
+  var c = this.abspoints;
+  if (1 < c.length)
+    for (var d = c[0], e = c[1], f = 2; f < c.length; f++) {
+      var g = c[f];
+      if ((Math.round(d.x) != Math.round(e.x) || Math.round(e.x) != Math.round(g.x)) && (Math.round(d.y) != Math.round(e.y) || Math.round(e.y) != Math.round(g.y))) d = e, e = e.clone(), this.convertPoint(
+        e, !1), b.push(e);
+      e = g
+    }
+  mxElbowEdgeHandler.prototype.changePoints.apply(this, arguments)
+};
+
+function mxKeyHandler(a, b) {
+  null != a && (this.graph = a, this.target = b || document.documentElement, this.normalKeys = [], this.shiftKeys = [], this.controlKeys = [], this.controlShiftKeys = [], mxEvent.addListener(this.target,
+    "keydown", mxUtils.bind(this, function(a) {
+      this.keyDown(a)
+    })), mxClient.IS_IE && mxEvent.addListener(window, "unload", mxUtils.bind(this, function() {
+    this.destroy()
+  })))
+}
+mxKeyHandler.prototype.graph = null;
+mxKeyHandler.prototype.target = null;
+mxKeyHandler.prototype.normalKeys = null;
+mxKeyHandler.prototype.shiftKeys = null;
+mxKeyHandler.prototype.controlKeys = null;
+mxKeyHandler.prototype.controlShiftKeys = null;
+mxKeyHandler.prototype.enabled = !0;
+mxKeyHandler.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxKeyHandler.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxKeyHandler.prototype.bindKey = function(a, b) {
+  this.normalKeys[a] = b
+};
+mxKeyHandler.prototype.bindShiftKey = function(a, b) {
+  this.shiftKeys[a] = b
+};
+mxKeyHandler.prototype.bindControlKey = function(a, b) {
+  this.controlKeys[a] = b
+};
+mxKeyHandler.prototype.bindControlShiftKey = function(a, b) {
+  this.controlShiftKeys[a] = b
+};
+mxKeyHandler.prototype.isControlDown = function(a) {
+  return mxEvent.isControlDown(a)
+};
+mxKeyHandler.prototype.getFunction = function(a) {
+  return null != a ? this.isControlDown(a) ? mxEvent.isShiftDown(a) ? this.controlShiftKeys[a.keyCode] : this.controlKeys[a.keyCode] : mxEvent.isShiftDown(a) ? this.shiftKeys[a.keyCode] : this.normalKeys[
+    a.keyCode] : null
+};
+mxKeyHandler.prototype.isGraphEvent = function(a) {
+  a = mxEvent.getSource(a);
+  if (a == this.target || a.parentNode == this.target || null != this.graph.cellEditor && a == this.graph.cellEditor.textarea) return !0;
+  for (; null != a;) {
+    if (a == this.graph.container) return !0;
+    a = a.parentNode
+  }
+  return !1
+};
+mxKeyHandler.prototype.keyDown = function(a) {
+  if (this.graph.isEnabled() && !mxEvent.isConsumed(a) && this.isGraphEvent(a) && this.isEnabled())
+    if (27 == a.keyCode) this.escape(a);
+    else if (!this.graph.isEditing()) {
+    var b = this.getFunction(a);
+    null != b && (b(a), mxEvent.consume(a))
+  }
+};
+mxKeyHandler.prototype.escape = function(a) {
+  this.graph.isEscapeEnabled() && this.graph.escape(a)
+};
+mxKeyHandler.prototype.destroy = function() {
+  this.target = null
+};
+
+function mxTooltipHandler(a, b) {
+  null != a && (this.graph = a, this.delay = b || 500, this.graph.addMouseListener(this))
+}
+mxTooltipHandler.prototype.zIndex = 10005;
+mxTooltipHandler.prototype.graph = null;
+mxTooltipHandler.prototype.delay = null;
+mxTooltipHandler.prototype.hideOnHover = !1;
+mxTooltipHandler.prototype.enabled = !0;
+mxTooltipHandler.prototype.isEnabled = function() {
+  return this.enabled
+};
+mxTooltipHandler.prototype.setEnabled = function(a) {
+  this.enabled = a
+};
+mxTooltipHandler.prototype.isHideOnHover = function() {
+  return this.hideOnHover
+};
+mxTooltipHandler.prototype.setHideOnHover = function(a) {
+  this.hideOnHover = a
+};
+mxTooltipHandler.prototype.init = function() {
+  null != document.body && (this.div = document.createElement("div"), this.div.className = "mxTooltip", this.div.style.visibility = "hidden", this.div.style.zIndex = this.zIndex, document.body.appendChild(
+    this.div), mxEvent.addListener(this.div, "mousedown", mxUtils.bind(this, function(a) {
+    this.hideTooltip()
+  })))
+};
+mxTooltipHandler.prototype.mouseDown = function(a, b) {
+  this.reset(b, !1);
+  this.hideTooltip()
+};
+mxTooltipHandler.prototype.mouseMove = function(a, b) {
+  if (b.getX() != this.lastX || b.getY() != this.lastY) this.reset(b, !0), (this.isHideOnHover() || b.getState() != this.state || b.getSource() != this.node && (!this.stateSource || null != b.getState() &&
+    this.stateSource == (b.isSource(b.getState().shape) || !b.isSource(b.getState().text)))) && this.hideTooltip();
+  this.lastX = b.getX();
+  this.lastY = b.getY()
+};
+mxTooltipHandler.prototype.mouseUp = function(a, b) {
+  this.reset(b, !0);
+  this.hideTooltip()
+};
+mxTooltipHandler.prototype.resetTimer = function() {
+  null != this.thread && (window.clearTimeout(this.thread), this.thread = null)
+};
+mxTooltipHandler.prototype.reset = function(a, b) {
+  this.resetTimer();
+  if (b && this.isEnabled() && null != a.getState() && (null == this.div || "hidden" == this.div.style.visibility)) {
+    var c = a.getState(),
+      d = a.getSource(),
+      e = a.getX(),
+      f = a.getY(),
+      g = a.isSource(c.shape) || a.isSource(c.text);
+    this.thread = window.setTimeout(mxUtils.bind(this, function() {
+      if (!this.graph.isEditing() && !this.graph.panningHandler.isMenuShowing()) {
+        var a = this.graph.getTooltip(c, d, e, f);
+        this.show(a, e, f);
+        this.state = c;
+        this.node = d;
+        this.stateSource = g
+      }
+    }), this.delay)
+  }
+};
+mxTooltipHandler.prototype.hide = function() {
+  this.resetTimer();
+  this.hideTooltip()
+};
+mxTooltipHandler.prototype.hideTooltip = function() {
+  null != this.div && (this.div.style.visibility = "hidden")
+};
+mxTooltipHandler.prototype.show = function(a, b, c) {
+  if (null != a && 0 < a.length) {
+    null == this.div && this.init();
+    var d = mxUtils.getScrollOrigin();
+    this.div.style.left = b + d.x + "px";
+    this.div.style.top = c + mxConstants.TOOLTIP_VERTICAL_OFFSET + d.y + "px";
+    mxUtils.isNode(a) ? (this.div.innerHTML = "", this.div.appendChild(a)) : this.div.innerHTML = a.replace(/\n/g, "\x3cbr\x3e");
+    this.div.style.visibility = "";
+    mxUtils.fit(this.div)
+  }
+};
+mxTooltipHandler.prototype.destroy = function() {
+  this.graph.removeMouseListener(this);
+  mxEvent.release(this.div);
+  null != this.div && null != this.div.parentNode && this.div.parentNode.removeChild(this.div);
+  this.div = null
+};
+
+function mxCellTracker(a, b, c) {
+  mxCellMarker.call(this, a, b);
+  this.graph.addMouseListener(this);
+  null != c && (this.getCell = c);
+  mxClient.IS_IE && mxEvent.addListener(window, "unload", mxUtils.bind(this, function() {
+    this.destroy()
+  }))
+}
+mxUtils.extend(mxCellTracker, mxCellMarker);
+mxCellTracker.prototype.mouseDown = function(a, b) {};
+mxCellTracker.prototype.mouseMove = function(a, b) {
+  this.isEnabled() && this.process(b)
+};
+mxCellTracker.prototype.mouseUp = function(a, b) {
+  this.reset()
+};
+mxCellTracker.prototype.destroy = function() {
+  this.destroyed || (this.destroyed = !0, this.graph.removeMouseListener(this), mxCellMarker.prototype.destroy.apply(this))
+};
+
+function mxCellHighlight(a, b, c, d) {
+  null != a && (this.graph = a, this.highlightColor = null != b ? b : mxConstants.DEFAULT_VALID_COLOR, this.strokeWidth = null != c ? c : mxConstants.HIGHLIGHT_STROKEWIDTH, this.dashed = null != d ?
+    d : !1, this.repaintHandler = mxUtils.bind(this, function() {
+      this.repaint()
+    }), this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler), this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler), this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,
+      this.repaintHandler), this.graph.getModel().addListener(mxEvent.CHANGE,
+      this.repaintHandler), this.resetHandler = mxUtils.bind(this, function() {
+      this.hide()
+    }), this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler), this.graph.getView().addListener(mxEvent.UP, this.resetHandler))
+}
+mxCellHighlight.prototype.keepOnTop = !1;
+mxCellHighlight.prototype.graph = !0;
+mxCellHighlight.prototype.state = null;
+mxCellHighlight.prototype.spacing = 2;
+mxCellHighlight.prototype.resetHandler = null;
+mxCellHighlight.prototype.setHighlightColor = function(a) {
+  this.highlightColor = a;
+  null != this.shape && (this.shape.stroke = a)
+};
+mxCellHighlight.prototype.drawHighlight = function() {
+  this.shape = this.createShape();
+  this.repaint();
+  !this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node && this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild)
+};
+mxCellHighlight.prototype.createShape = function() {
+  var a = null,
+    a = this.graph.model.isEdge(this.state.cell) ? new mxPolyline(this.state.absolutePoints, this.highlightColor, this.strokeWidth) : new mxRectangleShape(new mxRectangle, null, this.highlightColor,
+      this.strokeWidth);
+  a.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+  a.init(this.graph.getView().getOverlayPane());
+  mxEvent.redirectMouseEvents(a.node, this.graph, this.state);
+  a.svgStrokeTolerance = 0;
+  a.pointerEvents = !1;
+  a.isDashed = this.dashed;
+  return a
+};
+mxCellHighlight.prototype.repaint = function() {
+  if (null != this.state && null != this.shape) {
+    var a = 0;
+    this.graph.model.isEdge(this.state.cell) ? this.shape.points = this.state.absolutePoints : (this.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing, this.state
+      .width + 2 * this.spacing, this.state.height + 2 * this.spacing), a = Number(this.state.style[mxConstants.STYLE_ROTATION] || "0"));
+    null != this.state.shape && this.shape.setCursor(this.state.shape.getCursor());
+    0 != a && (this.shape.rotation = a);
+    this.shape.redraw()
+  }
+};
+mxCellHighlight.prototype.hide = function() {
+  this.highlight(null)
+};
+mxCellHighlight.prototype.highlight = function(a) {
+  this.state != a && (null != this.shape && (this.shape.destroy(), this.shape = null), this.state = a, null != this.state && this.drawHighlight())
+};
+mxCellHighlight.prototype.destroy = function() {
+  this.graph.getView().removeListener(this.repaintHandler);
+  this.graph.getModel().removeListener(this.repaintHandler);
+  this.graph.getView().removeListener(this.resetHandler);
+  this.graph.getModel().removeListener(this.resetHandler);
+  null != this.shape && (this.shape.destroy(), this.shape = null)
+};
+
+function mxDefaultKeyHandler(a) {
+  if (null != a) {
+    this.editor = a;
+    this.handler = new mxKeyHandler(a.graph);
+    var b = this.handler.escape;
+    this.handler.escape = function(c) {
+      b.apply(this, arguments);
+      a.hideProperties();
+      a.fireEvent(new mxEventObject(mxEvent.ESCAPE, "event", c))
+    }
+  }
+}
+mxDefaultKeyHandler.prototype.editor = null;
+mxDefaultKeyHandler.prototype.handler = null;
+mxDefaultKeyHandler.prototype.bindAction = function(a, b, c) {
+  var d = mxUtils.bind(this, function() {
+    this.editor.execute(b)
+  });
+  c ? this.handler.bindControlKey(a, d) : this.handler.bindKey(a, d)
+};
+mxDefaultKeyHandler.prototype.destroy = function() {
+  this.handler.destroy();
+  this.handler = null
+};
+
+function mxDefaultPopupMenu(a) {
+  this.config = a
+}
+mxDefaultPopupMenu.prototype.imageBasePath = null;
+mxDefaultPopupMenu.prototype.config = null;
+mxDefaultPopupMenu.prototype.createMenu = function(a, b, c, d) {
+  if (null != this.config) {
+    var e = this.createConditions(a, c, d);
+    this.addItems(a, b, c, d, e, this.config.firstChild, null)
+  }
+};
+mxDefaultPopupMenu.prototype.addItems = function(a, b, c, d, e, f, g) {
+  for (var h = !1; null != f;) {
+    if ("add" == f.nodeName) {
+      var k = f.getAttribute("if");
+      if (null == k || e[k]) {
+        var k = f.getAttribute("as"),
+          k = mxResources.get(k) || k,
+          l = mxUtils.eval(mxUtils.getTextContent(f)),
+          m = f.getAttribute("action"),
+          n = f.getAttribute("icon"),
+          p = f.getAttribute("iconCls");
+        h && (b.addSeparator(g), h = !1);
+        null != n && this.imageBasePath && (n = this.imageBasePath + n);
+        k = this.addAction(b, a, k, n, l, m, c, g, p);
+        this.addItems(a, b, c, d, e, f.firstChild, k)
+      }
+    } else "separator" ==
+      f.nodeName && (h = !0);
+    f = f.nextSibling
+  }
+};
+mxDefaultPopupMenu.prototype.addAction = function(a, b, c, d, e, f, g, h, k) {
+  return a.addItem(c, d, function(a) {
+    "function" == typeof e && e.call(b, b, g, a);
+    null != f && b.execute(f, g, a)
+  }, h, k)
+};
+mxDefaultPopupMenu.prototype.createConditions = function(a, b, c) {
+  var d = a.graph.getModel(),
+    e = d.getChildCount(b),
+    f = [];
+  f.nocell = null == b;
+  f.ncells = 1 < a.graph.getSelectionCount();
+  f.notRoot = d.getRoot() != d.getParent(a.graph.getDefaultParent());
+  f.cell = null != b;
+  d = null != b && 1 == a.graph.getSelectionCount();
+  f.nonEmpty = d && 0 < e;
+  f.expandable = d && a.graph.isCellFoldable(b, !1);
+  f.collapsable = d && a.graph.isCellFoldable(b, !0);
+  f.validRoot = d && a.graph.isValidRoot(b);
+  f.emptyValidRoot = f.validRoot && 0 == e;
+  f.swimlane = d && a.graph.isSwimlane(b);
+  e = this.config.getElementsByTagName("condition");
+  for (d = 0; d < e.length; d++) {
+    var g = mxUtils.eval(mxUtils.getTextContent(e[d])),
+      h = e[d].getAttribute("name");
+    null != h && "function" == typeof g && (f[h] = g(a, b, c))
+  }
+  return f
+};
+
+function mxDefaultToolbar(a, b) {
+  this.editor = b;
+  null != a && null != b && this.init(a)
+}
+mxDefaultToolbar.prototype.editor = null;
+mxDefaultToolbar.prototype.toolbar = null;
+mxDefaultToolbar.prototype.resetHandler = null;
+mxDefaultToolbar.prototype.spacing = 4;
+mxDefaultToolbar.prototype.connectOnDrop = !1;
+mxDefaultToolbar.prototype.init = function(a) {
+  null != a && (this.toolbar = new mxToolbar(a), this.toolbar.addListener(mxEvent.SELECT, mxUtils.bind(this, function(a, c) {
+    var d = c.getProperty("function");
+    this.editor.insertFunction = null != d ? mxUtils.bind(this, function() {
+      d.apply(this, arguments);
+      this.toolbar.resetMode()
+    }) : null
+  })), this.resetHandler = mxUtils.bind(this, function() {
+    null != this.toolbar && this.toolbar.resetMode(!0)
+  }), this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler), this.editor.addListener(mxEvent.ESCAPE,
+    this.resetHandler))
+};
+mxDefaultToolbar.prototype.addItem = function(a, b, c, d) {
+  var e = mxUtils.bind(this, function() {
+    null != c && 0 < c.length && this.editor.execute(c)
+  });
+  return this.toolbar.addItem(a, b, e, d)
+};
+mxDefaultToolbar.prototype.addSeparator = function(a) {
+  a = a || mxClient.imageBasePath + "/separator.gif";
+  this.toolbar.addSeparator(a)
+};
+mxDefaultToolbar.prototype.addCombo = function() {
+  return this.toolbar.addCombo()
+};
+mxDefaultToolbar.prototype.addActionCombo = function(a) {
+  return this.toolbar.addActionCombo(a)
+};
+mxDefaultToolbar.prototype.addActionOption = function(a, b, c) {
+  var d = mxUtils.bind(this, function() {
+    this.editor.execute(c)
+  });
+  this.addOption(a, b, d)
+};
+mxDefaultToolbar.prototype.addOption = function(a, b, c) {
+  return this.toolbar.addOption(a, b, c)
+};
+mxDefaultToolbar.prototype.addMode = function(a, b, c, d, e) {
+  var f = mxUtils.bind(this, function() {
+    this.editor.setMode(c);
+    null != e && e(this.editor)
+  });
+  return this.toolbar.addSwitchMode(a, b, f, d)
+};
+mxDefaultToolbar.prototype.addPrototype = function(a, b, c, d, e, f) {
+  var g = function() {
+      return "function" == typeof c ? c() : null != c ? c.clone() : null
+    },
+    h = mxUtils.bind(this, function(a, b) {
+      "function" == typeof e ? e(this.editor, g(), a, b) : this.drop(g(), a, b);
+      this.toolbar.resetMode();
+      mxEvent.consume(a)
+    });
+  a = this.toolbar.addMode(a, b, h, d, null, f);
+  this.installDropHandler(a, function(a, b, c) {
+    h(b, c)
+  });
+  return a
+};
+mxDefaultToolbar.prototype.drop = function(a, b, c) {
+  var d = this.editor.graph,
+    e = d.getModel();
+  if (null == c || e.isEdge(c) || !this.connectOnDrop || !d.isCellConnectable(c)) {
+    for (; null != c && !d.isValidDropTarget(c, [a], b);) c = e.getParent(c);
+    this.insert(a, b, c)
+  } else this.connect(a, b, c)
+};
+mxDefaultToolbar.prototype.insert = function(a, b, c) {
+  var d = this.editor.graph;
+  if (d.canImportCell(a)) {
+    var e = mxEvent.getClientX(b),
+      f = mxEvent.getClientY(b),
+      e = mxUtils.convertPoint(d.container, e, f);
+    return d.isSplitEnabled() && d.isSplitTarget(c, [a], b) ? d.splitEdge(c, [a], null, e.x, e.y) : this.editor.addVertex(c, a, e.x, e.y)
+  }
+  return null
+};
+mxDefaultToolbar.prototype.connect = function(a, b, c) {
+  b = this.editor.graph;
+  var d = b.getModel();
+  if (null != c && b.isCellConnectable(a) && b.isEdgeValid(null, c, a)) {
+    var e = null;
+    d.beginUpdate();
+    try {
+      var f = d.getGeometry(c),
+        g = d.getGeometry(a).clone();
+      g.x = f.x + (f.width - g.width) / 2;
+      g.y = f.y + (f.height - g.height) / 2;
+      var h = this.spacing * b.gridSize,
+        k = 20 * d.getDirectedEdgeCount(c, !0);
+      this.editor.horizontalFlow ? g.x += (g.width + f.width) / 2 + h + k : g.y += (g.height + f.height) / 2 + h + k;
+      a.setGeometry(g);
+      var l = d.getParent(c);
+      b.addCell(a, l);
+      b.constrainChild(a);
+      e = this.editor.createEdge(c, a);
+      if (null == d.getGeometry(e)) {
+        var m = new mxGeometry;
+        m.relative = !0;
+        d.setGeometry(e, m)
+      }
+      b.addEdge(e, l, c, a)
+    } finally {
+      d.endUpdate()
+    }
+    b.setSelectionCells([a, e]);
+    b.scrollCellToVisible(a)
+  }
+};
+mxDefaultToolbar.prototype.installDropHandler = function(a, b) {
+  var c = document.createElement("img");
+  c.setAttribute("src", a.getAttribute("src"));
+  var d = mxUtils.bind(this, function(e) {
+    c.style.width = 2 * a.offsetWidth + "px";
+    c.style.height = 2 * a.offsetHeight + "px";
+    mxUtils.makeDraggable(a, this.editor.graph, b, c);
+    mxEvent.removeListener(c, "load", d)
+  });
+  mxClient.IS_IE ? d() : mxEvent.addListener(c, "load", d)
+};
+mxDefaultToolbar.prototype.destroy = function() {
+  null != this.resetHandler && (this.editor.graph.removeListener("dblclick", this.resetHandler), this.editor.removeListener("escape", this.resetHandler), this.resetHandler = null);
+  null != this.toolbar && (this.toolbar.destroy(), this.toolbar = null)
+};
+
+function mxEditor(a) {
+  this.actions = [];
+  this.addActions();
+  if (null != document.body) {
+    this.cycleAttributeValues = [];
+    this.popupHandler = new mxDefaultPopupMenu;
+    this.undoManager = new mxUndoManager;
+    this.graph = this.createGraph();
+    this.toolbar = this.createToolbar();
+    this.keyHandler = new mxDefaultKeyHandler(this);
+    this.configure(a);
+    this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;
+    !mxClient.IS_LOCAL && null != this.urlInit && (this.session = this.createSession());
+    if (null != this.onInit) this.onInit();
+    mxClient.IS_IE &&
+      mxEvent.addListener(window, "unload", mxUtils.bind(this, function() {
+        this.destroy()
+      }))
+  }
+}
+mxLoadResources && mxResources.add(mxClient.basePath + "/resources/editor");
+mxEditor.prototype = new mxEventSource;
+mxEditor.prototype.constructor = mxEditor;
+mxEditor.prototype.askZoomResource = "none" != mxClient.language ? "askZoom" : "";
+mxEditor.prototype.lastSavedResource = "none" != mxClient.language ? "lastSaved" : "";
+mxEditor.prototype.currentFileResource = "none" != mxClient.language ? "currentFile" : "";
+mxEditor.prototype.propertiesResource = "none" != mxClient.language ? "properties" : "";
+mxEditor.prototype.tasksResource = "none" != mxClient.language ? "tasks" : "";
+mxEditor.prototype.helpResource = "none" != mxClient.language ? "help" : "";
+mxEditor.prototype.outlineResource = "none" != mxClient.language ? "outline" : "";
+mxEditor.prototype.outline = null;
+mxEditor.prototype.graph = null;
+mxEditor.prototype.graphRenderHint = null;
+mxEditor.prototype.toolbar = null;
+mxEditor.prototype.session = null;
+mxEditor.prototype.status = null;
+mxEditor.prototype.popupHandler = null;
+mxEditor.prototype.undoManager = null;
+mxEditor.prototype.keyHandler = null;
+mxEditor.prototype.actions = null;
+mxEditor.prototype.dblClickAction = "edit";
+mxEditor.prototype.swimlaneRequired = !1;
+mxEditor.prototype.disableContextMenu = !0;
+mxEditor.prototype.insertFunction = null;
+mxEditor.prototype.forcedInserting = !1;
+mxEditor.prototype.templates = null;
+mxEditor.prototype.defaultEdge = null;
+mxEditor.prototype.defaultEdgeStyle = null;
+mxEditor.prototype.defaultGroup = null;
+mxEditor.prototype.groupBorderSize = null;
+mxEditor.prototype.filename = null;
+mxEditor.prototype.linefeed = "\x26#xa;";
+mxEditor.prototype.postParameterName = "xml";
+mxEditor.prototype.escapePostData = !0;
+mxEditor.prototype.urlPost = null;
+mxEditor.prototype.urlImage = null;
+mxEditor.prototype.urlInit = null;
+mxEditor.prototype.urlNotify = null;
+mxEditor.prototype.urlPoll = null;
+mxEditor.prototype.horizontalFlow = !1;
+mxEditor.prototype.layoutDiagram = !1;
+mxEditor.prototype.swimlaneSpacing = 0;
+mxEditor.prototype.maintainSwimlanes = !1;
+mxEditor.prototype.layoutSwimlanes = !1;
+mxEditor.prototype.cycleAttributeValues = null;
+mxEditor.prototype.cycleAttributeIndex = 0;
+mxEditor.prototype.cycleAttributeName = "fillColor";
+mxEditor.prototype.tasks = null;
+mxEditor.prototype.tasksWindowImage = null;
+mxEditor.prototype.tasksTop = 20;
+mxEditor.prototype.help = null;
+mxEditor.prototype.helpWindowImage = null;
+mxEditor.prototype.urlHelp = null;
+mxEditor.prototype.helpWidth = 300;
+mxEditor.prototype.helpHeight = 260;
+mxEditor.prototype.propertiesWidth = 240;
+mxEditor.prototype.propertiesHeight = null;
+mxEditor.prototype.movePropertiesDialog = !1;
+mxEditor.prototype.validating = !1;
+mxEditor.prototype.modified = !1;
+mxEditor.prototype.isModified = function() {
+  return this.modified
+};
+mxEditor.prototype.setModified = function(a) {
+  this.modified = a
+};
+mxEditor.prototype.addActions = function() {
+  this.addAction("save", function(a) {
+    a.save()
+  });
+  this.addAction("print", function(a) {
+    (new mxPrintPreview(a.graph, 1)).open()
+  });
+  this.addAction("show", function(a) {
+    mxUtils.show(a.graph, null, 10, 10)
+  });
+  this.addAction("exportImage", function(a) {
+    var b = a.getUrlImage();
+    if (null == b || mxClient.IS_LOCAL) a.execute("show");
+    else {
+      var c = mxUtils.getViewXml(a.graph, 1),
+        c = mxUtils.getXml(c, "\n");
+      mxUtils.submit(b, a.postParameterName + "\x3d" + encodeURIComponent(c), document, "_blank")
+    }
+  });
+  this.addAction("refresh",
+    function(a) {
+      a.graph.refresh()
+    });
+  this.addAction("cut", function(a) {
+    a.graph.isEnabled() && mxClipboard.cut(a.graph)
+  });
+  this.addAction("copy", function(a) {
+    a.graph.isEnabled() && mxClipboard.copy(a.graph)
+  });
+  this.addAction("paste", function(a) {
+    a.graph.isEnabled() && mxClipboard.paste(a.graph)
+  });
+  this.addAction("delete", function(a) {
+    a.graph.isEnabled() && a.graph.removeCells()
+  });
+  this.addAction("group", function(a) {
+    a.graph.isEnabled() && a.graph.setSelectionCell(a.groupCells())
+  });
+  this.addAction("ungroup", function(a) {
+    a.graph.isEnabled() &&
+      a.graph.setSelectionCells(a.graph.ungroupCells())
+  });
+  this.addAction("removeFromParent", function(a) {
+    a.graph.isEnabled() && a.graph.removeCellsFromParent()
+  });
+  this.addAction("undo", function(a) {
+    a.graph.isEnabled() && a.undo()
+  });
+  this.addAction("redo", function(a) {
+    a.graph.isEnabled() && a.redo()
+  });
+  this.addAction("zoomIn", function(a) {
+    a.graph.zoomIn()
+  });
+  this.addAction("zoomOut", function(a) {
+    a.graph.zoomOut()
+  });
+  this.addAction("actualSize", function(a) {
+    a.graph.zoomActual()
+  });
+  this.addAction("fit", function(a) {
+    a.graph.fit()
+  });
+  this.addAction("showProperties", function(a, b) {
+    a.showProperties(b)
+  });
+  this.addAction("selectAll", function(a) {
+    a.graph.isEnabled() && a.graph.selectAll()
+  });
+  this.addAction("selectNone", function(a) {
+    a.graph.isEnabled() && a.graph.clearSelection()
+  });
+  this.addAction("selectVertices", function(a) {
+    a.graph.isEnabled() && a.graph.selectVertices()
+  });
+  this.addAction("selectEdges", function(a) {
+    a.graph.isEnabled() && a.graph.selectEdges()
+  });
+  this.addAction("edit", function(a, b) {
+    a.graph.isEnabled() && a.graph.isCellEditable(b) && a.graph.startEditingAtCell(b)
+  });
+  this.addAction("toBack", function(a, b) {
+    a.graph.isEnabled() && a.graph.orderCells(!0)
+  });
+  this.addAction("toFront", function(a, b) {
+    a.graph.isEnabled() && a.graph.orderCells(!1)
+  });
+  this.addAction("enterGroup", function(a, b) {
+    a.graph.enterGroup(b)
+  });
+  this.addAction("exitGroup", function(a) {
+    a.graph.exitGroup()
+  });
+  this.addAction("home", function(a) {
+    a.graph.home()
+  });
+  this.addAction("selectPrevious", function(a) {
+    a.graph.isEnabled() && a.graph.selectPreviousCell()
+  });
+  this.addAction("selectNext", function(a) {
+    a.graph.isEnabled() &&
+      a.graph.selectNextCell()
+  });
+  this.addAction("selectParent", function(a) {
+    a.graph.isEnabled() && a.graph.selectParentCell()
+  });
+  this.addAction("selectChild", function(a) {
+    a.graph.isEnabled() && a.graph.selectChildCell()
+  });
+  this.addAction("collapse", function(a) {
+    a.graph.isEnabled() && a.graph.foldCells(!0)
+  });
+  this.addAction("collapseAll", function(a) {
+    if (a.graph.isEnabled()) {
+      var b = a.graph.getChildVertices();
+      a.graph.foldCells(!0, !1, b)
+    }
+  });
+  this.addAction("expand", function(a) {
+    a.graph.isEnabled() && a.graph.foldCells(!1)
+  });
+  this.addAction("expandAll", function(a) {
+    if (a.graph.isEnabled()) {
+      var b = a.graph.getChildVertices();
+      a.graph.foldCells(!1, !1, b)
+    }
+  });
+  this.addAction("bold", function(a) {
+    a.graph.isEnabled() && a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_BOLD)
+  });
+  this.addAction("italic", function(a) {
+    a.graph.isEnabled() && a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_ITALIC)
+  });
+  this.addAction("underline", function(a) {
+    a.graph.isEnabled() && a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE,
+      mxConstants.FONT_UNDERLINE)
+  });
+  this.addAction("shadow", function(a) {
+    a.graph.isEnabled() && a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_SHADOW)
+  });
+  this.addAction("alignCellsLeft", function(a) {
+    a.graph.isEnabled() && a.graph.alignCells(mxConstants.ALIGN_LEFT)
+  });
+  this.addAction("alignCellsCenter", function(a) {
+    a.graph.isEnabled() && a.graph.alignCells(mxConstants.ALIGN_CENTER)
+  });
+  this.addAction("alignCellsRight", function(a) {
+    a.graph.isEnabled() && a.graph.alignCells(mxConstants.ALIGN_RIGHT)
+  });
+  this.addAction("alignCellsTop", function(a) {
+    a.graph.isEnabled() && a.graph.alignCells(mxConstants.ALIGN_TOP)
+  });
+  this.addAction("alignCellsMiddle", function(a) {
+    a.graph.isEnabled() && a.graph.alignCells(mxConstants.ALIGN_MIDDLE)
+  });
+  this.addAction("alignCellsBottom", function(a) {
+    a.graph.isEnabled() && a.graph.alignCells(mxConstants.ALIGN_BOTTOM)
+  });
+  this.addAction("alignFontLeft", function(a) {
+    a.graph.setCellStyles(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT)
+  });
+  this.addAction("alignFontCenter", function(a) {
+    a.graph.isEnabled() &&
+      a.graph.setCellStyles(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER)
+  });
+  this.addAction("alignFontRight", function(a) {
+    a.graph.isEnabled() && a.graph.setCellStyles(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_RIGHT)
+  });
+  this.addAction("alignFontTop", function(a) {
+    a.graph.isEnabled() && a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_TOP)
+  });
+  this.addAction("alignFontMiddle", function(a) {
+    a.graph.isEnabled() && a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE)
+  });
+  this.addAction("alignFontBottom", function(a) {
+    a.graph.isEnabled() && a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_BOTTOM)
+  });
+  this.addAction("zoom", function(a) {
+    var b = 100 * a.graph.getView().scale,
+      b = parseFloat(mxUtils.prompt(mxResources.get(a.askZoomResource) || a.askZoomResource, b)) / 100;
+    isNaN(b) || a.graph.getView().setScale(b)
+  });
+  this.addAction("toggleTasks", function(a) {
+    null != a.tasks ? a.tasks.setVisible(!a.tasks.isVisible()) : a.showTasks()
+  });
+  this.addAction("toggleHelp", function(a) {
+    null !=
+      a.help ? a.help.setVisible(!a.help.isVisible()) : a.showHelp()
+  });
+  this.addAction("toggleOutline", function(a) {
+    null == a.outline ? a.showOutline() : a.outline.setVisible(!a.outline.isVisible())
+  });
+  this.addAction("toggleConsole", function(a) {
+    mxLog.setVisible(!mxLog.isVisible())
+  })
+};
+mxEditor.prototype.createSession = function() {
+  var a = mxUtils.bind(this, function(a) {
+    this.fireEvent(new mxEventObject(mxEvent.SESSION, "session", a))
+  });
+  return this.connect(this.urlInit, this.urlPoll, this.urlNotify, a)
+};
+mxEditor.prototype.configure = function(a) {
+  null != a && ((new mxCodec(a.ownerDocument)).decode(a, this), this.resetHistory())
+};
+mxEditor.prototype.resetFirstTime = function() {
+  document.cookie = "mxgraph\x3dseen; expires\x3dFri, 27 Jul 2001 02:47:11 UTC; path\x3d/"
+};
+mxEditor.prototype.resetHistory = function() {
+  this.lastSnapshot = (new Date).getTime();
+  this.undoManager.clear();
+  this.ignoredChanges = 0;
+  this.setModified(!1)
+};
+mxEditor.prototype.addAction = function(a, b) {
+  this.actions[a] = b
+};
+mxEditor.prototype.execute = function(a, b, c) {
+  var d = this.actions[a];
+  if (null != d) try {
+    var e = arguments;
+    e[0] = this;
+    d.apply(this, e)
+  } catch (f) {
+    throw mxUtils.error("Cannot execute " + a + ": " + f.message, 280, !0), f;
+  } else mxUtils.error("Cannot find action " + a, 280, !0)
+};
+mxEditor.prototype.addTemplate = function(a, b) {
+  this.templates[a] = b
+};
+mxEditor.prototype.getTemplate = function(a) {
+  return this.templates[a]
+};
+mxEditor.prototype.createGraph = function() {
+  var a = new mxGraph(null, null, this.graphRenderHint);
+  a.setTooltips(!0);
+  a.setPanning(!0);
+  this.installDblClickHandler(a);
+  this.installUndoHandler(a);
+  this.installDrillHandler(a);
+  this.installChangeHandler(a);
+  this.installInsertHandler(a);
+  a.panningHandler.factoryMethod = mxUtils.bind(this, function(a, c, d) {
+    return this.createPopupMenu(a, c, d)
+  });
+  a.connectionHandler.factoryMethod = mxUtils.bind(this, function(a, c) {
+    return this.createEdge(a, c)
+  });
+  this.createSwimlaneManager(a);
+  this.createLayoutManager(a);
+  return a
+};
+mxEditor.prototype.createSwimlaneManager = function(a) {
+  a = new mxSwimlaneManager(a, !1);
+  a.isHorizontal = mxUtils.bind(this, function() {
+    return this.horizontalFlow
+  });
+  a.isEnabled = mxUtils.bind(this, function() {
+    return this.maintainSwimlanes
+  });
+  return a
+};
+mxEditor.prototype.createLayoutManager = function(a) {
+  var b = new mxLayoutManager(a),
+    c = this;
+  b.getLayout = function(b) {
+    var e = null,
+      f = c.graph.getModel();
+    if (null != f.getParent(b))
+      if (c.layoutSwimlanes && a.isSwimlane(b)) null == c.swimlaneLayout && (c.swimlaneLayout = c.createSwimlaneLayout()), e = c.swimlaneLayout;
+      else if (c.layoutDiagram && (a.isValidRoot(b) || null == f.getParent(f.getParent(b)))) null == c.diagramLayout && (c.diagramLayout = c.createDiagramLayout()), e = c.diagramLayout;
+    return e
+  };
+  return b
+};
+mxEditor.prototype.setGraphContainer = function(a) {
+  null == this.graph.container && (this.graph.init(a), this.rubberband = new mxRubberband(this.graph), this.disableContextMenu && mxEvent.disableContextMenu(a), mxClient.IS_QUIRKS && new mxDivResizer(
+    a))
+};
+mxEditor.prototype.installDblClickHandler = function(a) {
+  a.addListener(mxEvent.DOUBLE_CLICK, mxUtils.bind(this, function(b, c) {
+    var d = c.getProperty("cell");
+    null != d && (a.isEnabled() && null != this.dblClickAction) && (this.execute(this.dblClickAction, d), c.consume())
+  }))
+};
+mxEditor.prototype.installUndoHandler = function(a) {
+  var b = mxUtils.bind(this, function(a, b) {
+    var e = b.getProperty("edit");
+    this.undoManager.undoableEditHappened(e)
+  });
+  a.getModel().addListener(mxEvent.UNDO, b);
+  a.getView().addListener(mxEvent.UNDO, b);
+  b = function(b, d) {
+    var e = d.getProperty("edit").changes;
+    a.setSelectionCells(a.getSelectionCellsForChanges(e))
+  };
+  this.undoManager.addListener(mxEvent.UNDO, b);
+  this.undoManager.addListener(mxEvent.REDO, b)
+};
+mxEditor.prototype.installDrillHandler = function(a) {
+  var b = mxUtils.bind(this, function(a) {
+    this.fireEvent(new mxEventObject(mxEvent.ROOT))
+  });
+  a.getView().addListener(mxEvent.DOWN, b);
+  a.getView().addListener(mxEvent.UP, b)
+};
+mxEditor.prototype.installChangeHandler = function(a) {
+  var b = mxUtils.bind(this, function(b, d) {
+    this.setModified(!0);
+    !0 == this.validating && a.validateGraph();
+    for (var e = d.getProperty("edit").changes, f = 0; f < e.length; f++) {
+      var g = e[f];
+      if (g instanceof mxRootChange || g instanceof mxValueChange && g.cell == this.graph.model.root || g instanceof mxCellAttributeChange && g.cell == this.graph.model.root) {
+        this.fireEvent(new mxEventObject(mxEvent.ROOT));
+        break
+      }
+    }
+  });
+  a.getModel().addListener(mxEvent.CHANGE, b)
+};
+mxEditor.prototype.installInsertHandler = function(a) {
+  var b = this;
+  a.addMouseListener({
+    mouseDown: function(a, d) {
+      if (null != b.insertFunction && !d.isPopupTrigger() && (b.forcedInserting || null == d.getState())) b.graph.clearSelection(), b.insertFunction(d.getEvent(), d.getCell()), this.isActive = !
+        0, d.consume()
+    },
+    mouseMove: function(a, b) {
+      this.isActive && b.consume()
+    },
+    mouseUp: function(a, b) {
+      this.isActive && (this.isActive = !1, b.consume())
+    }
+  })
+};
+mxEditor.prototype.createDiagramLayout = function() {
+  var a = this.graph.gridSize,
+    b = new mxStackLayout(this.graph, !this.horizontalFlow, this.swimlaneSpacing, 2 * a, 2 * a);
+  b.isVertexIgnored = function(a) {
+    return !b.graph.isSwimlane(a)
+  };
+  return b
+};
+mxEditor.prototype.createSwimlaneLayout = function() {
+  return new mxCompactTreeLayout(this.graph, this.horizontalFlow)
+};
+mxEditor.prototype.createToolbar = function() {
+  return new mxDefaultToolbar(null, this)
+};
+mxEditor.prototype.setToolbarContainer = function(a) {
+  this.toolbar.init(a);
+  mxClient.IS_QUIRKS && new mxDivResizer(a)
+};
+mxEditor.prototype.setStatusContainer = function(a) {
+  null == this.status && (this.status = a, this.addListener(mxEvent.SAVE, mxUtils.bind(this, function() {
+    var a = (new Date).toLocaleString();
+    this.setStatus((mxResources.get(this.lastSavedResource) || this.lastSavedResource) + ": " + a)
+  })), this.addListener(mxEvent.OPEN, mxUtils.bind(this, function() {
+    this.setStatus((mxResources.get(this.currentFileResource) || this.currentFileResource) + ": " + this.filename)
+  })), mxClient.IS_QUIRKS && new mxDivResizer(a))
+};
+mxEditor.prototype.setStatus = function(a) {
+  null != this.status && null != a && (this.status.innerHTML = a)
+};
+mxEditor.prototype.setTitleContainer = function(a) {
+  this.addListener(mxEvent.ROOT, mxUtils.bind(this, function(b) {
+    a.innerHTML = this.getTitle()
+  }));
+  mxClient.IS_QUIRKS && new mxDivResizer(a)
+};
+mxEditor.prototype.treeLayout = function(a, b) {
+  null != a && (new mxCompactTreeLayout(this.graph, b)).execute(a)
+};
+mxEditor.prototype.getTitle = function() {
+  for (var a = "", b = this.graph, c = b.getCurrentRoot(); null != c && null != b.getModel().getParent(b.getModel().getParent(c));) b.isValidRoot(c) && (a = " \x3e " + b.convertValueToString(c) + a),
+    c = b.getModel().getParent(c);
+  return this.getRootTitle() + a
+};
+mxEditor.prototype.getRootTitle = function() {
+  var a = this.graph.getModel().getRoot();
+  return this.graph.convertValueToString(a)
+};
+mxEditor.prototype.undo = function() {
+  this.undoManager.undo()
+};
+mxEditor.prototype.redo = function() {
+  this.undoManager.redo()
+};
+mxEditor.prototype.groupCells = function() {
+  var a = null != this.groupBorderSize ? this.groupBorderSize : this.graph.gridSize;
+  return this.graph.groupCells(this.createGroup(), a)
+};
+mxEditor.prototype.createGroup = function() {
+  return this.graph.getModel().cloneCell(this.defaultGroup)
+};
+mxEditor.prototype.open = function(a) {
+  if (null != a) {
+    var b = mxUtils.load(a).getXml();
+    this.readGraphModel(b.documentElement);
+    this.filename = a;
+    this.fireEvent(new mxEventObject(mxEvent.OPEN, "filename", a))
+  }
+};
+mxEditor.prototype.readGraphModel = function(a) {
+  (new mxCodec(a.ownerDocument)).decode(a, this.graph.getModel());
+  this.resetHistory()
+};
+mxEditor.prototype.save = function(a, b) {
+  a = a || this.getUrlPost();
+  if (null != a && 0 < a.length) {
+    var c = this.writeGraphModel(b);
+    this.postDiagram(a, c);
+    this.setModified(!1)
+  }
+  this.fireEvent(new mxEventObject(mxEvent.SAVE, "url", a))
+};
+mxEditor.prototype.postDiagram = function(a, b) {
+  this.escapePostData && (b = encodeURIComponent(b));
+  mxUtils.post(a, this.postParameterName + "\x3d" + b, mxUtils.bind(this, function(c) {
+    this.fireEvent(new mxEventObject(mxEvent.POST, "request", c, "url", a, "data", b))
+  }))
+};
+mxEditor.prototype.writeGraphModel = function(a) {
+  a = null != a ? a : this.linefeed;
+  var b = (new mxCodec).encode(this.graph.getModel());
+  return mxUtils.getXml(b, a)
+};
+mxEditor.prototype.getUrlPost = function() {
+  return this.urlPost
+};
+mxEditor.prototype.getUrlImage = function() {
+  return this.urlImage
+};
+mxEditor.prototype.connect = function(a, b, c, d) {
+  var e = null;
+  mxClient.IS_LOCAL || (e = new mxSession(this.graph.getModel(), a, b, c), e.addListener(mxEvent.RECEIVE, mxUtils.bind(this, function(a, b) {
+    null != b.getProperty("node").getAttribute("namespace") && this.resetHistory()
+  })), e.addListener(mxEvent.DISCONNECT, d), e.addListener(mxEvent.CONNECT, d), e.addListener(mxEvent.NOTIFY, d), e.addListener(mxEvent.GET, d), e.start());
+  return e
+};
+mxEditor.prototype.swapStyles = function(a, b) {
+  var c = this.graph.getStylesheet().styles[b];
+  this.graph.getView().getStylesheet().putCellStyle(b, this.graph.getStylesheet().styles[a]);
+  this.graph.getStylesheet().putCellStyle(a, c);
+  this.graph.refresh()
+};
+mxEditor.prototype.showProperties = function(a) {
+  a = a || this.graph.getSelectionCell();
+  null == a && (a = this.graph.getCurrentRoot(), null == a && (a = this.graph.getModel().getRoot()));
+  if (null != a) {
+    this.graph.stopEditing(!0);
+    var b = mxUtils.getOffset(this.graph.container),
+      c = b.x + 10,
+      b = b.y;
+    if (null != this.properties && !this.movePropertiesDialog) c = this.properties.getX(), b = this.properties.getY();
+    else {
+      var d = this.graph.getCellBounds(a);
+      null != d && (c += d.x + Math.min(200, d.width), b += d.y)
+    }
+    this.hideProperties();
+    a = this.createProperties(a);
+    null != a && (this.properties = new mxWindow(mxResources.get(this.propertiesResource) || this.propertiesResource, a, c, b, this.propertiesWidth, this.propertiesHeight, !1), this.properties.setVisible(!
+      0))
+  }
+};
+mxEditor.prototype.isPropertiesVisible = function() {
+  return null != this.properties
+};
+mxEditor.prototype.createProperties = function(a) {
+  var b = this.graph.getModel(),
+    c = b.getValue(a);
+  if (mxUtils.isNode(c)) {
+    var d = new mxForm("properties");
+    d.addText("ID", a.getId()).setAttribute("readonly", "true");
+    var e = null,
+      f = null,
+      g = null,
+      h = null,
+      k = null;
+    b.isVertex(a) && (e = b.getGeometry(a), null != e && (f = d.addText("top", e.y), g = d.addText("left", e.x), h = d.addText("width", e.width), k = d.addText("height", e.height)));
+    for (var l = b.getStyle(a), m = d.addText("Style", l || ""), n = c.attributes, p = [], c = 0; c < n.length; c++) p[c] = d.addTextarea(n[c].nodeName,
+      n[c].nodeValue, "label" == n[c].nodeName ? 4 : 2);
+    c = mxUtils.bind(this, function() {
+      this.hideProperties();
+      b.beginUpdate();
+      try {
+        null != e && (e = e.clone(), e.x = parseFloat(g.value), e.y = parseFloat(f.value), e.width = parseFloat(h.value), e.height = parseFloat(k.value), b.setGeometry(a, e));
+        0 < m.value.length ? b.setStyle(a, m.value) : b.setStyle(a, null);
+        for (var c = 0; c < n.length; c++) {
+          var d = new mxCellAttributeChange(a, n[c].nodeName, p[c].value);
+          b.execute(d)
+        }
+        this.graph.isAutoSizeCell(a) && this.graph.updateCellSize(a)
+      } finally {
+        b.endUpdate()
+      }
+    });
+    l = mxUtils.bind(this, function() {
+      this.hideProperties()
+    });
+    d.addButtons(c, l);
+    return d.table
+  }
+  return null
+};
+mxEditor.prototype.hideProperties = function() {
+  null != this.properties && (this.properties.destroy(), this.properties = null)
+};
+mxEditor.prototype.showTasks = function() {
+  if (null == this.tasks) {
+    var a = document.createElement("div");
+    a.style.padding = "4px";
+    a.style.paddingLeft = "20px";
+    var b = document.body.clientWidth,
+      b = new mxWindow(mxResources.get(this.tasksResource) || this.tasksResource, a, b - 220, this.tasksTop, 200);
+    b.setClosable(!0);
+    b.destroyOnClose = !1;
+    var c = mxUtils.bind(this, function(b) {
+      mxEvent.release(a);
+      a.innerHTML = "";
+      this.createTasks(a)
+    });
+    this.graph.getModel().addListener(mxEvent.CHANGE, c);
+    this.graph.getSelectionModel().addListener(mxEvent.CHANGE,
+      c);
+    this.graph.addListener(mxEvent.ROOT, c);
+    null != this.tasksWindowImage && b.setImage(this.tasksWindowImage);
+    this.tasks = b;
+    this.createTasks(a)
+  }
+  this.tasks.setVisible(!0)
+};
+mxEditor.prototype.refreshTasks = function(a) {
+  null != this.tasks && (a = this.tasks.content, mxEvent.release(a), a.innerHTML = "", this.createTasks(a))
+};
+mxEditor.prototype.createTasks = function(a) {};
+mxEditor.prototype.showHelp = function(a) {
+  if (null == this.help) {
+    var b = document.createElement("iframe");
+    b.setAttribute("src", mxResources.get("urlHelp") || this.urlHelp);
+    b.setAttribute("height", "100%");
+    b.setAttribute("width", "100%");
+    b.setAttribute("frameBorder", "0");
+    b.style.backgroundColor = "white";
+    a = document.body.clientWidth;
+    var c = document.body.clientHeight || document.documentElement.clientHeight,
+      d = new mxWindow(mxResources.get(this.helpResource) || this.helpResource, b, (a - this.helpWidth) / 2, (c - this.helpHeight) /
+        3, this.helpWidth, this.helpHeight);
+    d.setMaximizable(!0);
+    d.setClosable(!0);
+    d.destroyOnClose = !1;
+    d.setResizable(!0);
+    null != this.helpWindowImage && d.setImage(this.helpWindowImage);
+    mxClient.IS_NS && (a = function(a) {
+      b.setAttribute("height", d.div.offsetHeight - 26 + "px")
+    }, d.addListener(mxEvent.RESIZE_END, a), d.addListener(mxEvent.MAXIMIZE, a), d.addListener(mxEvent.NORMALIZE, a), d.addListener(mxEvent.SHOW, a));
+    this.help = d
+  }
+  this.help.setVisible(!0)
+};
+mxEditor.prototype.showOutline = function() {
+  if (null == this.outline) {
+    var a = document.createElement("div");
+    a.style.overflow = "hidden";
+    a.style.width = "100%";
+    a.style.height = "100%";
+    a.style.background = "white";
+    a.style.cursor = "move";
+    var b = new mxWindow(mxResources.get(this.outlineResource) || this.outlineResource, a, 600, 480, 200, 200, !1),
+      c = new mxOutline(this.graph, a);
+    b.setClosable(!0);
+    b.setResizable(!0);
+    b.destroyOnClose = !1;
+    b.addListener(mxEvent.RESIZE_END, function() {
+      c.update()
+    });
+    this.outline = b;
+    this.outline.outline = c
+  }
+  this.outline.setVisible(!0);
+  this.outline.outline.update(!0)
+};
+mxEditor.prototype.setMode = function(a) {
+  "select" == a ? (this.graph.panningHandler.useLeftButtonForPanning = !1, this.graph.setConnectable(!1)) : "connect" == a ? (this.graph.panningHandler.useLeftButtonForPanning = !1, this.graph.setConnectable(!
+    0)) : "pan" == a && (this.graph.panningHandler.useLeftButtonForPanning = !0, this.graph.setConnectable(!1))
+};
+mxEditor.prototype.createPopupMenu = function(a, b, c) {
+  this.popupHandler.createMenu(this, a, b, c)
+};
+mxEditor.prototype.createEdge = function(a, b) {
+  var c = null;
+  if (null != this.defaultEdge) c = this.graph.getModel().cloneCell(this.defaultEdge);
+  else {
+    c = new mxCell("");
+    c.setEdge(!0);
+    var d = new mxGeometry;
+    d.relative = !0;
+    c.setGeometry(d)
+  }
+  d = this.getEdgeStyle();
+  null != d && c.setStyle(d);
+  return c
+};
+mxEditor.prototype.getEdgeStyle = function() {
+  return this.defaultEdgeStyle
+};
+mxEditor.prototype.consumeCycleAttribute = function(a) {
+  return null != this.cycleAttributeValues && 0 < this.cycleAttributeValues.length && this.graph.isSwimlane(a) ? this.cycleAttributeValues[this.cycleAttributeIndex++ % this.cycleAttributeValues.length] :
+    null
+};
+mxEditor.prototype.cycleAttribute = function(a) {
+  if (null != this.cycleAttributeName) {
+    var b = this.consumeCycleAttribute(a);
+    null != b && a.setStyle(a.getStyle() + ";" + this.cycleAttributeName + "\x3d" + b)
+  }
+};
+mxEditor.prototype.addVertex = function(a, b, c, d) {
+  for (var e = this.graph.getModel(); null != a && !this.graph.isValidDropTarget(a);) a = e.getParent(a);
+  a = null != a ? a : this.graph.getSwimlaneAt(c, d);
+  var f = this.graph.getView().scale,
+    g = e.getGeometry(b),
+    h = e.getGeometry(a);
+  if (this.graph.isSwimlane(b) && !this.graph.swimlaneNesting) a = null;
+  else {
+    if (null == a && this.swimlaneRequired) return null;
+    if (null != a && null != h) {
+      var k = this.graph.getView().getState(a);
+      if (null != k) {
+        if (c -= k.origin.x * f, d -= k.origin.y * f, this.graph.isConstrainedMoving) {
+          var h =
+            g.width,
+            l = g.height,
+            m = k.x + k.width;
+          c + h > m && (c -= c + h - m);
+          m = k.y + k.height;
+          d + l > m && (d -= d + l - m)
+        }
+      } else null != h && (c -= h.x * f, d -= h.y * f)
+    }
+  }
+  g = g.clone();
+  g.x = this.graph.snap(c / f - this.graph.getView().translate.x - this.graph.gridSize / 2);
+  g.y = this.graph.snap(d / f - this.graph.getView().translate.y - this.graph.gridSize / 2);
+  b.setGeometry(g);
+  null == a && (a = this.graph.getDefaultParent());
+  this.cycleAttribute(b);
+  this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX, "vertex", b, "parent", a));
+  e.beginUpdate();
+  try {
+    b = this.graph.addCell(b,
+      a), null != b && (this.graph.constrainChild(b), this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, "vertex", b)))
+  } finally {
+    e.endUpdate()
+  }
+  null != b && (this.graph.setSelectionCell(b), this.graph.scrollCellToVisible(b), this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, "vertex", b)));
+  return b
+};
+mxEditor.prototype.destroy = function() {
+  this.destroyed || (this.destroyed = !0, null != this.tasks && this.tasks.destroy(), null != this.outline && this.outline.destroy(), null != this.properties && this.properties.destroy(), null !=
+    this.keyHandler && this.keyHandler.destroy(), null != this.rubberband && this.rubberband.destroy(), null != this.toolbar && this.toolbar.destroy(), null != this.graph && this.graph.destroy(),
+    this.templates = this.status = null)
+};
+var mxCodecRegistry = {
+  codecs: [],
+  aliases: [],
+  register: function(a) {
+    if (null != a) {
+      var b = a.getName();
+      mxCodecRegistry.codecs[b] = a;
+      var c = mxUtils.getFunctionName(a.template.constructor);
+      c != b && mxCodecRegistry.addAlias(c, b)
+    }
+    return a
+  },
+  addAlias: function(a, b) {
+    mxCodecRegistry.aliases[a] = b
+  },
+  getCodec: function(a) {
+    var b = null;
+    if (null != a) {
+      var b = mxUtils.getFunctionName(a),
+        c = mxCodecRegistry.aliases[b];
+      null != c && (b = c);
+      b = mxCodecRegistry.codecs[b];
+      if (null == b) try {
+        b = new mxObjectCodec(new a), mxCodecRegistry.register(b)
+      } catch (d) {}
+    }
+    return b
+  }
+};
+
+function mxCodec(a) {
+  this.document = a || mxUtils.createXmlDocument();
+  this.objects = []
+}
+mxCodec.prototype.document = null;
+mxCodec.prototype.objects = null;
+mxCodec.prototype.encodeDefaults = !1;
+mxCodec.prototype.putObject = function(a, b) {
+  return this.objects[a] = b
+};
+mxCodec.prototype.getObject = function(a) {
+  var b = null;
+  null != a && (b = this.objects[a], null == b && (b = this.lookup(a), null == b && (a = this.getElementById(a), null != a && (b = this.decode(a)))));
+  return b
+};
+mxCodec.prototype.lookup = function(a) {
+  return null
+};
+mxCodec.prototype.getElementById = function(a, b) {
+  return mxUtils.findNodeByAttribute(this.document.documentElement, null != b ? b : "id", a)
+};
+mxCodec.prototype.getId = function(a) {
+  var b = null;
+  null != a && (b = this.reference(a), null == b && a instanceof mxCell && (b = a.getId(), null == b && (b = mxCellPath.create(a), 0 == b.length && (b = "root"))));
+  return b
+};
+mxCodec.prototype.reference = function(a) {
+  return null
+};
+mxCodec.prototype.encode = function(a) {
+  var b = null;
+  if (null != a && null != a.constructor) {
+    var c = mxCodecRegistry.getCodec(a.constructor);
+    null != c ? b = c.encode(this, a) : mxUtils.isNode(a) ? b = mxClient.IS_IE ? a.cloneNode(!0) : this.document.importNode(a, !0) : mxLog.warn("mxCodec.encode: No codec for " + mxUtils.getFunctionName(
+      a.constructor))
+  }
+  return b
+};
+mxCodec.prototype.decode = function(a, b) {
+  var c = null;
+  if (null != a && a.nodeType == mxConstants.NODETYPE_ELEMENT) {
+    var d = null;
+    try {
+      d = eval(a.nodeName)
+    } catch (e) {}
+    try {
+      var f = mxCodecRegistry.getCodec(d);
+      null != f ? c = f.decode(this, a, b) : (c = a.cloneNode(!0), c.removeAttribute("as"))
+    } catch (g) {
+      mxLog.debug("Cannot decode " + a.nodeName + ": " + g.message)
+    }
+  }
+  return c
+};
+mxCodec.prototype.encodeCell = function(a, b, c) {
+  b.appendChild(this.encode(a));
+  if (null == c || c) {
+    c = a.getChildCount();
+    for (var d = 0; d < c; d++) this.encodeCell(a.getChildAt(d), b)
+  }
+};
+mxCodec.prototype.isCellCodec = function(a) {
+  return null != a && "function" == typeof a.isCellCodec ? a.isCellCodec() : !1
+};
+mxCodec.prototype.decodeCell = function(a, b) {
+  b = null != b ? b : !0;
+  var c = null;
+  if (null != a && a.nodeType == mxConstants.NODETYPE_ELEMENT) {
+    c = mxCodecRegistry.getCodec(a.nodeName);
+    if (!this.isCellCodec(c))
+      for (var d = a.firstChild; null != d && !this.isCellCodec(c);) c = mxCodecRegistry.getCodec(d.nodeName), d = d.nextSibling;
+    this.isCellCodec(c) || (c = mxCodecRegistry.getCodec(mxCell));
+    c = c.decode(this, a);
+    b && this.insertIntoGraph(c)
+  }
+  return c
+};
+mxCodec.prototype.insertIntoGraph = function(a) {
+  var b = a.parent,
+    c = a.getTerminal(!0),
+    d = a.getTerminal(!1);
+  a.setTerminal(null, !1);
+  a.setTerminal(null, !0);
+  a.parent = null;
+  null != b && b.insert(a);
+  null != c && c.insertEdge(a, !0);
+  null != d && d.insertEdge(a, !1)
+};
+mxCodec.prototype.setAttribute = function(a, b, c) {
+  null != b && null != c && a.setAttribute(b, c)
+};
+
+function mxObjectCodec(a, b, c, d) {
+  this.template = a;
+  this.exclude = null != b ? b : [];
+  this.idrefs = null != c ? c : [];
+  this.mapping = null != d ? d : [];
+  this.reverse = {};
+  for (var e in this.mapping) this.reverse[this.mapping[e]] = e
+}
+mxObjectCodec.prototype.template = null;
+mxObjectCodec.prototype.exclude = null;
+mxObjectCodec.prototype.idrefs = null;
+mxObjectCodec.prototype.mapping = null;
+mxObjectCodec.prototype.reverse = null;
+mxObjectCodec.prototype.getName = function() {
+  return mxUtils.getFunctionName(this.template.constructor)
+};
+mxObjectCodec.prototype.cloneTemplate = function() {
+  return new this.template.constructor
+};
+mxObjectCodec.prototype.getFieldName = function(a) {
+  if (null != a) {
+    var b = this.reverse[a];
+    null != b && (a = b)
+  }
+  return a
+};
+mxObjectCodec.prototype.getAttributeName = function(a) {
+  if (null != a) {
+    var b = this.mapping[a];
+    null != b && (a = b)
+  }
+  return a
+};
+mxObjectCodec.prototype.isExcluded = function(a, b, c, d) {
+  return b == mxObjectIdentity.FIELD_NAME || 0 <= mxUtils.indexOf(this.exclude, b)
+};
+mxObjectCodec.prototype.isReference = function(a, b, c, d) {
+  return 0 <= mxUtils.indexOf(this.idrefs, b)
+};
+mxObjectCodec.prototype.encode = function(a, b) {
+  var c = a.document.createElement(this.getName());
+  b = this.beforeEncode(a, b, c);
+  this.encodeObject(a, b, c);
+  return this.afterEncode(a, b, c)
+};
+mxObjectCodec.prototype.encodeObject = function(a, b, c) {
+  a.setAttribute(c, "id", a.getId(b));
+  for (var d in b) {
+    var e = d,
+      f = b[e];
+    null != f && !this.isExcluded(b, e, f, !0) && (mxUtils.isNumeric(e) && (e = null), this.encodeValue(a, b, e, f, c))
+  }
+};
+mxObjectCodec.prototype.encodeValue = function(a, b, c, d, e) {
+  if (null != d) {
+    if (this.isReference(b, c, d, !0)) {
+      var f = a.getId(d);
+      if (null == f) {
+        mxLog.warn("mxObjectCodec.encode: No ID for " + this.getName() + "." + c + "\x3d" + d);
+        return
+      }
+      d = f
+    }
+    f = this.template[c];
+    if (null == c || a.encodeDefaults || f != d) c = this.getAttributeName(c), this.writeAttribute(a, b, c, d, e)
+  }
+};
+mxObjectCodec.prototype.writeAttribute = function(a, b, c, d, e) {
+  "object" != typeof d ? this.writePrimitiveAttribute(a, b, c, d, e) : this.writeComplexAttribute(a, b, c, d, e)
+};
+mxObjectCodec.prototype.writePrimitiveAttribute = function(a, b, c, d, e) {
+  d = this.convertValueToXml(d);
+  null == c ? (b = a.document.createElement("add"), "function" == typeof d ? b.appendChild(a.document.createTextNode(d)) : a.setAttribute(b, "value", d), e.appendChild(b)) : "function" != typeof d &&
+    a.setAttribute(e, c, d)
+};
+mxObjectCodec.prototype.writeComplexAttribute = function(a, b, c, d, e) {
+  a = a.encode(d);
+  null != a ? (null != c && a.setAttribute("as", c), e.appendChild(a)) : mxLog.warn("mxObjectCodec.encode: No node for " + this.getName() + "." + c + ": " + d)
+};
+mxObjectCodec.prototype.convertValueToXml = function(a) {
+  if ("undefined" == typeof a.length && (!0 == a || !1 == a)) a = !0 == a ? "1" : "0";
+  return a
+};
+mxObjectCodec.prototype.convertValueFromXml = function(a) {
+  mxUtils.isNumeric(a) && (a = parseFloat(a));
+  //
+  // Marco ([email-masked]) + Andrea Bianco ([email-masked])
+  // 2016-02-24
+  //
+  // mxUtils.isNumeric(a) && (a = ((Number(a) === a) && (a % 1 !== 0)) ? parseFloat(a) : a);
+  return a
+};
+mxObjectCodec.prototype.beforeEncode = function(a, b, c) {
+  return b
+};
+mxObjectCodec.prototype.afterEncode = function(a, b, c) {
+  return c
+};
+mxObjectCodec.prototype.decode = function(a, b, c) {
+  var d = b.getAttribute("id"),
+    e = a.objects[d];
+  null == e && (e = c || this.cloneTemplate(), null != d && a.putObject(d, e));
+  b = this.beforeDecode(a, b, e);
+  this.decodeNode(a, b, e);
+  return this.afterDecode(a, b, e)
+};
+mxObjectCodec.prototype.decodeNode = function(a, b, c) {
+  null != b && (this.decodeAttributes(a, b, c), this.decodeChildren(a, b, c))
+};
+mxObjectCodec.prototype.decodeAttributes = function(a, b, c) {
+  b = b.attributes;
+  if (null != b)
+    for (var d = 0; d < b.length; d++) this.decodeAttribute(a, b[d], c)
+};
+mxObjectCodec.prototype.decodeAttribute = function(a, b, c) {
+  var d = b.nodeName;
+  if ("as" != d && "id" != d) {
+    b = this.convertValueFromXml(b.nodeValue);
+    var e = this.getFieldName(d);
+    if (this.isReference(c, e, b, !1)) {
+      a = a.getObject(b);
+      if (null == a) {
+        mxLog.warn("mxObjectCodec.decode: No object for " + this.getName() + "." + d + "\x3d" + b);
+        return
+      }
+      b = a
+    }
+    this.isExcluded(c, d, b, !1) || (c[d] = b)
+  }
+};
+mxObjectCodec.prototype.decodeChildren = function(a, b, c) {
+  for (b = b.firstChild; null != b;) {
+    var d = b.nextSibling;
+    b.nodeType == mxConstants.NODETYPE_ELEMENT && !this.processInclude(a, b, c) && this.decodeChild(a, b, c);
+    b = d
+  }
+};
+mxObjectCodec.prototype.decodeChild = function(a, b, c) {
+  var d = this.getFieldName(b.getAttribute("as"));
+  if (null == d || !this.isExcluded(c, d, b, !1)) {
+    var e = this.getFieldTemplate(c, d, b),
+      f = null;
+    "add" == b.nodeName ? (f = b.getAttribute("value"), null == f && (f = mxUtils.eval(mxUtils.getTextContent(b)))) : f = a.decode(b, e);
+    this.addObjectValue(c, d, f, e)
+  }
+};
+mxObjectCodec.prototype.getFieldTemplate = function(a, b, c) {
+  a = a[b];
+  a instanceof Array && 0 < a.length && (a = null);
+  return a
+};
+mxObjectCodec.prototype.addObjectValue = function(a, b, c, d) {
+  null != c && c != d && (null != b && 0 < b.length ? a[b] = c : a.push(c))
+};
+mxObjectCodec.prototype.processInclude = function(a, b, c) {
+  if ("include" == b.nodeName) {
+    b = b.getAttribute("name");
+    if (null != b) try {
+      var d = mxUtils.load(b).getDocumentElement();
+      null != d && a.decode(d, c)
+    } catch (e) {}
+    return !0
+  }
+  return !1
+};
+mxObjectCodec.prototype.beforeDecode = function(a, b, c) {
+  return b
+};
+mxObjectCodec.prototype.afterDecode = function(a, b, c) {
+  return c
+};
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxCell, ["children", "edges", "overlays", "mxTransient"], ["parent", "source", "target"]);
+  a.isCellCodec = function() {
+    return !0
+  };
+  a.isExcluded = function(a, c, d, e) {
+    return mxObjectCodec.prototype.isExcluded.apply(this, arguments) || e && "value" == c && d.nodeType == mxConstants.NODETYPE_ELEMENT
+  };
+  a.afterEncode = function(a, c, d) {
+    if (null != c.value && c.value.nodeType == mxConstants.NODETYPE_ELEMENT) {
+      var e = d;
+      d = mxClient.IS_IE ? c.value.cloneNode(!0) : a.document.importNode(c.value, !0);
+      d.appendChild(e);
+      a = e.getAttribute("id");
+      d.setAttribute("id", a);
+      e.removeAttribute("id")
+    }
+    return d
+  };
+  a.beforeDecode = function(a, c, d) {
+    var e = c,
+      f = this.getName();
+    c.nodeName != f ? (e = c.getElementsByTagName(f)[0], null != e && e.parentNode == c ? (mxUtils.removeWhitespace(e, !0), mxUtils.removeWhitespace(e, !1), e.parentNode.removeChild(e)) : e =
+      null, d.value = c.cloneNode(!0), c = d.value.getAttribute("id"), null != c && (d.setId(c), d.value.removeAttribute("id"))) : d.setId(c.getAttribute("id"));
+    if (null != e)
+      for (c = 0; c < this.idrefs.length; c++) {
+        var f =
+          this.idrefs[c],
+          g = e.getAttribute(f);
+        if (null != g) {
+          e.removeAttribute(f);
+          var h = a.objects[g] || a.lookup(g);
+          null == h && (g = a.getElementById(g), null != g && (h = (mxCodecRegistry.codecs[g.nodeName] || this).decode(a, g)));
+          d[f] = h
+        }
+      }
+    return e
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxGraphModel);
+  a.encodeObject = function(a, c, d) {
+    var e = a.document.createElement("root");
+    a.encodeCell(c.getRoot(), e);
+    d.appendChild(e)
+  };
+  a.decodeChild = function(a, c, d) {
+    "root" == c.nodeName ? this.decodeRoot(a, c, d) : mxObjectCodec.prototype.decodeChild.apply(this, arguments)
+  };
+  a.decodeRoot = function(a, c, d) {
+    var e = null;
+    for (c = c.firstChild; null != c;) {
+      var f = a.decodeCell(c);
+      null != f && null == f.getParent() && (e = f);
+      c = c.nextSibling
+    }
+    null != e && d.setRoot(e)
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxRootChange, ["model", "previous", "root"]);
+  a.afterEncode = function(a, c, d) {
+    a.encodeCell(c.root, d);
+    return d
+  };
+  a.beforeDecode = function(a, c, d) {
+    if (null != c.firstChild && c.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT) {
+      c = c.cloneNode(!0);
+      var e = c.firstChild;
+      d.root = a.decodeCell(e, !1);
+      d = e.nextSibling;
+      e.parentNode.removeChild(e);
+      for (e = d; null != e;) d = e.nextSibling, a.decodeCell(e), e.parentNode.removeChild(e), e = d
+    }
+    return c
+  };
+  a.afterDecode = function(a, c,
+    d) {
+    d.previous = d.root;
+    return d
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxChildChange, ["model", "child", "previousIndex"], ["parent", "previous"]);
+  a.isReference = function(a, c, d, e) {
+    return "child" == c && (null != a.previous || !e) ? !0 : 0 <= mxUtils.indexOf(this.idrefs, c)
+  };
+  a.afterEncode = function(a, c, d) {
+    this.isReference(c, "child", c.child, !0) ? d.setAttribute("child", a.getId(c.child)) : a.encodeCell(c.child, d);
+    return d
+  };
+  a.beforeDecode = function(a, c, d) {
+    if (null != c.firstChild && c.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT) {
+      c = c.cloneNode(!0);
+      var e = c.firstChild;
+      d.child = a.decodeCell(e, !1);
+      d = e.nextSibling;
+      e.parentNode.removeChild(e);
+      for (e = d; null != e;) {
+        d = e.nextSibling;
+        if (e.nodeType == mxConstants.NODETYPE_ELEMENT) {
+          var f = e.getAttribute("id");
+          null == a.lookup(f) && a.decodeCell(e)
+        }
+        e.parentNode.removeChild(e);
+        e = d
+      }
+    } else e = c.getAttribute("child"), d.child = a.getObject(e);
+    return c
+  };
+  a.afterDecode = function(a, c, d) {
+    d.child.parent = d.previous;
+    d.previous = d.parent;
+    d.previousIndex = d.index;
+    return d
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxTerminalChange, ["model", "previous"], ["cell", "terminal"]);
+  a.afterDecode = function(a, c, d) {
+    d.previous = d.terminal;
+    return d
+  };
+  return a
+}());
+var mxGenericChangeCodec = function(a, b) {
+  var c = new mxObjectCodec(a, ["model", "previous"], ["cell"]);
+  c.afterDecode = function(a, c, f) {
+    mxUtils.isNode(f.cell) && (f.cell = a.decodeCell(f.cell, !1));
+    f.previous = f[b];
+    return f
+  };
+  return c
+};
+mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange, "value"));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange, "style"));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange, "geometry"));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange, "collapsed"));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange, "visible"));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange, "value"));
+mxCodecRegistry.register(function() {
+  return new mxObjectCodec(new mxGraph, "graphListeners eventListeners view container cellRenderer editor selection".split(" "))
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxGraphView);
+  a.encode = function(a, c) {
+    return this.encodeCell(a, c, c.graph.getModel().getRoot())
+  };
+  a.encodeCell = function(a, c, d) {
+    var e = c.graph.getModel(),
+      f = c.getState(d),
+      g = e.getParent(d);
+    if (null == g || null != f) {
+      var h = e.getChildCount(d),
+        k = c.graph.getCellGeometry(d),
+        l = null;
+      g == e.getRoot() ? l = "layer" : null == g ? l = "graph" : e.isEdge(d) ? l = "edge" : 0 < h && null != k ? l = "group" : e.isVertex(d) && (l = "vertex");
+      if (null != l) {
+        var m = a.document.createElement(l);
+        null != c.graph.getLabel(d) &&
+          (m.setAttribute("label", c.graph.getLabel(d)), c.graph.isHtmlLabel(d) && m.setAttribute("html", !0));
+        if (null == g) {
+          var n = c.getGraphBounds();
+          null != n && (m.setAttribute("x", Math.round(n.x)), m.setAttribute("y", Math.round(n.y)), m.setAttribute("width", Math.round(n.width)), m.setAttribute("height", Math.round(n.height)));
+          m.setAttribute("scale", c.scale)
+        } else if (null != f && null != k) {
+          for (n in f.style) g = f.style[n], "function" == typeof g && "object" == typeof g && (g = mxStyleRegistry.getName(g)), null != g && ("function" != typeof g && "object" !=
+            typeof g) && m.setAttribute(n, g);
+          g = f.absolutePoints;
+          if (null != g && 0 < g.length) {
+            k = Math.round(g[0].x) + "," + Math.round(g[0].y);
+            for (n = 1; n < g.length; n++) k += " " + Math.round(g[n].x) + "," + Math.round(g[n].y);
+            m.setAttribute("points", k)
+          } else m.setAttribute("x", Math.round(f.x)), m.setAttribute("y", Math.round(f.y)), m.setAttribute("width", Math.round(f.width)), m.setAttribute("height", Math.round(f.height));
+          n = f.absoluteOffset;
+          null != n && (0 != n.x && m.setAttribute("dx", Math.round(n.x)), 0 != n.y && m.setAttribute("dy", Math.round(n.y)))
+        }
+        for (n =
+          0; n < h; n++) f = this.encodeCell(a, c, e.getChildAt(d, n)), null != f && m.appendChild(f)
+      }
+    }
+    return m
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxStylesheet);
+  a.encode = function(a, c) {
+    var d = a.document.createElement(this.getName()),
+      e;
+    for (e in c.styles) {
+      var f = c.styles[e],
+        g = a.document.createElement("add");
+      if (null != e) {
+        g.setAttribute("as", e);
+        for (var h in f) {
+          var k = this.getStringValue(h, f[h]);
+          if (null != k) {
+            var l = a.document.createElement("add");
+            l.setAttribute("value", k);
+            l.setAttribute("as", h);
+            g.appendChild(l)
+          }
+        }
+        0 < g.childNodes.length && d.appendChild(g)
+      }
+    }
+    return d
+  };
+  a.getStringValue = function(a,
+    c) {
+    var d = typeof c;
+    "function" == d ? c = mxStyleRegistry.getName(style[j]) : "object" == d && (c = null);
+    return c
+  };
+  a.decode = function(a, c, d) {
+    d = d || new this.template.constructor;
+    var e = c.getAttribute("id");
+    null != e && (a.objects[e] = d);
+    for (c = c.firstChild; null != c;) {
+      if (!this.processInclude(a, c, d) && "add" == c.nodeName && (e = c.getAttribute("as"), null != e)) {
+        var f = c.getAttribute("extend"),
+          g = null != f ? mxUtils.clone(d.styles[f]) : null;
+        null == g && (null != f && mxLog.warn("mxStylesheetCodec.decode: stylesheet " + f + " not found to extend"), g = {});
+        for (f = c.firstChild; null != f;) {
+          if (f.nodeType == mxConstants.NODETYPE_ELEMENT) {
+            var h = f.getAttribute("as");
+            if ("add" == f.nodeName) {
+              var k = mxUtils.getTextContent(f),
+                l = null;
+              null != k && 0 < k.length ? l = mxUtils.eval(k) : (l = f.getAttribute("value"), mxUtils.isNumeric(l) && (l = parseFloat(l)));
+              null != l && (g[h] = l)
+            } else "remove" == f.nodeName && delete g[h]
+          }
+          f = f.nextSibling
+        }
+        d.putCellStyle(e, g)
+      }
+      c = c.nextSibling
+    }
+    return d
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxDefaultKeyHandler);
+  a.encode = function(a, c) {
+    return null
+  };
+  a.decode = function(a, c, d) {
+    if (null != d)
+      for (c = c.firstChild; null != c;) {
+        if (!this.processInclude(a, c, d) && "add" == c.nodeName) {
+          var e = c.getAttribute("as"),
+            f = c.getAttribute("action"),
+            g = c.getAttribute("control");
+          d.bindAction(e, f, g)
+        }
+        c = c.nextSibling
+      }
+    return d
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxDefaultToolbar);
+  a.encode = function(a, c) {
+    return null
+  };
+  a.decode = function(a, c, d) {
+    if (null != d) {
+      var e = d.editor;
+      for (c = c.firstChild; null != c;) {
+        if (c.nodeType == mxConstants.NODETYPE_ELEMENT && !this.processInclude(a, c, d))
+          if ("separator" == c.nodeName) d.addSeparator();
+          else if ("br" == c.nodeName) d.toolbar.addBreak();
+        else if ("hr" == c.nodeName) d.toolbar.addLine();
+        else if ("add" == c.nodeName) {
+          var f = c.getAttribute("as"),
+            f = mxResources.get(f) || f,
+            g = c.getAttribute("icon"),
+            h = c.getAttribute("pressedIcon"),
+            k = c.getAttribute("action"),
+            l = c.getAttribute("mode"),
+            m = c.getAttribute("template"),
+            n = "0" != c.getAttribute("toggle"),
+            p = mxUtils.getTextContent(c),
+            q = null;
+          if (null != k) q = d.addItem(f, g, k, h);
+          else if (null != l) var s = mxUtils.eval(p),
+            q = d.addMode(f, g, l, h, s);
+          else if (null != m || null != p && 0 < p.length) q = e.templates[m], m = c.getAttribute("style"), null != q && null != m && (q = q.clone(), q.setStyle(m)), m = null, null != p && 0 <
+            p.length && (m = mxUtils.eval(p)), q = d.addPrototype(f, g, q, h, m, n);
+          else if (h = mxUtils.getChildNodes(c),
+            0 < h.length)
+            if (null == g) {
+              m = d.addActionCombo(f);
+              for (f = 0; f < h.length; f++) n = h[f], "separator" == n.nodeName ? d.addOption(m, "---") : "add" == n.nodeName && (g = n.getAttribute("as"), n = n.getAttribute("action"), d.addActionOption(
+                m, g, n))
+            } else {
+              var r = null,
+                t = d.addPrototype(f, g, function() {
+                  var a = e.templates[r.value];
+                  if (null != a) {
+                    var a = a.clone(),
+                      b = r.options[r.selectedIndex].cellStyle;
+                    null != b && a.setStyle(b);
+                    return a
+                  }
+                  mxLog.warn("Template " + a + " not found");
+                  return null
+                }, null, null, n),
+                r = d.addCombo();
+              mxEvent.addListener(r, "change",
+                function() {
+                  d.toolbar.selectMode(t, function(a) {
+                    a = mxUtils.convertPoint(e.graph.container, mxEvent.getClientX(a), mxEvent.getClientY(a));
+                    return e.addVertex(null, s(), a.x, a.y)
+                  });
+                  d.toolbar.noReset = !1
+                });
+              for (f = 0; f < h.length; f++) n = h[f], "separator" == n.nodeName ? d.addOption(r, "---") : "add" == n.nodeName && (g = n.getAttribute("as"), p = n.getAttribute("template"), d.addOption(
+                r, g, p || m).cellStyle = n.getAttribute("style"))
+            }
+          null != q && (m = c.getAttribute("id"), null != m && 0 < m.length && q.setAttribute("id", m))
+        }
+        c = c.nextSibling
+      }
+    }
+    return d
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxDefaultPopupMenu);
+  a.encode = function(a, c) {
+    return null
+  };
+  a.decode = function(a, c, d) {
+    var e = c.getElementsByTagName("include")[0];
+    null != e ? this.processInclude(a, e, d) : null != d && (d.config = c);
+    return d
+  };
+  return a
+}());
+mxCodecRegistry.register(function() {
+  var a = new mxObjectCodec(new mxEditor, "modified lastSnapshot ignoredChanges undoManager graphContainer toolbarContainer".split(" "));
+  a.afterDecode = function(a, c, d) {
+    a = c.getAttribute("defaultEdge");
+    null != a && (c.removeAttribute("defaultEdge"), d.defaultEdge = d.templates[a]);
+    a = c.getAttribute("defaultGroup");
+    null != a && (c.removeAttribute("defaultGroup"), d.defaultGroup = d.templates[a]);
+    return d
+  };
+  a.decodeChild = function(a, c, d) {
+    if ("Array" == c.nodeName) {
+      if ("templates" == c.getAttribute("as")) {
+        this.decodeTemplates(a,
+          c, d);
+        return
+      }
+    } else if ("ui" == c.nodeName) {
+      this.decodeUi(a, c, d);
+      return
+    }
+    mxObjectCodec.prototype.decodeChild.apply(this, arguments)
+  };
+  a.decodeUi = function(a, c, d) {
+    for (a = c.firstChild; null != a;) {
+      if ("add" == a.nodeName) {
+        c = a.getAttribute("as");
+        var e = a.getAttribute("element"),
+          f = a.getAttribute("style"),
+          g = null;
+        if (null != e) g = document.getElementById(e), null != g && null != f && (g.style.cssText += ";" + f);
+        else {
+          var e = parseInt(a.getAttribute("x")),
+            h = parseInt(a.getAttribute("y")),
+            k = a.getAttribute("width"),
+            l = a.getAttribute("height"),
+            g = document.createElement("div");
+          g.style.cssText = f;
+          (new mxWindow(mxResources.get(c) || c, g, e, h, k, l, !1, !0)).setVisible(!0)
+        }
+        "graph" == c ? d.setGraphContainer(g) : "toolbar" == c ? d.setToolbarContainer(g) : "title" == c ? d.setTitleContainer(g) : "status" == c ? d.setStatusContainer(g) : "map" == c && d.setMapContainer(
+          g)
+      } else "resource" == a.nodeName ? mxResources.add(a.getAttribute("basename")) : "stylesheet" == a.nodeName && mxClient.link("stylesheet", a.getAttribute("name"));
+      a = a.nextSibling
+    }
+  };
+  a.decodeTemplates = function(a, c, d) {
+    null == d.templates &&
+      (d.templates = []);
+    c = mxUtils.getChildNodes(c);
+    for (var e = 0; e < c.length; e++) {
+      for (var f = c[e].getAttribute("as"), g = c[e].firstChild; null != g && 1 != g.nodeType;) g = g.nextSibling;
+      null != g && (d.templates[f] = a.decodeCell(g))
+    }
+  };
+  return a
+}());
diff --git a/public/assets/plugins/square/js/Actions.js b/public/assets/plugins/square/js/Actions.js
index acee5a0..aeaa420 100644
--- a/public/assets/plugins/square/js/Actions.js
+++ b/public/assets/plugins/square/js/Actions.js
@@ -1 +1,634 @@
\ No newline at end of file
+'use strict';
+/**
+ * $Id: Actions.js,v 1.7 2013-02-14 07:48:01 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs the actions object for the given UI.
+ */
+function Actions(editorUi) {
+	this.editorUi = editorUi;
+	this.actions = new Object();
+	this.init();
+};
+
+/**
+ * Adds the default actions.
+ */
+Actions.prototype.init = function() {
+	var ui = this.editorUi;
+	var editor = ui.editor;
+	var graph = editor.graph;
+
+	// File actions
+	this.addAction('new', function() {
+		ui.showDialog(new NewDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('open', function() {
+		ui.showDialog(new OpenDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('save', function() {
+		ui.saveFile(false);
+	}, null, null, 'Ctrl+S');
+	this.addAction('saveAs', function() {
+		ui.saveFile(true);
+	}, null, null, 'Ctrl+Shift-S');
+	this.addAction('publish', function() {
+		ui.publishFile(false);
+	}, null, null, 'Ctrl+Shift+P');
+	this.addAction('variable', function() {
+		ui.showDialog(new VariableDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('import', function() {
+		ui.showDialog(new ImportDialog(ui).container, 300, 200, true, true);
+	});
+	this.addAction('export', function() {
+		ui.showDialog(new ExportDialog(ui).container, 300, 200, true, true);
+	}, null, null, 'Ctrl+E');
+	this.put('editFile', new Action(mxResources.get('edit'), mxUtils.bind(this,
+		function() {
+			this.editorUi.showDialog(new EditFileDialog(ui).container, 620, 420,
+				true, true);
+		})));
+	this.addAction('rename', function() {
+		ui.showDialog(new RenameDialog(ui).container, 300, 180, true, true);
+	});
+	this.addAction('print', function() {
+		mxUtils.printScreen(graph);
+	}, null, 'sprite-print', 'Ctrl+P');
+	this.addAction('preview', function() {
+		mxUtils.show(graph, null, 10, 10);
+	});
+
+	// Edit actions
+	this.addAction('undo', function() {
+		editor.undoManager.undo();
+	}, null, 'sprite-undo', 'Ctrl+Z');
+	this.addAction('redo', function() {
+		editor.undoManager.redo();
+	}, null, 'sprite-redo', 'Ctrl+Y');
+	this.addAction('cut', function() {
+		mxClipboard.cut(graph);
+	}, null, 'sprite-cut', 'Ctrl+X');
+	this.addAction('copy', function() {
+		mxClipboard.copy(graph);
+	}, null, 'sprite-copy', 'Ctrl+C');
+	this.addAction('paste', function() {
+		mxClipboard.paste(graph);
+	}, false, 'sprite-paste', 'Ctrl+V');
+	this.addAction('delete', function() {
+		graph.removeCells();
+	}, null, null, 'Delete');
+	this.addAction('duplicate', function() {
+		var s = graph.gridSize;
+		graph.setSelectionCells(graph.moveCells(graph.getSelectionCells(), s, s,
+			true));
+	}, null, null, 'Ctrl+D');
+	this.addAction('selectVertices', function() {
+		graph.selectVertices();
+	}, null, null, 'Ctrl+Shift+V');
+	this.addAction('selectEdges', function() {
+		graph.selectEdges();
+	}, null, null, 'Ctrl+Shift+E');
+	this.addAction('selectAll', function() {
+		graph.selectAll();
+	}, null, null, 'Ctrl+A');
+
+	// Navigation actions
+	this.addAction('home', function() {
+		graph.home();
+	}, null, null, 'Home');
+	this.addAction('exitGroup', function() {
+		graph.exitGroup();
+	}, null, null, 'Page Up');
+	this.addAction('enterGroup', function() {
+		graph.enterGroup();
+	}, null, null, 'Page Down');
+	this.addAction('expand', function() {
+		graph.foldCells(false);
+	}, null, null, 'Enter');
+	this.addAction('collapse', function() {
+		graph.foldCells(true);
+	}, null, null, 'Backspace');
+
+	// Arrange actions
+	this.addAction('toFront', function() {
+		graph.orderCells(false);
+	}, null, null, 'Ctrl+F');
+	this.addAction('toBack', function() {
+		graph.orderCells(true);
+	}, null, null, 'Ctrl+B');
+	this.addAction('group', function() {
+		graph.setSelectionCell(graph.groupCells(null, 0));
+	}, null, null, 'Ctrl+G');
+	this.addAction('ungroup', function() {
+		graph.setSelectionCells(graph.ungroupCells());
+	}, null, null, 'Ctrl+U');
+	this.addAction('removeFromGroup', function() {
+		graph.removeCellsFromParent();
+	});
+	this.addAction('editLink', function() {
+		var cell = graph.getSelectionCell();
+		var link = graph.getLinkForCell(cell);
+
+		if (link == null) {
+			link = '';
+		}
+
+		link = mxUtils.prompt(mxResources.get('enterValue'), link);
+
+		if (link != null) {
+			graph.setLinkForCell(cell, link);
+		}
+	});
+	this.addAction('openLink', function() {
+		var cell = graph.getSelectionCell();
+		var link = graph.getLinkForCell(cell);
+
+		if (link != null) {
+			window.open(link);
+		}
+	});
+	this.addAction('autosize', function() {
+		var cells = graph.getSelectionCells();
+
+		if (cells != null) {
+			graph.getModel().beginUpdate();
+			try {
+				for (var i = 0; i < cells.length; i++) {
+					var cell = cells[i];
+
+					if (graph.getModel().getChildCount(cell)) {
+						graph.updateGroupBounds([cell], 20);
+					} else {
+						graph.updateCellSize(cell);
+					}
+				}
+			} finally {
+				graph.getModel().endUpdate();
+			}
+		}
+	});
+	this.addAction('wordWrap', function() {
+		var state = graph.getView().getState(graph.getSelectionCell());
+		var value = 'wrap';
+
+		if (state != null && state.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') {
+			value = null;
+		}
+
+		graph.setCellStyles(mxConstants.STYLE_WHITE_SPACE, value);
+	});
+	this.addAction('rotation', function() {
+		var value = '0';
+		var state = graph.getView().getState(graph.getSelectionCell());
+
+		if (state != null) {
+			value = state.style[mxConstants.STYLE_ROTATION] || value;
+		}
+
+		value = mxUtils.prompt(mxResources.get('enterValue') + ' (' +
+			mxResources.get('rotation') + ' 0-360)', value);
+
+		if (value != null) {
+			graph.setCellStyles(mxConstants.STYLE_ROTATION, value);
+		}
+	});
+	this.addAction('tilt', function() {
+		var cells = graph.getSelectionCells();
+
+		if (cells != null) {
+			graph.getModel().beginUpdate();
+			try {
+				for (var i = 0; i < cells.length; i++) {
+					var cell = cells[i];
+
+					if (graph.getModel().isVertex(cell) && graph.getModel().getChildCount(
+							cell) == 0) {
+						var geo = graph.getCellGeometry(cell);
+
+						if (geo != null) {
+							// Rotates the size and position in the geometry
+							geo = geo.clone();
+							geo.x += geo.width / 2 - geo.height / 2;
+							geo.y += geo.height / 2 - geo.width / 2;
+							var tmp = geo.width;
+							geo.width = geo.height;
+							geo.height = tmp;
+							graph.getModel().setGeometry(cell, geo);
+
+							// Reads the current direction and advances by 90 degrees
+							var state = graph.view.getState(cell);
+
+							if (state != null) {
+								var dir = state.style[mxConstants.STYLE_DIRECTION] || 'east' /*default*/ ;
+
+								if (dir == 'east') {
+									dir = 'south';
+								} else if (dir == 'south') {
+									dir = 'west';
+								} else if (dir == 'west') {
+									dir = 'north';
+								} else if (dir == 'north') {
+									dir = 'east';
+								}
+
+								graph.setCellStyles(mxConstants.STYLE_DIRECTION, dir, [cell]);
+							}
+						}
+					}
+				}
+			} finally {
+				graph.getModel().endUpdate();
+			}
+		}
+	}, null, null, 'Ctrl+R');
+
+	// View actions
+	this.addAction('actualSize', function() {
+		graph.zoomTo(1);
+	});
+	this.addAction('zoomIn', function() {
+		graph.zoomIn();
+	}, null, null, 'Add');
+	this.addAction('zoomOut', function() {
+		graph.zoomOut();
+	}, null, null, 'Subtract');
+	this.addAction('fitWindow', function() {
+		graph.fit();
+	});
+
+	this.addAction('fitPage', mxUtils.bind(this, function() {
+		if (!graph.pageVisible) {
+			this.get('pageView').funct();
+		}
+
+		var fmt = graph.pageFormat;
+		var ps = graph.pageScale;
+		var cw = graph.container.clientWidth - 20;
+		var ch = graph.container.clientHeight - 20;
+
+		var scale = Math.floor(100 * Math.min(cw / fmt.width / ps, ch / fmt.height /
+			ps)) / 100;
+		graph.zoomTo(scale);
+
+		graph.container.scrollLeft = Math.round(graph.view.translate.x * scale -
+			Math.max(10, (graph.container.clientWidth - fmt.width * ps * scale) / 2)
+		);
+		graph.container.scrollTop = Math.round(graph.view.translate.y * scale -
+			Math.max(10, (graph.container.clientHeight - fmt.height * ps * scale) /
+				2));
+	}));
+	this.addAction('fitPageWidth', mxUtils.bind(this, function() {
+		if (!graph.pageVisible) {
+			this.get('pageView').funct();
+		}
+
+		var fmt = graph.pageFormat;
+		var ps = graph.pageScale;
+		var cw = graph.container.clientWidth - 20;
+
+		var scale = Math.floor(100 * cw / fmt.width / ps) / 100;
+		graph.zoomTo(scale);
+
+		graph.container.scrollLeft = Math.round(graph.view.translate.x * scale -
+			Math.max(10, (graph.container.clientWidth - fmt.width * ps * scale) / 2)
+		);
+		graph.container.scrollTop = Math.round(graph.view.translate.y * scale -
+			Math.max(10, (graph.container.clientHeight - fmt.height * ps * scale) /
+				2));
+	}));
+	this.put('customZoom', new Action(mxResources.get('custom'), function() {
+		var value = mxUtils.prompt(mxResources.get('enterValue') + ' (%)',
+			parseInt(graph.getView().getScale() * 100));
+
+		if (value != null && value.length > 0 && !isNaN(parseInt(value))) {
+			graph.zoomTo(parseInt(value) / 100);
+		}
+	}));
+
+	// Option actions
+	var action = null;
+	action = this.addAction('grid', function() {
+		graph.setGridEnabled(!graph.isGridEnabled());
+		editor.updateGraphComponents();
+	}, null, null, 'Ctrl+Shift+G');
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.isGridEnabled();
+	});
+	action = this.addAction('guides', function() {
+		graph.graphHandler.guidesEnabled = !graph.graphHandler.guidesEnabled;
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.graphHandler.guidesEnabled;
+	});
+	action = this.addAction('tooltips', function() {
+		graph.tooltipHandler.setEnabled(!graph.tooltipHandler.isEnabled());
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.tooltipHandler.isEnabled();
+	});
+	action = this.addAction('navigation', function() {
+		graph.foldingEnabled = !graph.foldingEnabled;
+		graph.view.revalidate();
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.foldingEnabled;
+	});
+	action = this.addAction('scrollbars', function() {
+		graph.scrollbars = !graph.scrollbars;
+		editor.updateGraphComponents();
+
+		if (!graph.scrollbars) {
+			var t = graph.view.translate;
+			graph.view.setTranslate(t.x - graph.container.scrollLeft / graph.view.scale,
+				t.y - graph.container.scrollTop / graph.view.scale);
+			graph.container.scrollLeft = 0;
+			graph.container.scrollTop = 0;
+			graph.sizeDidChange();
+		} else {
+			var dx = graph.view.translate.x;
+			var dy = graph.view.translate.y;
+
+			graph.view.translate.x = 0;
+			graph.view.translate.y = 0;
+			graph.sizeDidChange();
+			graph.container.scrollLeft -= Math.round(dx * graph.view.scale);
+			graph.container.scrollTop -= Math.round(dy * graph.view.scale);
+		}
+	}, !mxClient.IS_TOUCH);
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.container.style.overflow == 'auto';
+	});
+	action = this.addAction('pageView', mxUtils.bind(this, function() {
+		graph.pageVisible = !graph.pageVisible;
+		graph.pageBreaksVisible = graph.pageVisible;
+		graph.preferPageSize = graph.pageBreaksVisible;
+		graph.view.validate();
+		graph.sizeDidChange();
+
+		editor.updateGraphComponents();
+		editor.outline.update();
+
+		if (mxUtils.hasScrollbars(graph.container)) {
+			if (graph.pageVisible) {
+				graph.container.scrollLeft -= 20;
+				graph.container.scrollTop -= 20;
+			} else {
+				graph.container.scrollLeft += 20;
+				graph.container.scrollTop += 20;
+			}
+		}
+	}));
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.pageVisible;
+	});
+	action = this.addAction('connect', function() {
+		graph.setConnectable(!graph.connectionHandler.isEnabled());
+	}, null, null, 'Ctrl+Q');
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.connectionHandler.isEnabled();
+	});
+	action = this.addAction('copyConnect', function() {
+		graph.connectionHandler.setCreateTarget(!graph.connectionHandler.isCreateTarget());
+	});
+	action.setToggleAction(true);
+	action.setSelectedCallback(function() {
+		return graph.connectionHandler.isCreateTarget();
+	});
+
+	// Help actions
+	this.addAction('help', function() {
+		var ext = '';
+
+		if (mxResources.isLanguageSupported(mxClient.language)) {
+			ext = '_' + mxClient.language;
+		}
+
+		window.open(RESOURCES_PATH + '/help' + ext + '.html');
+	});
+	this.put('about', new Action(mxResources.get('about') + ' Cally Square',
+		function() {
+			ui.showDialog(new AboutDialog(ui).container, 320, 280, true, true);
+		}, null, null, 'F1'));
+
+	// Font style actions
+	var toggleFontStyle = mxUtils.bind(this, function(key, style) {
+		this.addAction(key, function() {
+			graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, style);
+		});
+	});
+
+	toggleFontStyle('bold', mxConstants.FONT_BOLD);
+	toggleFontStyle('italic', mxConstants.FONT_ITALIC);
+	toggleFontStyle('underline', mxConstants.FONT_UNDERLINE);
+
+	// Format actions
+	this.addAction('shadow', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_SHADOW);
+	});
+	this.addAction('dashed', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_DASHED);
+	});
+	this.addAction('rounded', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_ROUNDED);
+	});
+	this.addAction('curved', function() {
+		graph.toggleCellStyles(mxConstants.STYLE_CURVED);
+	});
+	this.addAction('style', function() {
+		var cells = graph.getSelectionCells();
+
+		if (cells != null && cells.length > 0) {
+			var model = graph.getModel();
+			var style = mxUtils.prompt(mxResources.get('enterValue') + ' (' +
+				mxResources.get('style') + ')',
+				model.getStyle(cells[0]) || '');
+
+			if (style != null) {
+				graph.setCellStyle(style, cells);
+			}
+		}
+	});
+	this.addAction('setAsDefaultEdge', function() {
+		graph.setDefaultEdge(graph.getSelectionCell());
+	});
+	this.addAction('addWaypoint', function() {
+		var cell = graph.getSelectionCell();
+
+		if (cell != null && graph.getModel().isEdge(cell)) {
+			var handler = editor.graph.selectionCellsHandler.getHandler(cell);
+
+			if (handler instanceof mxEdgeHandler) {
+				var t = graph.view.translate;
+				var s = graph.view.scale;
+				var dx = t.x;
+				var dy = t.y;
+
+				var parent = graph.getModel().getParent(cell);
+				var pgeo = graph.getCellGeometry(parent);
+
+				if (graph.getModel().isVertex(parent) && pgeo != null) {
+					dx += pgeo.x;
+					dy += pgeo.y;
+				}
+
+				handler.addPointAt(handler.state, graph.panningHandler.triggerX / s - dx,
+					graph.panningHandler.triggerY / s - dy);
+			}
+		}
+	});
+	this.addAction('removeWaypoint', function() {
+		// TODO: Action should run with "this" set to action
+		var rmWaypointAction = ui.actions.get('removeWaypoint');
+
+		if (rmWaypointAction.handler != null) {
+			// NOTE: Popupevent handled and action updated in Menus.createPopupMenu
+			rmWaypointAction.handler.removePoint(rmWaypointAction.handler.state,
+				rmWaypointAction.index);
+		}
+	});
+	this.addAction('image', function() {
+		function updateImage(value, w, h) {
+			var select = null;
+			var cells = graph.getSelectionCells();
+
+			graph.getModel().beginUpdate();
+			try {
+				// Inserts new cell if no cell is selected
+				if (cells.length == 0) {
+					var gs = graph.getGridSize();
+					cells = [graph.insertVertex(graph.getDefaultParent(), null, '', gs, gs,
+						w, h)];
+					select = cells;
+				}
+
+				graph.setCellStyles(mxConstants.STYLE_IMAGE, value, cells);
+				graph.setCellStyles(mxConstants.STYLE_SHAPE, 'image', cells);
+
+				if (graph.getSelectionCount() == 1) {
+					if (w != null && h != null) {
+						var cell = cells[0];
+						var geo = graph.getModel().getGeometry(cell);
+
+						if (geo != null) {
+							geo = geo.clone();
+							geo.width = w;
+							geo.height = h;
+							graph.getModel().setGeometry(cell, geo);
+						}
+					}
+				}
+			} finally {
+				graph.getModel().endUpdate();
+			}
+
+			if (select != null) {
+				graph.setSelectionCells(select);
+				graph.scrollCellToVisible(select[0]);
+			}
+		};
+
+		var value = '';
+		var state = graph.getView().getState(graph.getSelectionCell());
+
+		if (state != null) {
+			value = state.style[mxConstants.STYLE_IMAGE] || value;
+		}
+
+		value = mxUtils.prompt(mxResources.get('enterValue') + ' (' + mxResources.get(
+			'url') + ')', value);
+
+		if (value != null) {
+			if (value.length > 0) {
+				var img = new Image();
+
+				img.onload = function() {
+					updateImage(value, img.width, img.height);
+				};
+				img.onerror = function() {
+					mxUtils.alert(mxResources.get('fileNotFound'));
+				};
+
+				img.src = value;
+			}
+		}
+	});
+};
+
+/**
+ * Registers the given action under the given name.
+ */
+Actions.prototype.addAction = function(key, funct, enabled, iconCls, shortcut) {
+	return this.put(key, new Action(mxResources.get(key), funct, enabled, iconCls,
+		shortcut));
+};
+
+/**
+ * Registers the given action under the given name.
+ */
+Actions.prototype.put = function(name, action) {
+	this.actions[name] = action;
+
+	return action;
+};
+
+/**
+ * Returns the action for the given name or null if no such action exists.
+ */
+Actions.prototype.get = function(name) {
+	return this.actions[name];
+};
+
+/**
+ * Constructs a new action for the given parameters.
+ */
+function Action(label, funct, enabled, iconCls, shortcut) {
+	mxEventSource.call(this);
+	this.label = label;
+	this.funct = funct;
+	this.enabled = (enabled != null) ? enabled : true;
+	this.iconCls = iconCls;
+	this.shortcut = shortcut;
+};
+
+// Action inherits from mxEventSource
+mxUtils.extend(Action, mxEventSource);
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setEnabled = function(value) {
+	if (this.enabled != value) {
+		this.enabled = value;
+		this.fireEvent(new mxEventObject('stateChanged'));
+	}
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setToggleAction = function(value) {
+	this.toggleAction = value;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setSelectedCallback = function(funct) {
+	this.selectedCallback = funct;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.isSelected = function() {
+	return this.selectedCallback();
+};
diff --git a/public/assets/plugins/square/js/Dialogs.js b/public/assets/plugins/square/js/Dialogs.js
index 82e2cb3..c78d34e 100644
--- a/public/assets/plugins/square/js/Dialogs.js
+++ b/public/assets/plugins/square/js/Dialogs.js
@@ -1 +1,1048 @@
\ No newline at end of file
+'use strict';
+/**
+ * $Id: Dialogs.js,v 1.5 2013-01-29 17:23:31 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new dialog.
+ */
+
+function createCheckbox(value) {
+	var input = document.createElement('input');
+	input.setAttribute('type', 'checkbox');
+	if (value) {
+		input.setAttribute('checked', true);
+	}
+	return input;
+}
+
+function createDropdownFromApi(path, value, option_name, option_value, editorUi, keys, pattern) {
+	var req = new XMLHttpRequest();
+	req.open('GET', path, false); // `false` makes the request synchronous
+	req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+	req.send(null);
+	var res = [];
+	if (req.status === 200) {
+		res = JSON.parse(req.response);
+	}
+	var input = document.createElement('select');
+	var option = document.createElement('option');
+	option.text = '-- None --';
+	option.value = '-1';
+	input.appendChild(option);
+	res.rows.forEach(function(elem) {
+		option = document.createElement('option');
+		option.text = elem[option_name];
+		if (keys && pattern) {
+			keys.forEach(function(key) {
+				pattern = pattern.replace('%' + key + '%', elem[key]);
+			});
+			option.value = pattern;
+			option.selected = (pattern == value);
+		} else {
+			option.value = elem[option_value];
+			option.selected = (elem[option_value] == value);
+		}
+		input.appendChild(option);
+	});
+	input.className = 'form-control select2';
+
+	return input;
+}
+
+function createGroupedDropdownFromApi(path, value, option_name, option_value, editorUi, associationField) {
+	var req = new XMLHttpRequest();
+	req.open('GET', path, false); // `false` makes the request synchronous
+	req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+	req.send(null);
+	var res = [];
+	if (req.status === 200) {
+		res = JSON.parse(req.response);
+	}
+	var input = document.createElement('select');
+	var option = document.createElement('option');
+	option.text = '-- None --';
+	option.value = '0';
+	input.appendChild(option);
+	var mainFilter = {};
+	var groupFilter = {};
+	mainFilter[associationField] = null;
+	var mainValues = _.filter(res.rows, mainFilter);
+	var groupValues = {};
+	mainValues.forEach(function(elem) {
+		option = document.createElement('option');
+		option.className = 'select-group-father';
+		option.text = elem[option_name].toUpperCase();
+		option.value = elem[option_value];
+		option.selected = (elem[option_value] == value);
+		input.appendChild(option);
+		groupFilter[associationField] = elem[option_value];
+		groupValues = _.filter(res.rows, groupFilter);
+		groupValues.forEach(function(elem) {
+			option = document.createElement('option');
+			option.className = 'select-group-son';
+			option.text = '-' + _.capitalize(elem[option_name]);
+			option.value = elem[option_value];
+			option.selected = (elem[option_value] == value);
+			input.appendChild(option);
+		});
+	})
+	input.className = 'form-control select2';
+
+	return input;
+}
+
+function createDropdownFromArray(array, value) {
+	var input = document.createElement('select');
+
+	for (var item in array) {
+		var option = document.createElement('option');
+		option.text = array[item];
+		option.value = item;
+		if (value > 0 || value != '')
+			option.selected = (value === item) ? true : false;
+
+		input.appendChild(option);
+	}
+
+	input.className = 'form-control select2';
+
+	return input;
+}
+
+function Dialog(editorUi, elt, w, h, modal, closable, onClose) {
+	var dx = 0;
+
+	if (mxClient.IS_IE && document.documentMode != 9) {
+		dx = 60;
+	}
+
+	w += dx;
+	h += dx;
+
+	var left = Math.max(0, Math.round((document.body.scrollWidth - w) / 2));
+	var top = Math.max(0, Math.round((Math.max(document.body.scrollHeight,
+		document.documentElement.scrollHeight) - h) / 3));
+
+	var div = editorUi.createDiv('geDialog');
+	div.className = 'modal fade in center';
+	div.style.display = 'block';
+	div.style.paddingRight = '12px';
+	// div.style.width = w + 'px';
+	// div.style.height = h + 'px';
+	// div.style.left = left + 'px';
+	// div.style.top = top + 'px';
+
+	var divModalDialog = editorUi.createDiv('geModalDialog');
+	divModalDialog.className = 'modal-dialog';
+
+	divModalDialog.appendChild(elt);
+	div.appendChild(divModalDialog);
+
+	if (this.bg == null) {
+		this.bg = editorUi.createDiv('background');
+		this.bg.className = 'modal-backdrop fade in';
+
+		if (mxClient.IS_QUIRKS) {
+			new mxDivResizer(this.bg);
+		}
+	}
+
+	if (modal) {
+		document.body.appendChild(this.bg);
+	}
+
+	document.body.appendChild(div);
+
+	this.onDialogClose = onClose;
+	this.container = div;
+};
+
+/**
+ * Removes the dialog from the DOM.
+ */
+Dialog.prototype.close = function() {
+	if (this.onDialogClose != null) {
+		this.onDialogClose();
+		this.onDialogClose = null;
+	}
+
+	this.container.parentNode.removeChild(this.container);
+	this.bg.parentNode.removeChild(this.bg);
+};
+
+/**
+ * Constructs a new open dialog.
+ */
+function ImportDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('import') + ' XML');
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-12');
+
+	var textarea = document.createElement('textarea');
+	textarea.style.width = '100%';
+	textarea.style.height = '374px';
+
+	var input = document.createElement('input');
+	input.type = 'file';
+	input.setAttribute('accept', 'text/xml');
+
+	input.addEventListener('change', function readSingleFile(evt) {
+		//Retrieve the first (and only!) File from the FileList object
+		var f = evt.target.files[0];
+		console.log(f);
+		if (f) {
+			if (f.type === 'text/xml') {
+				var r = new FileReader();
+				r.onload = function(e) {
+					var contents = e.target.result;
+					mxUtils.write(textarea, contents);
+				};
+				r.readAsText(f);
+			} else {
+				alert('Failed to load format file');
+			}
+		} else {
+			alert('Failed to load file');
+		}
+	}, false);
+
+	col1.appendChild(input);
+	col1.appendChild(textarea);
+
+	row.appendChild(col1);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('import'), mxUtils.bind(this,
+		function(data) {
+			var doc = mxUtils.parseXml(textarea.value);
+			editorUi.editor.setGraphXml(doc.documentElement);
+			editorUi.hideDialog();
+		}));
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+
+};
+
+/**
+ * Constructs a new about dialog.
+ */
+function AboutDialog(editorUi) {
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('about') + ' Cally Square');
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var img = document.createElement('img');
+	img.style.border = '0px';
+	img.setAttribute('width', '176');
+	img.setAttribute('width', '151');
+	img.setAttribute('src', IMAGE_PATH + '/logo.png');
+	body.appendChild(img);
+	mxUtils.br(body);
+	mxUtils.write(body, 'Powered by Xenialab ' + mxClient.VERSION);
+	mxUtils.br(body);
+	var link = document.createElement('a');
+	link.setAttribute('href', 'http://www.callysquare.com/');
+	link.setAttribute('target', '_blank');
+	mxUtils.write(link, 'www.callysquare.com');
+	body.appendChild(link);
+	mxUtils.br(body);
+	mxUtils.br(body);
+	//--- END BODY
+
+	var close = mxUtils.button(mxResources.get('close'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function SaveDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('saveAs'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var name = 'name';
+	var id = '_' + name;
+	var value = editorUi.editor.getOrCreateFilename();
+
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get(name));
+	col1.appendChild(label);
+
+	var select = document.createElement('input');
+	select.setAttribute('value', value + '_copy');
+	select.setAttribute('id', id)
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), function() {
+		editorUi.saveAs(select.value);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function NewDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('new'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var name = 'name';
+	var id = '_' + name;
+	var value = editorUi.editor.getOrCreateFilename();
+
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get(name));
+	col1.appendChild(label);
+
+	var select = document.createElement('input');
+	select.setAttribute('value', value + '_new');
+	select.setAttribute('id', id)
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('new'), function() {
+		editorUi.new(select.value);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function VariableDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('variable'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var name = 'name';
+	var id = '_' + name;
+	var value = editorUi.editor.getOrCreateFilename();
+
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get(name));
+	col1.appendChild(label);
+
+	var select = document.createElement('input');
+	select.setAttribute('value', 'variable name');
+	select.setAttribute('id', id)
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('new'), function() {
+		editorUi.variable(select.value);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new save dialog.
+ */
+function OpenDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('open'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get('name'));
+	col1.appendChild(label);
+
+	var req = new XMLHttpRequest();
+	req.open('GET', '/api/square/projects', false); // `false` makes the request synchronous
+	req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+	req.send(null);
+	var res = [];
+	if (req.status === 200) {
+		res = JSON.parse(req.response);
+	}
+
+	var select = document.createElement('select');
+
+	for (var j = 0; j < res.length; j++) {
+		var option = document.createElement('option');
+		option.text = res[j].name;
+		option.value = res[j].id;
+		select.appendChild(option);
+	}
+	select.className = 'form-control';
+	col2.appendChild(select);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('open'), function() {
+		console.log(select);
+		console.log(select.value);
+		window.open('square/project/' + select.value, '_blank');
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+/**
+ * Constructs a new rename dialog.
+ */
+function RenameDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('rename'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-4');
+	var col2 = editorUi.createDiv('col-md-8');
+
+	var label = document.createElement('label');
+	label.className = 'control-label pull-right';
+	mxUtils.write(label, mxResources.get('name'));
+	col1.appendChild(label);
+	var name = editorUi.editor.filename;
+	var input = document.createElement('input');
+	input.setAttribute('value', name);
+	input.className = 'form-control';
+
+	col2.appendChild(input);
+
+	row.appendChild(col1);
+	row.appendChild(col2);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), function() {
+		var req = new XMLHttpRequest();
+
+		req.open("PUT", SAVE_URL + editorUi.editor.data.id, true);
+		req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+		req.setRequestHeader('Authorization', 'Bearer ' + editorUi.editor.data.token);
+		req.send('name=' + input.value);
+
+		req.onload = function(e) {
+			if (req.status === 200) {
+				editorUi.editor.setStatus('Project successfully renamed');
+				editorUi.editor.filename = input.value;
+
+			} else {
+				console.log(req.response);
+				editorUi.editor.setStatus(JSON.parse(req.response).errors[0].message);
+			}
+		}
+		req.onerror = function(e) {
+			mxUtils.alert(req.statusText);
+		};
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+	//nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+};
+
+/**
+ * Constructs a new edit file dialog.
+ */
+function EditFileDialog(editorUi) {
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('edit'));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var row = editorUi.createDiv('row');
+	var col1 = editorUi.createDiv('col-md-12');
+
+	var textarea = document.createElement('textarea');
+	textarea.style.width = '100%';
+	textarea.style.height = '374px';
+	textarea.value = mxUtils.getPrettyXml(editorUi.editor.getGraphXml());
+
+	// Enables dropping files
+	if (fileSupport) {
+		function handleDrop(evt) {
+			evt.stopPropagation();
+			evt.preventDefault();
+
+			if (evt.dataTransfer.files.length > 0) {
+				var file = evt.dataTransfer.files[0];
+
+				var reader = new FileReader();
+				reader.onload = function(e) {
+					textarea.value = e.target.result;
+				};
+				reader.readAsText(file);
+			}
+		};
+
+		function handleDragOver(evt) {
+			evt.stopPropagation();
+			evt.preventDefault();
+		};
+
+		// Setup the dnd listeners.
+		textarea.addEventListener('dragover', handleDragOver, false);
+		textarea.addEventListener('drop', handleDrop, false);
+	}
+	col1.appendChild(textarea);
+
+	row.appendChild(col1);
+	body.appendChild(row);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), function() {
+		var doc = mxUtils.parseXml(textarea.value);
+		editorUi.editor.setGraphXml(doc.documentElement);
+		editorUi.hideDialog();
+	});
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
+
+/**
+ * Constructs a new export dialog.
+ */
+function ExportDialog(editorUi) {
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body form');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('export') + ' XML');
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var form = editorUi.createDiv('form-horizontal form-row-seperated');
+	var group = editorUi.createDiv('form-group last');
+
+	var label = document.createElement('label');
+	label.className = 'col-sm-4 control-label';
+	mxUtils.write(label, mxResources.get('filename'));
+
+	var input = document.createElement('input');
+	input.setAttribute('value', editorUi.editor.getOrCreateFilename());
+	input.className = 'form-control';
+
+	var div = editorUi.createDiv('col-sm-8');
+	div.appendChild(input);
+
+	group.appendChild(label);
+	group.appendChild(div);
+	form.appendChild(group);
+
+	body.appendChild(form);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('export'), mxUtils.bind(this,
+		function(data) {
+			editorUi.save(false);
+
+			var xml = encodeURIComponent(mxUtils.getXml(editorUi.editor.getGraphXml()));
+			new mxXmlRequest(SAVE_URL + editorUi.editor.data.id + '/download',
+				'filename=' + input.value, 'GET').simulate(document, "_blank");
+			editorUi.hideDialog();
+		}));
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
+
+/**
+ * Giuseppe Careri
+ * Constructs a new general dialog.
+ */
+function GeneralDialog(editorUi, cell) {
+	var graph = editorUi.editor.graph;
+
+	var content = editorUi.createDiv('modal-content');
+	var header = editorUi.createDiv('modal-header');
+	var body = editorUi.createDiv('modal-body form modal-body-scroll');
+	var footer = editorUi.createDiv('modal-footer');
+
+	//--- START HEADER
+	var title = editorUi.createHeader('h4');
+	mxUtils.write(title, mxResources.get('edit') + ' ' + mxResources.get(cell.value
+		.nodeName));
+
+	var x = mxUtils.button('', function() {
+		editorUi.hideDialog();
+	});
+	x.className = 'close';
+
+	header.appendChild(x);
+	header.appendChild(title);
+	//--- END HEADER
+
+	//--- START BODY
+	var length = cell.value.attributes.length;
+	var form = editorUi.createDiv('form-horizontal form-row-seperated');
+
+	for (var i = 0; i < length; i++) {
+		var name = cell.value.attributes[i].name;
+		var id = '_' + name;
+		var value = cell.value.attributes[i].value;
+
+		var group = editorUi.createDiv((i == length - 1) ? 'form-group last' :
+			'form-group');
+
+		var label = document.createElement('label');
+		label.className = 'col-sm-4 control-label';
+		mxUtils.write(label, mxResources.get(name));
+		group.appendChild(label);
+
+		var input;
+
+		switch (name) {
+			case 'sip_id':
+				input = createDropdownFromApi('/api/agents', value, 'name', 'id', editorUi);
+				break;
+			case 'queue_id':
+				input = createDropdownFromApi('/api/voice/queues', value, 'name', 'name', editorUi);
+				break;
+			case 'trunk_id':
+				input = createDropdownFromApi('/api/trunks', value, 'name', 'id', editorUi);
+				break;
+			case 'variable_id':
+				input = createDropdownFromApi('/api/variables', value, 'name', 'id', editorUi);
+				break;
+			case 'context':
+				input = createDropdownFromApi('/api/voice/contexts', value, 'name', 'name', editorUi);
+				break;
+			case 'mailbox':
+				input = createDropdownFromApi('/api/voice/voicemails', value, 'mailbox', 'id', editorUi, ['mailbox', 'context'], '%mailbox%@%context%');
+				break;
+			case 'model':
+				input = createDropdownFromArray(ISPEECHASRMODEL, value);
+				break;
+			case 'ispeech_asr_language':
+				input = createDropdownFromArray(ISPEECHASRLANG, value);
+				break;
+			case 'ispeech_tts_language':
+				input = createDropdownFromArray(ISPEECHLANG, value);
+				break;
+			case 'google_tts_language':
+				input = createDropdownFromArray(GOOGLETTSLANG, value);
+				break;
+			case 'interval_id':
+				input = createGroupedDropdownFromApi('/api/intervals', value, 'name', 'id', editorUi, 'IntervalId');
+				break;
+			case 'project_id':
+				input = createDropdownFromApi('/api/square/projects', value, 'name', 'id', editorUi);
+				break;
+			case 'odbc_id':
+				input = createDropdownFromApi('/api/square/odbc', value, 'name', 'id', editorUi);
+				break;
+			case 'file_id':
+				input = createDropdownFromApi('/api/sounds', value, 'display_name', 'id', editorUi);
+				break;
+			case 'timeout':
+			case 'digit':
+			case 'mindigit':
+			case 'maxdigit':
+			case 'response':
+			case 'retry':
+				input = document.createElement('input');
+				input.setAttribute('type', 'number');
+				input.setAttribute('min', 0);
+				input.setAttribute('max', 1000);
+				input.setAttribute('value', value);
+				input.className = 'form-control';
+				break;
+			case 'text':
+			case 'key':
+			case 'query':
+			case 'condition':
+			case 'command':
+				input = document.createElement('textarea');
+				input.innerHTML = value;
+				input.className = 'form-control';
+				break;
+			default:
+				input = document.createElement('input');
+				input.setAttribute('value', value);
+				input.className = 'form-control';
+				break;
+		}
+
+		input.setAttribute('id', id)
+
+		var div = editorUi.createDiv('col-sm-8');
+		div.appendChild(input);
+
+		// Help
+		if (mxResources.get('help_' + name)) {
+			var help = editorUi.createDiv('p');
+			help.className = 'help-block';
+			mxUtils.write(help, mxResources.get('help_' + name));
+			div.appendChild(help);
+		}
+
+		group.appendChild(div);
+		form.appendChild(group);
+	}
+	body.appendChild(form);
+	//--- END BODY
+
+	//--- START FOOTER
+	var save = mxUtils.button(mxResources.get('save'), mxUtils.bind(this,
+		function(data) {
+			for (var i = 0; i < cell.value.attributes.length; i++) {
+				var id = '_' + cell.value.attributes[i].name;
+				var name = cell.value.attributes[i].name;
+				console.log(name, document.getElementById(id).value);
+				cell.setAttribute(name, document.getElementById(id).value);
+			};
+			graph.refresh(cell);
+			editorUi.hideDialog();
+		}));
+	save.className = 'btn blue';
+
+	var close = mxUtils.button(mxResources.get('cancel'), function() {
+		editorUi.hideDialog();
+	});
+	close.className = 'btn default';
+
+	footer.appendChild(save);
+	footer.appendChild(close);
+	//--- END FOOTER
+
+	//--- START CONTENT
+	content.appendChild(header);
+	content.appendChild(body);
+	content.appendChild(footer);
+	//--- END CONTENT
+
+	this.container = content;
+};
diff --git a/public/assets/plugins/square/js/Editor.js b/public/assets/plugins/square/js/Editor.js
index 1fbd323..9858989 100644
--- a/public/assets/plugins/square/js/Editor.js
+++ b/public/assets/plugins/square/js/Editor.js
@@ -1 +1,888 @@
\ No newline at end of file
+/**
+ * $Id: Editor.js,v 1.14 2013/03/06 17:57:10 boris Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+// Specifies if local storage should be used (eg. on the iPad which has no filesystem)
+var useLocalStorage = (mxClient.IS_TOUCH || urlParams['storage'] == 'local') && typeof(localStorage) != 'undefined';
+var fileSupport = window.File != null && window.FileReader != null && window.FileList != null;
+
+// Specifies if connector should be shown on selected cells
+var touchStyle = mxClient.IS_TOUCH || urlParams['touch'] == '1';
+
+// Counts open editor tabs (must be global for cross-window access)
+var counter = 0;
+
+// Cross-domain window access is not allowed in FF, so if we
+// were opened from another domain then this will fail.
+try {
+	var op = window;
+
+	while (op.opener != null && !isNaN(op.opener.counter)) {
+		op = op.opener;
+	}
+
+	// Increments the counter in the first opener in the chain
+	if (op != null) {
+		op.counter++;
+		counter = op.counter;
+	}
+} catch (e) {
+	// ignore
+}
+
+/**
+ * Editor constructor executed on page load.
+ */
+Editor = function() {
+	mxEventSource.call(this);
+	this.init();
+	this.initStencilRegistry();
+	this.graph = new Graph();
+	this.outline = new mxOutline(this.graph);
+	this.outline.updateOnPan = true;
+	this.undoManager = this.createUndoManager();
+	this.status = '';
+
+	// Contains the name which was used for the last save. Default value is null.
+	this.filename = null;
+
+	this.getOrCreateFilename = function() {
+		return this.filename || mxResources.get('drawing', [counter]) + '.xml';
+	};
+
+	this.getFilename = function() {
+		return this.filename;
+	};
+
+	// Sets the status and fires a statusChanged event
+	this.setStatus = function(value) {
+		this.status = value;
+		this.fireEvent(new mxEventObject('statusChanged'));
+	};
+
+	// Returns the current status
+	this.getStatus = function() {
+		return this.status;
+	};
+
+	// Contains the current modified state of the diagram. This is false for
+	// new diagrams and after the diagram was saved.
+	this.modified = false;
+
+	// Updates modified state if graph changes
+	this.graphChangeListener = function() {
+		this.modified = true;
+	};
+	this.graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function() {
+		this.graphChangeListener.apply(this, arguments);
+	}));
+
+	// Installs dialog if browser window is closed without saving
+	// This must be disabled during save and image export
+	window.onbeforeunload = mxUtils.bind(this, function() {
+		if (this.modified) {
+			return mxResources.get('allChangesLost');
+		}
+	});
+
+	// Sets persistent graph state defaults
+	this.graph.resetViewOnRootChange = false;
+	this.graph.scrollbars = true;
+	this.graph.background = null;
+};
+
+// Editor inherits from mxEventSource
+mxUtils.extend(Editor, mxEventSource);
+
+/**
+ * Specifies the image URL to be used for the grid.
+ */
+Editor.prototype.gridImage = IMAGE_PATH + '/grid.gif';
+
+/**
+ * Specifies the image URL to be used for the transparent background.
+ */
+Editor.prototype.transparentImage = IMAGE_PATH + '/transparent.gif';
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.setGraphXml = function(node) {
+	var dec = new mxCodec(node.ownerDocument);
+
+	if (node.nodeName == 'mxGraphModel') {
+		this.graph.view.scale = 1;
+		this.graph.gridEnabled = node.getAttribute('grid') != '0';
+		this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
+		this.graph.setTooltips(node.getAttribute('tooltips') != '0');
+		this.graph.setConnectable(node.getAttribute('connect') != '0');
+		this.graph.foldingEnabled = node.getAttribute('fold') != '0';
+		this.graph.scrollbars = node.getAttribute('scrollbars') != '0';
+
+		if (!this.graph.scrollbars) {
+			this.graph.container.scrollLeft = 0;
+			this.graph.container.scrollTop = 0;
+			this.graph.view.translate.x = Number(node.getAttribute('dx') || 0);
+			this.graph.view.translate.y = Number(node.getAttribute('dy') || 0);
+		}
+
+		this.graph.pageVisible = node.getAttribute('page') == '1';
+		this.graph.pageBreaksVisible = this.graph.pageVisible;
+		this.graph.preferPageSize = this.graph.pageBreaksVisible;
+
+		// Loads the persistent state settings
+		var ps = node.getAttribute('pageScale');
+
+		if (ps != null) {
+			this.graph.pageScale = ps;
+		} else {
+			this.graph.pageScale = 1.5;
+		}
+
+		var pw = node.getAttribute('pageWidth');
+		var ph = node.getAttribute('pageHeight');
+
+		if (pw != null && ph != null) {
+			this.graph.pageFormat = new mxRectangle(0, 0, parseFloat(pw), parseFloat(ph));
+			this.outline.outline.pageFormat = this.graph.pageFormat;
+		}
+
+		// Loads the persistent state settings
+		var bg = node.getAttribute('background');
+
+		if (bg != null && bg.length > 0) {
+			this.graph.background = bg;
+		}
+
+		dec.decode(node, this.graph.getModel());
+		this.updateGraphComponents();
+	}
+};
+
+/**
+ * Returns the XML node that represents the current diagram.
+ */
+Editor.prototype.getGraphXml = function() {
+	var enc = new mxCodec(mxUtils.createXmlDocument());
+	var node = enc.encode(this.graph.getModel());
+
+	if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0) {
+		node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
+		node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
+	}
+
+	node.setAttribute('grid', (this.graph.isGridEnabled()) ? '1' : '0');
+	node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
+	node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
+	node.setAttribute('tooltips', (this.graph.tooltipHandler.isEnabled()) ? '1' : '0');
+	node.setAttribute('connect', (this.graph.connectionHandler.isEnabled()) ? '1' : '0');
+	node.setAttribute('fold', (this.graph.foldingEnabled) ? '1' : '0');
+	node.setAttribute('page', (this.graph.pageVisible) ? '1' : '0');
+	node.setAttribute('pageScale', this.graph.pageScale);
+	node.setAttribute('pageWidth', this.graph.pageFormat.width);
+	node.setAttribute('pageHeight', this.graph.pageFormat.height);
+
+	if (!this.graph.scrollbars) {
+		node.setAttribute('scrollbars', '0');
+	}
+
+	if (this.graph.background != null) {
+		node.setAttribute('background', this.graph.background);
+	}
+
+	return node;
+};
+
+/**
+ * Keeps the graph container in sync with the persistent graph state
+ */
+Editor.prototype.updateGraphComponents = function() {
+	var graph = this.graph;
+	var outline = this.outline;
+
+	if (graph.container != null && outline.outline.container != null) {
+		if (graph.background != null) {
+			if (graph.background == 'none') {
+				graph.container.style.backgroundColor = 'transparent';
+			} else {
+				if (graph.view.backgroundPageShape != null) {
+					graph.view.backgroundPageShape.fill = graph.background;
+					graph.view.backgroundPageShape.reconfigure();
+				}
+
+				graph.container.style.backgroundColor = graph.background;
+			}
+		} else {
+			graph.container.style.backgroundColor = '';
+		}
+
+		if (graph.pageVisible) {
+			graph.container.style.backgroundColor = '#ebebeb';
+			graph.container.style.borderStyle = 'solid';
+			graph.container.style.borderColor = '#e5e5e5';
+			graph.container.style.borderTopWidth = '1px';
+			graph.container.style.borderLeftWidth = '1px';
+			graph.container.style.borderRightWidth = '0px';
+			graph.container.style.borderBottomWidth = '0px';
+		} else {
+			graph.container.style.border = '';
+		}
+
+		outline.outline.container.style.backgroundColor = graph.container.style.backgroundColor;
+
+		if (outline.outline.pageVisible != graph.pageVisible ||
+			outline.outline.pageScale != graph.pageScale) {
+			outline.outline.pageScale = graph.pageScale;
+			outline.outline.pageVisible = graph.pageVisible;
+			outline.outline.view.validate();
+		}
+
+		if (graph.scrollbars && graph.container.style.overflow == 'hidden' && !touchStyle) {
+			graph.container.style.overflow = 'auto';
+		} else if (!graph.scrollbars || touchStyle) {
+			graph.container.style.overflow = 'hidden';
+		}
+
+		// Transparent.gif is a workaround for focus repaint problems in IE
+		var noBackground = (mxClient.IS_IE && document.documentMode >= 9) ? 'url(' + this.transparentImage + ')' : 'none';
+		graph.container.style.backgroundImage = (!graph.pageVisible && graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : noBackground;
+
+		if (graph.view.backgroundPageShape != null) {
+			graph.view.backgroundPageShape.node.style.backgroundImage = (this.graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : 'none';
+		}
+	}
+};
+
+/**
+ * Initializes the environment.
+ */
+Editor.prototype.init = function() {
+	// Adds stylesheet for IE6
+	if (mxClient.IS_IE6) {
+		mxClient.link('stylesheet', CSS_PATH + '/grapheditor-ie6.css');
+	}
+
+	// Adds required resources (disables loading of fallback properties, this can only
+	// be used if we know that all keys are defined in the language specific file)
+	mxResources.loadDefaultBundle = false;
+	mxResources.add(RESOURCE_BASE);
+
+	// Makes the connection hotspot smaller
+	mxConstants.DEFAULT_HOTSPOT = 0.3;
+
+	var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
+	mxConnectionHandler.prototype.createMarker = function() {
+		var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
+
+		// Overrides to ignore hotspot only for target terminal
+		marker.intersects = mxUtils.bind(this, function(state, evt) {
+			if (this.isConnecting()) {
+				return true;
+			}
+
+			return mxCellMarker.prototype.intersects.apply(marker, arguments);
+		});
+
+		return marker;
+	};
+
+	// Makes the shadow brighter
+	mxConstants.SHADOWCOLOR = '#d0d0d0';
+
+	// Changes some default colors
+	mxConstants.HANDLE_FILLCOLOR = '#99ccff';
+	mxConstants.HANDLE_STROKECOLOR = '#0088cf';
+	mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
+	mxConstants.OUTLINE_COLOR = '#00a8ff';
+	mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff';
+	mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff';
+	mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff';
+	mxConstants.EDGE_SELECTION_COLOR = '#00a8ff';
+	mxConstants.DEFAULT_VALID_COLOR = '#00a8ff';
+	mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff';
+	mxConstants.GUIDE_COLOR = '#0088cf';
+
+	mxGraph.prototype.pageBreakColor = '#c0c0c0';
+	mxGraph.prototype.pageScale = 1;
+
+	// Increases default rubberband opacity (default is 20)
+	mxRubberband.prototype.defaultOpacity = 30;
+
+	// Changes border color of background page shape
+	mxGraphView.prototype.createBackgroundPageShape = function(bounds) {
+		return new mxRectangleShape(bounds, this.graph.background || 'white', '#cacaca');
+	};
+
+	// Fits the number of background pages to the graph
+	mxGraphView.prototype.getBackgroundPageBounds = function() {
+		var gb = this.getGraphBounds();
+
+		// Computes unscaled, untranslated graph bounds
+		var x = (gb.width > 0) ? gb.x / this.scale - this.translate.x : 0;
+		var y = (gb.height > 0) ? gb.y / this.scale - this.translate.y : 0;
+		var w = gb.width / this.scale;
+		var h = gb.height / this.scale;
+
+		var fmt = this.graph.pageFormat;
+		var ps = this.graph.pageScale;
+
+		var pw = fmt.width * ps;
+		var ph = fmt.height * ps;
+
+		var x0 = Math.floor(Math.min(0, x) / pw);
+		var y0 = Math.floor(Math.min(0, y) / ph);
+		var xe = Math.ceil(Math.max(1, x + w) / pw);
+		var ye = Math.ceil(Math.max(1, y + h) / ph);
+
+		var rows = xe - x0;
+		var cols = ye - y0;
+
+		var bounds = new mxRectangle(this.scale * (this.translate.x + x0 * pw), this.scale *
+			(this.translate.y + y0 * ph), this.scale * rows * pw, this.scale * cols * ph);
+
+		return bounds;
+	};
+
+	// Add panning for background page in VML
+	var graphPanGraph = mxGraph.prototype.panGraph;
+	mxGraph.prototype.panGraph = function(dx, dy) {
+		graphPanGraph.apply(this, arguments);
+
+		if ((this.dialect != mxConstants.DIALECT_SVG && this.view.backgroundPageShape != null) &&
+			(!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container))) {
+			this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
+			this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
+		}
+	};
+
+	var editor = this;
+
+	// Uses HTML for background pages (to support grid background image)
+	mxGraphView.prototype.validateBackground = function() {
+		var bg = this.graph.getBackgroundImage();
+
+		if (bg != null) {
+			if (this.backgroundImage == null || this.backgroundImage.image != bg.src) {
+				if (this.backgroundImage != null) {
+					this.backgroundImage.destroy();
+				}
+
+				var bounds = new mxRectangle(0, 0, 1, 1);
+
+				this.backgroundImage = new mxImageShape(bounds, bg.src);
+				this.backgroundImage.dialect = this.graph.dialect;
+				this.backgroundImage.init(this.backgroundPane);
+				this.backgroundImage.redraw();
+			}
+
+			this.redrawBackgroundImage(this.backgroundImage, bg);
+		} else if (this.backgroundImage != null) {
+			this.backgroundImage.destroy();
+			this.backgroundImage = null;
+		}
+
+		if (this.graph.pageVisible) {
+			var bounds = this.getBackgroundPageBounds();
+
+			if (this.backgroundPageShape == null) {
+				this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+				this.backgroundPageShape.scale = 1;
+				this.backgroundPageShape.isShadow = true;
+				this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
+				this.backgroundPageShape.init(this.graph.container);
+				// Required for the browser to render the background page in correct order
+				this.graph.container.firstChild.style.position = 'absolute';
+				this.graph.container.insertBefore(this.backgroundPageShape.node, this.graph.container.firstChild);
+				this.backgroundPageShape.redraw();
+
+				this.backgroundPageShape.node.className = 'geBackgroundPage';
+				this.backgroundPageShape.node.style.backgroundPosition = '-1px -1px';
+
+				// Adds listener for double click handling on background
+				mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
+					mxUtils.bind(this, function(evt) {
+						this.graph.dblClick(evt);
+					})
+				);
+
+				// Adds basic listeners for graph event dispatching outside of the
+				// container and finishing the handling of a single gesture
+				mxEvent.addGestureListeners(this.backgroundPageShape.node,
+					mxUtils.bind(this, function(evt) {
+						this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt) {
+						// Hides the tooltip if mouse is outside container
+						if (this.graph.tooltipHandler != null &&
+							this.graph.tooltipHandler.isHideOnHover()) {
+							this.graph.tooltipHandler.hide();
+						}
+
+						if (this.graph.isMouseDown &&
+							!mxEvent.isConsumed(evt)) {
+							this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+								new mxMouseEvent(evt));
+						}
+					}),
+					mxUtils.bind(this, function(evt) {
+						this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+							new mxMouseEvent(evt));
+					}));
+			} else {
+				this.backgroundPageShape.scale = 1;
+				this.backgroundPageShape.bounds = bounds;
+				this.backgroundPageShape.redraw();
+			}
+
+			this.backgroundPageShape.node.style.backgroundImage = (this.graph.isGridEnabled()) ?
+				'url(' + editor.gridImage + ')' : 'none';
+		} else if (this.backgroundPageShape != null) {
+			this.backgroundPageShape.destroy();
+			this.backgroundPageShape = null;
+		}
+	};
+
+	// Draws page breaks only within the page
+	mxGraph.prototype.updatePageBreaks = function(visible, width, height) {
+		var scale = this.view.scale;
+		var tr = this.view.translate;
+		var fmt = this.pageFormat;
+		var ps = scale * this.pageScale;
+
+		var bounds2 = this.view.getBackgroundPageBounds();
+
+		width = bounds2.width;
+		height = bounds2.height;
+		var bounds = new mxRectangle(scale * tr.x, scale * tr.y,
+			fmt.width * ps, fmt.height * ps);
+
+		// Does not show page breaks if the scale is too small
+		visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+		var horizontalCount = (visible) ? Math.ceil(width / bounds.width) - 1 : 0;
+		var verticalCount = (visible) ? Math.ceil(height / bounds.height) - 1 : 0;
+		var right = bounds2.x + width;
+		var bottom = bounds2.y + height;
+
+		if (this.horizontalPageBreaks == null && horizontalCount > 0) {
+			this.horizontalPageBreaks = [];
+		}
+
+		if (this.horizontalPageBreaks != null) {
+			for (var i = 0; i <= horizontalCount; i++) {
+				var pts = [new mxPoint(bounds2.x + (i + 1) * bounds.width, bounds2.y),
+					new mxPoint(bounds2.x + (i + 1) * bounds.width, bottom)
+				];
+
+				if (this.horizontalPageBreaks[i] != null) {
+					this.horizontalPageBreaks[i].scale = 1;
+					this.horizontalPageBreaks[i].points = pts;
+					this.horizontalPageBreaks[i].redraw();
+				} else {
+					var pageBreak = new mxPolyline(pts, this.pageBreakColor, this.scale);
+					pageBreak.dialect = this.dialect;
+					pageBreak.isDashed = this.pageBreakDashed;
+					pageBreak.addPipe = false;
+					pageBreak.scale = scale;
+					pageBreak.init(this.view.backgroundPane);
+					pageBreak.redraw();
+
+					this.horizontalPageBreaks[i] = pageBreak;
+				}
+			}
+
+			for (var i = horizontalCount; i < this.horizontalPageBreaks.length; i++) {
+				this.horizontalPageBreaks[i].destroy();
+			}
+
+			this.horizontalPageBreaks.splice(horizontalCount, this.horizontalPageBreaks.length - horizontalCount);
+		}
+
+		if (this.verticalPageBreaks == null && verticalCount > 0) {
+			this.verticalPageBreaks = [];
+		}
+
+		if (this.verticalPageBreaks != null) {
+			for (var i = 0; i <= verticalCount; i++) {
+				var pts = [new mxPoint(bounds2.x, bounds2.y + (i + 1) * bounds.height),
+					new mxPoint(right, bounds2.y + (i + 1) * bounds.height)
+				];
+
+				if (this.verticalPageBreaks[i] != null) {
+					this.verticalPageBreaks[i].scale = 1; //scale;
+					this.verticalPageBreaks[i].points = pts;
+					this.verticalPageBreaks[i].redraw();
+				} else {
+					var pageBreak = new mxPolyline(pts, this.pageBreakColor, scale);
+					pageBreak.dialect = this.dialect;
+					pageBreak.isDashed = this.pageBreakDashed;
+					pageBreak.addPipe = false;
+					pageBreak.scale = scale;
+					pageBreak.init(this.view.backgroundPane);
+					pageBreak.redraw();
+
+					this.verticalPageBreaks[i] = pageBreak;
+				}
+			}
+
+			for (var i = verticalCount; i < this.verticalPageBreaks.length; i++) {
+				this.verticalPageBreaks[i].destroy();
+			}
+
+			this.verticalPageBreaks.splice(verticalCount, this.verticalPageBreaks.length - verticalCount);
+		}
+	};
+
+	// Enables snapping to off-grid terminals for edge waypoints
+	mxEdgeHandler.prototype.snapToTerminals = true;
+
+	// Enables guides
+	mxGraphHandler.prototype.guidesEnabled = true;
+
+	// Disables removing relative children from parents
+	var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
+	mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt) {
+		for (var i = 0; i < cells.length; i++) {
+			if (this.graph.getModel().isVertex(cells[i])) {
+				var geo = this.graph.getCellGeometry(cells[i]);
+
+				if (geo != null && geo.relative) {
+					return false;
+				}
+			}
+		}
+
+		return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
+	};
+
+	// Alt-move disables guides
+	mxGuide.prototype.isEnabledForEvent = function(evt) {
+		return !mxEvent.isAltDown(evt);
+	};
+
+	// Consumes click events for disabled menu items
+	mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
+	mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled) {
+		var result = mxPopupMenuAddItem.apply(this, arguments);
+
+		if (enabled != null && !enabled) {
+			mxEvent.addListener(result, 'mousedown', function(evt) {
+				mxEvent.consume(evt);
+			});
+		}
+
+		return result;
+	};
+
+	// Selects descendants before children selection mode
+	var graphHandlerGetInitialCellForEvent = mxGraphHandler.prototype.getInitialCellForEvent;
+	mxGraphHandler.prototype.getInitialCellForEvent = function(me) {
+		var model = this.graph.getModel();
+		var psel = model.getParent(this.graph.getSelectionCell());
+		var cell = graphHandlerGetInitialCellForEvent.apply(this, arguments);
+		var parent = model.getParent(cell);
+
+		if (psel == null || (psel != cell && psel != parent)) {
+			while (!this.graph.isCellSelected(cell) && !this.graph.isCellSelected(parent) &&
+				model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+				cell = parent;
+				parent = this.graph.getModel().getParent(cell);
+			}
+		}
+
+		return cell;
+	};
+
+	// Selection is delayed to mouseup if child selected
+	var graphHandlerIsDelayedSelection = mxGraphHandler.prototype.isDelayedSelection;
+	mxGraphHandler.prototype.isDelayedSelection = function(cell) {
+		var result = graphHandlerIsDelayedSelection.apply(this, arguments);
+		var model = this.graph.getModel();
+		var psel = model.getParent(this.graph.getSelectionCell());
+		var parent = model.getParent(cell);
+
+		if (psel == null || (psel != cell && psel != parent)) {
+			if (!this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+				result = true;
+			}
+		}
+
+		return result;
+	};
+
+	// Delayed selection of parent group
+	mxGraphHandler.prototype.selectDelayed = function(me) {
+		var cell = me.getCell();
+
+		if (cell == null) {
+			cell = this.cell;
+		}
+
+		var model = this.graph.getModel();
+		var parent = model.getParent(cell);
+
+		while (this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+			cell = parent;
+			parent = model.getParent(cell);
+		}
+
+		this.graph.selectCellForEvent(cell, me.getEvent());
+	};
+
+	// Returns last selected ancestor
+	mxPanningHandler.prototype.getCellForPopupEvent = function(me) {
+		var cell = me.getCell();
+		var model = this.graph.getModel();
+		var parent = model.getParent(cell);
+
+		while (model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
+			if (this.graph.isCellSelected(parent)) {
+				cell = parent;
+			}
+
+			parent = model.getParent(parent);
+		}
+
+		return cell;
+	};
+};
+
+/**
+ * Creates and returns a new undo manager.
+ */
+Editor.prototype.createUndoManager = function() {
+	var graph = this.graph;
+	var undoMgr = new mxUndoManager();
+
+	// Installs the command history
+	var listener = function(sender, evt) {
+		undoMgr.undoableEditHappened(evt.getProperty('edit'));
+	};
+
+	graph.getModel().addListener(mxEvent.UNDO, listener);
+	graph.getView().addListener(mxEvent.UNDO, listener);
+
+	// Keeps the selection in sync with the history
+	var undoHandler = function(sender, evt) {
+		var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes);
+		var cells = [];
+
+		for (var i = 1; i < cand.length; i++) {
+			if (graph.view.getState(cand[i]) != null) {
+				cells.push(cand[i]);
+			}
+		}
+
+		graph.setSelectionCells(cells);
+	};
+
+	undoMgr.addListener(mxEvent.UNDO, undoHandler);
+	undoMgr.addListener(mxEvent.REDO, undoHandler);
+
+	return undoMgr;
+};
+
+/**
+ * Adds basic stencil set (no namespace).
+ */
+Editor.prototype.initStencilRegistry = function() {
+	// Loads default stencils
+	mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/general.xml');
+};
+
+/**
+ * Overrides stencil registry for dynamic loading of stencils.
+ */
+(function() {
+	/**
+	 * Maps from library names to an array of Javascript filenames,
+	 * which are synchronously loaded. Currently only stencil files
+	 * (.xml) and JS files (.js) are supported.
+	 * IMPORTANT: For embedded diagrams to work entries must also
+	 * be added in EmbedServlet.java.
+	 */
+	mxStencilRegistry.libraries = {};
+
+	/**
+	 * Stores all package names that have been dynamically loaded.
+	 * Each package is only loaded once.
+	 */
+	mxStencilRegistry.packages = [];
+
+	// Extends the default stencil registry to add dynamic loading
+	mxStencilRegistry.getStencil = function(name) {
+		var result = mxStencilRegistry.stencils[name];
+
+		if (result == null) {
+			var basename = mxStencilRegistry.getBasenameForStencil(name);
+
+			// Loads stencil files and tries again
+			if (basename != null) {
+				var libs = mxStencilRegistry.libraries[basename];
+
+				if (libs != null) {
+					if (mxStencilRegistry.packages[basename] == null) {
+						mxStencilRegistry.packages[basename] = 1;
+
+						for (var i = 0; i < libs.length; i++) {
+							var fname = libs[i];
+
+							if (fname.toLowerCase().substring(fname.length - 4, fname.length) == '.xml') {
+								mxStencilRegistry.loadStencilSet(fname, null);
+							} else if (fname.toLowerCase().substring(fname.length - 3, fname.length) == '.js') {
+								var req = mxUtils.load(fname);
+
+								if (req != null) {
+									eval.call(window, req.getText());
+								}
+							} else {
+								// FIXME: This does not yet work as the loading is triggered after
+								// the shape was used in the graph, at which point the keys have
+								// typically been translated in the calling method.
+								//mxResources.add(fname);
+							}
+						}
+					}
+				} else {
+					mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null);
+				}
+
+				result = mxStencilRegistry.stencils[name];
+			}
+		}
+
+		return result;
+	};
+
+	// Returns the basename for the given stencil or null if no file must be
+	// loaded to render the given stencil.
+	mxStencilRegistry.getBasenameForStencil = function(name) {
+		var parts = name.split('.');
+		var tmp = null;
+
+		if (parts.length > 0 && parts[0] == 'mxgraph') {
+			tmp = parts[1];
+
+			for (var i = 2; i < parts.length - 1; i++) {
+				tmp += '/' + parts[i];
+			}
+		}
+
+		return tmp;
+	};
+
+	// Loads the given stencil set
+	mxStencilRegistry.loadStencilSet = function(stencilFile, postStencilLoad, force) {
+		force = (force != null) ? force : false;
+
+		// Uses additional cache for detecting previous load attempts
+		var xmlDoc = mxStencilRegistry.packages[stencilFile];
+
+		if (force || xmlDoc == null) {
+			var install = false;
+
+			if (xmlDoc == null) {
+				var req = mxUtils.load(stencilFile);
+				xmlDoc = req.getXml();
+				mxStencilRegistry.packages[stencilFile] = xmlDoc;
+				install = true;
+			}
+
+			mxStencilRegistry.parseStencilSet(xmlDoc, postStencilLoad, install);
+		}
+	};
+
+	// Parses the given stencil set
+	mxStencilRegistry.parseStencilSet = function(xmlDocument, postStencilLoad, install) {
+		install = (install != null) ? install : true;
+		var root = xmlDocument.documentElement;
+		var shape = root.firstChild;
+		var packageName = '';
+		var name = root.getAttribute('name');
+
+		if (name != null) {
+			packageName = name + '.';
+		}
+
+		while (shape != null) {
+			if (shape.nodeType == mxConstants.NODETYPE_ELEMENT) {
+				name = shape.getAttribute('name');
+
+				if (name != null) {
+					packageName = packageName.toLowerCase();
+					var stencilName = name.replace(/ /g, "_");
+
+					if (install) {
+						mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape));
+					}
+
+					if (postStencilLoad != null) {
+						var w = shape.getAttribute('w');
+						var h = shape.getAttribute('h');
+
+						w = (w == null) ? 80 : parseInt(w, 10);
+						h = (h == null) ? 80 : parseInt(h, 10);
+
+						postStencilLoad(packageName, stencilName, name, w, h);
+					}
+				}
+			}
+
+			shape = shape.nextSibling;
+		}
+	};
+})();
+
+/**
+ * Class for asynchronously opening a new window and loading a file at the same
+ * time. This acts as a bridge between the open dialog and the new editor.
+ */
+OpenFile = function(done) {
+	this.producer = null;
+	this.consumer = null;
+	this.done = done;
+};
+
+/**
+ * Registers the editor from the new window.
+ */
+OpenFile.prototype.setConsumer = function(value) {
+	this.consumer = value;
+	this.execute();
+};
+
+/**
+ * Sets the data from the loaded file.
+ */
+OpenFile.prototype.setData = function(value, filename) {
+	this.data = value;
+	this.filename = filename;
+	this.execute();
+};
+
+/**
+ * Displays an error message.
+ */
+OpenFile.prototype.error = function(msg) {
+	this.cancel();
+	mxUtils.alert(msg);
+};
+
+/**
+ * Consumes the data.
+ */
+OpenFile.prototype.execute = function() {
+	if (this.consumer != null && this.data != null) {
+		this.consumer(this.data, this.filename);
+		this.cancel();
+	}
+};
+
+/**
+ * Cancels the operation.
+ */
+OpenFile.prototype.cancel = function() {
+	if (this.done != null) {
+		this.done();
+	}
+};
diff --git a/public/assets/plugins/square/js/EditorUi.js b/public/assets/plugins/square/js/EditorUi.js
index f68281a..4104913 100644
--- a/public/assets/plugins/square/js/EditorUi.js
+++ b/public/assets/plugins/square/js/EditorUi.js
@@ -1 +1,1484 @@
\ No newline at end of file
+/**
+ * $Id: EditorUi.js,v 1.21 2013/03/14 20:46:36 david Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new graph editor
+ */
+EditorUi = function(editor, container) {
+	this.editor = editor || new Editor();
+	this.container = container || document.getElementById('geEditor');
+	var graph = editor.graph;
+	var self = this;
+
+	// Disables scrollbars
+	this.container.style.overflow = 'hidden';
+
+	// Pre-fetches submenu image
+	new Image().src = mxPopupMenu.prototype.submenuImage;
+
+	// Pre-fetches connect image
+	if (mxConnectionHandler.prototype.connectImage != null) {
+		new Image().src = mxConnectionHandler.prototype.connectImage.src;
+	}
+
+	// Creates the user interface
+	this.actions = new Actions(this);
+	this.menus = new Menus(this);
+	this.createDivs();
+	this.refresh();
+	this.createUi();
+
+	// Disables HTML and text selection
+	var textEditing = mxUtils.bind(this, function(evt) {
+		if (evt == null) {
+			evt = window.event;
+		}
+
+		if (this.isSelectionAllowed(evt)) {
+			return true;
+		}
+
+		return graph.isEditing();
+	});
+
+	// Disables text selection while not editing and no dialog visible
+	if (this.container == document.body) {
+		this.menubarContainer.onselectstart = textEditing;
+		this.menubarContainer.onmousedown = textEditing;
+		this.toolbarContainer.onselectstart = textEditing;
+		this.toolbarContainer.onmousedown = textEditing;
+		this.diagramContainer.onselectstart = textEditing;
+		this.diagramContainer.onmousedown = textEditing;
+		this.sidebarContainer.onselectstart = textEditing;
+		this.sidebarContainer.onmousedown = textEditing;
+		this.footerContainer.onselectstart = textEditing;
+		this.footerContainer.onmousedown = textEditing;
+	}
+
+	// And uses built-in context menu while editing
+	if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' ||
+			document.documentMode < 9)) {
+		mxEvent.addListener(this.diagramContainer, 'contextmenu', textEditing);
+		mxEvent.addListener(this.sidebarContainer, 'contextmenu', textEditing);
+	} else {
+		// Allows browser context menu outside of diagram and sidebar
+		this.diagramContainer.oncontextmenu = textEditing;
+		this.sidebarContainer.oncontextmenu = textEditing;
+	}
+
+	// Contains the main graph instance inside the given panel
+	graph.init(this.diagramContainer);
+	graph.refresh();
+
+	// Enables scrollbars and sets cursor style for the container
+	graph.container.setAttribute('tabindex', '0');
+	// graph.container.style.overflow = (touchStyle) ? 'hidden' : 'auto';
+	graph.container.style.cursor = 'default';
+	graph.container.style.backgroundImage = 'url(' + editor.gridImage + ')';
+	graph.container.style.backgroundPosition = '-1px -1px';
+	graph.container.focus();
+
+	// Keeps graph container focused on mouse down
+	var graphFireMouseEvent = graph.fireMouseEvent;
+	graph.fireMouseEvent = function(evtName, me, sender) {
+		if (evtName == mxEvent.MOUSE_DOWN) {
+			this.container.focus();
+		}
+
+		graphFireMouseEvent.apply(this, arguments);
+	};
+
+	// Giuseppe Careri
+	// Defines invalid connections along with the error messages that they produce.
+	this.createMultiplicities(graph.multiplicities, ['start', 'finally', 'end'], [
+		[1, 1],
+		[1, 1],
+		[0, 0]
+	], [
+		[0, 0],
+		[0, 0],
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['answer', 'hangup', 'dial',
+		'ext_dial', 'queue', 'voicemail', 'callback'
+	], [
+		[1, 1],
+		[0, 0],
+		[0, 1],
+		[0, 1],
+		[0, 1],
+		[1, 1],
+		[0, 1]
+	], [
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['set', 'math'], [
+		[1, 1],
+		[1, 1]
+	], [
+		[1, 'n'],
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['background', 'playback',
+		'menu', 'saydigits', 'saynumber', 'sayphonetic', 'tts', 'ispeechtts', 'getdigits'
+	], [
+		[1, 'n'],
+		[1, 1],
+		[1, 'n'],
+		[1, 1],
+		[1, 1],
+		[1, 1],
+		[1, 1],
+		[1, 1],
+		[1, 'n']
+	], [
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['record'], [
+		[1, 1]
+	], [
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['database', 'ispeechasr'], [
+		[1, 1],
+		[1, 1]
+	], [
+		[1, 'n'],
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['gotoc', 'gotoif',
+		'gotoiftime', 'gotoifmultitime', 'vswitch'
+	], [
+		[0, 0],
+		[1, 2],
+		[1, 2],
+		[1, 2],
+		[1, 'n']
+	], [
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['queuelog', 'goal'], [
+		[1, 1],
+		[1, 1]
+	], [
+		[1, 'n'],
+		[1, 'n']
+	]);
+	this.createMultiplicities(graph.multiplicities, ['noop', 'system', 'agi',
+		'subproject', 'custom_app'
+	], [
+		[1, 1],
+		[1, 1],
+		[0, 1],
+		[0, 1],
+		[0, 1]
+	], [
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n'],
+		[1, 'n']
+	]);
+
+	// Giuseppe Careri
+	// Processes a doubleclick on an optional cell and fires a <dblclick> event.
+	// The event is fired initially.
+	// If the graph is enabled and the event has not been consumed, then <edit> is called with the given cell.
+	// The event is ignored if no cell was specified.
+	graph.dblClick = function(evt, cell) {
+		var edges = ['background', 'gotoif', 'gotoiftime', 'menu', 'getdigits', 'vswitch'];
+		var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell',
+			cell);
+		this.fireEvent(mxe);
+
+		if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) {
+			// Override dblClick Vertices and Edges
+			if (this.getModel().isEdge(cell)) {
+				if (edges.indexOf(cell.source.value.tagName) >= 0)
+					this.startEditingAtCell(cell);
+			} else if (this.getModel().isVertex(cell)) {
+				console.log(self);
+				console.log(cell);
+				self.showDialog(new GeneralDialog(self, cell).container, 320, 280, true,
+					true);
+			}
+			mxe.consume();
+		}
+	}
+
+	// Configures automatic expand on mouseover
+	graph.panningHandler.autoExpand = true;
+
+	// Installs context menu
+	graph.panningHandler.factoryMethod = mxUtils.bind(this, function(menu, cell,
+		evt) {
+		this.menus.createPopupMenu(menu, cell, evt);
+	});
+
+	// Initializes the outline
+	editor.outline.init(this.outlineContainer);
+
+	// Hides context menu
+	mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
+		graph.panningHandler.hideMenu();
+	}));
+
+	// Adds gesture handling (pinch to zoom)
+	if (mxClient.IS_TOUCH) {
+		mxEvent.addListener(graph.container, 'gesturechange',
+			mxUtils.bind(this, function(evt) {
+				graph.view.getDrawPane().setAttribute('transform', 'scale(' + evt.scale +
+					')');
+				graph.view.getOverlayPane().style.visibility = 'hidden';
+			})
+		);
+
+		mxEvent.addListener(graph.container, 'gestureend',
+			mxUtils.bind(this, function(evt) {
+				graph.view.getDrawPane().removeAttribute('transform');
+				graph.zoomToCenter = true;
+				graph.zoom(evt.scale);
+				graph.view.getOverlayPane().style.visibility = 'visible';
+			})
+		);
+	}
+
+	// Create handler for key events
+	var keyHandler = this.createKeyHandler(editor);
+
+	// Getter for key handler
+	this.getKeyHandler = function() {
+		return keyHandler;
+	};
+
+	// Updates the editor UI after the window has been resized
+	mxEvent.addListener(window, 'resize', mxUtils.bind(this, function() {
+		this.refresh();
+		graph.sizeDidChange();
+		this.editor.outline.update(false);
+		this.editor.outline.outline.sizeDidChange();
+	}));
+
+	// Updates action and menu states
+	this.init();
+	this.open();
+};
+
+/**
+ * Specifies the size of the split bar.
+ */
+EditorUi.prototype.splitSize = (mxClient.IS_TOUCH) ? 16 : 8;
+
+/**
+ * Specifies the height of the menubar. Default is 34.
+ */
+EditorUi.prototype.menubarHeight = 33;
+
+/**
+ * Specifies the height of the toolbar. Default is 36.
+ */
+EditorUi.prototype.toolbarHeight = 36;
+
+/**
+ * Specifies the height of the footer. Default is 28.
+ */
+EditorUi.prototype.footerHeight = 28;
+
+/**
+ * Specifies the position of the horizontal split bar. Default is 212.
+ */
+EditorUi.prototype.hsplitPosition = 204;
+
+/**
+ * Specifies the position of the vertical split bar. Default is 190.
+ */
+EditorUi.prototype.vsplitPosition = 190;
+
+/**
+ * Installs the listeners to update the action states.
+ */
+EditorUi.prototype.init = function() {
+	// Updates action states
+	this.addUndoListener();
+	this.addSelectionListener();
+
+	// Overrides clipboard to update paste action state
+	var paste = this.actions.get('paste');
+
+	var updatePaste = function() {
+		paste.setEnabled(!mxClipboard.isEmpty());
+	};
+
+	var mxClipboardCut = mxClipboard.cut;
+	mxClipboard.cut = function() {
+		mxClipboardCut.apply(this, arguments);
+		updatePaste();
+	};
+
+	var mxClipboardCopy = mxClipboard.copy;
+	mxClipboard.copy = function() {
+		mxClipboardCopy.apply(this, arguments);
+		updatePaste();
+	};
+};
+
+/**
+ * Hook for allowing selection and context menu for certain events.
+ */
+EditorUi.prototype.isSelectionAllowed = function(evt) {
+	return false;
+};
+
+/**
+ * Opens the current diagram via the window.opener if one exists.
+ */
+EditorUi.prototype.open = function() {
+	// Cross-domain window access is not allowed in FF, so if we
+	// were opened from another domain then this will fail.
+	try {
+		if (window.opener != null && window.opener.openFile != null) {
+			window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml,
+				filename) {
+				try {
+					var doc = mxUtils.parseXml(xml);
+					this.editor.setGraphXml(doc.documentElement);
+					this.editor.modified = false;
+					this.editor.undoManager.clear();
+
+					if (filename != null) {
+						this.editor.filename = filename;
+					}
+				} catch (e) {
+					mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
+				}
+			}));
+		}
+	} catch (e) {
+		// ignore
+	}
+};
+
+/**
+ * Giuseppe Careri
+ * Opens the current diagram via string.
+ */
+EditorUi.prototype.openString = function(xml, filename, data) {
+	try {
+		var doc = mxUtils.parseXml(xml);
+		this.editor.setGraphXml(doc.documentElement);
+		this.editor.modified = false;
+		this.editor.undoManager.clear();
+
+		if (filename != null) {
+			this.editor.filename = filename;
+		}
+
+		if (data != null) {
+			this.editor.data = data;
+		}
+	} catch (e) {
+		mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
+	}
+};
+
+/**
+ * Saves As the current graph under the given project name.
+ */
+EditorUi.prototype.new = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+		try {
+			if (useLocalStorage) {
+				if (localStorage.getItem(name) != null &&
+					!mxUtils.confirm(mxResources.get('replace', [name]))) {
+					return;
+				}
+
+				localStorage.setItem(name, xml);
+				this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+			} else {
+				console.log(xml.length);
+				console.log(MAX_REQUEST_SIZE);
+				if (xml.length < MAX_REQUEST_SIZE) {
+					xml = encodeURIComponent(xml);
+					name = encodeURIComponent(name);
+
+					var xhr = new XMLHttpRequest();
+					xhr.open("POST", SAVE_URL, true);
+					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+					xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+					xhr.onload = function(e) {
+						if (xhr.readyState === 4) {
+							if (xhr.status === 201) {
+								editor.setStatus('Project ' + JSON.parse(xhr.response).name +
+									' is opening in new window!');
+								setTimeout(function() {
+									window.open('square/project/' + JSON.parse(xhr.response).id,
+										'_blank');
+								}, 1500);
+							} else {
+								editor.setStatus('Error creating project: ' +
+									JSON.parse(xhr.response).errors[0].message);
+							}
+						}
+					};
+					xhr.onerror = function(e) {
+						mxUtils.alert(xhr.statusText);
+					};
+					xhr.send('description=project_new&name=' + name);
+				} else {
+					mxUtils.alert(mxResources.get('drawingTooLarge'));
+					mxUtils.popup(xml);
+
+					return;
+				}
+			}
+
+			this.editor.filename = name;
+			this.editor.modified = false;
+		} catch (e) {
+			this.editor.setStatus('Error creating project');
+		}
+	} else {
+		this.editor.setStatus('Error creating project');
+	}
+};
+
+
+/**
+ * Saves the current graph under the given project name.
+ */
+EditorUi.prototype.save = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+		try {
+			if (useLocalStorage) {
+				if (localStorage.getItem(name) != null &&
+					!mxUtils.confirm(mxResources.get('replace', [name]))) {
+					return;
+				}
+
+				localStorage.setItem(name, xml);
+				this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+			} else {
+				console.log(xml.length);
+				console.log(MAX_REQUEST_SIZE);
+				if (xml.length < MAX_REQUEST_SIZE) {
+					xml = encodeURIComponent(xml);
+
+					var xhr = new XMLHttpRequest();
+					xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
+					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+					xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+					xhr.onload = function(e) {
+						if (xhr.readyState === 4) {
+							if (xhr.status === 200) {
+								editor.setStatus('Updated at ' + JSON.parse(xhr.response).updatedAt +
+									' (' + xhr.status + ' ' + xhr.statusText + ')');
+							} else {
+								editor.setStatus('Error saving project: ' +
+									JSON.parse(xhr.response).errors[0].message);
+							}
+						}
+					};
+					xhr.onerror = function(e) {
+						mxUtils.alert(xhr.statusText);
+					};
+					xhr.send('preproduction=' + xml);
+				} else {
+					mxUtils.alert(mxResources.get('drawingTooLarge'));
+					mxUtils.popup(xml);
+
+					return;
+				}
+			}
+
+			this.editor.filename = name;
+			this.editor.modified = false;
+		} catch (e) {
+			this.editor.setStatus('Error saving file');
+		}
+	} else {
+		this.editor.setStatus('Error saving file');
+	}
+};
+
+/**
+ * Saves As the current graph under the given project name.
+ */
+EditorUi.prototype.saveAs = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+		try {
+			if (useLocalStorage) {
+				if (localStorage.getItem(name) != null &&
+					!mxUtils.confirm(mxResources.get('replace', [name]))) {
+					return;
+				}
+
+				localStorage.setItem(name, xml);
+				this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+			} else {
+				console.log(xml.length);
+				console.log(MAX_REQUEST_SIZE);
+				if (xml.length < MAX_REQUEST_SIZE) {
+					xml = encodeURIComponent(xml);
+					name = encodeURIComponent(name);
+
+					var xhr = new XMLHttpRequest();
+					xhr.open("POST", SAVE_URL, true);
+					xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+					xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+					xhr.onload = function(e) {
+						if (xhr.readyState === 4) {
+							if (xhr.status === 201) {
+								editor.setStatus('Project ' + JSON.parse(xhr.response).name +
+									' is opening in new window!');
+								setTimeout(function() {
+									window.open('square/project/' + JSON.parse(xhr.response).id,
+										'_blank');
+								}, 1500);
+							} else {
+								editor.setStatus('Error cloning project: ' +
+									JSON.parse(xhr.response).errors[0].message);
+							}
+						}
+					};
+					xhr.onerror = function(e) {
+						mxUtils.alert(xhr.statusText);
+					};
+					xhr.send('description=project_cloned&name=' + name +
+						'&preproduction=' + xml + '&production=' + xml);
+				} else {
+					mxUtils.alert(mxResources.get('drawingTooLarge'));
+					mxUtils.popup(xml);
+
+					return;
+				}
+			}
+
+			this.editor.filename = name;
+			this.editor.modified = false;
+		} catch (e) {
+			this.editor.setStatus('Error saving file');
+		}
+	} else {
+		this.editor.setStatus('Error saving file');
+	}
+};
+
+/**
+ * Plush the current graph under the given project name.
+ */
+EditorUi.prototype.validate = function(a, b) {
+	var graph = this.editor.graph;
+	for (var a = a != null ? a : graph.model.getRoot(), b = b != null ? b : {}, c = true, d = graph.model.getChildCount(a), e = 0; e < d; e++) {
+		var f = graph.model.getChildAt(a, e),
+			g = b;
+		graph.isValidRoot(f) && (g = {});
+		g = this.validate(f, g);
+		g != null ? graph.setCellWarning(f, g.replace(/\n/g, "<br>")) : graph.setCellWarning(f, null);
+		c = c && g == null
+	}
+
+	d = "";
+
+	//--- Giuseppe Careri - Modify 12/12/2013 - [email-masked]
+	if (graph.model.isVertex(a)) {
+		switch (a.value.nodeName) {
+			case 'start':
+				d = this.validateBlock('start') ? '' : "Only one Start block is allowed" + "\n";
+				break;
+			case 'finally':
+				d = this.validateBlock('finally') ? '' : "Only one Finally block is allowed" + "\n";
+				break;
+			case 'playback':
+				d = (a.value.getAttribute('file_id') > 0) ? "" : mxResources.get('noAudioSelected') + "\n";
+				break;
+			case 'background':
+			case 'menu':
+				d = (a.value.getAttribute('file_id') > 0) ? "" : mxResources.get('noAudioSelected') + "\n";
+				d += (a.value.getAttribute('variable_id') > 0) ? "" : mxResources.get('noVariableSelected') + "\n";
+				break;
+			case 'getdigits':
+				d = (a.value.getAttribute('file_id') > 0) ? "" : mxResources.get('noAudioSelected') + "\n";
+				d += (a.value.getAttribute('variable_id') > 0) ? "" : mxResources.get('noVariableSelected') + "\n";
+				break;
+			case 'vswitch':
+				d = (a.value.getAttribute('variable') !== "") ? "" : mxResources.get('noVariableSelected') + "\n";
+				break;
+			case 'database':
+				d = (a.value.getAttribute('odbc_id') > 0) ? "" : mxResources.get('noDatabaseSelected') + "\n";
+				break;
+			case 'set':
+			case 'math':
+				d = (a.value.getAttribute('variable_id') > 0) ? "" : mxResources.get('noVariableSelected') + "\n";
+				break;
+			case 'gotoc':
+				d = (a.value.getAttribute('extension') !== "") ? "" : mxResources.get('noExtensionSelected') + "\n";
+				break;
+			case 'system':
+			case 'agi':
+				d = (a.value.getAttribute('command') !== "") ? "" : mxResources.get('noCommandSelected') + "\n";
+				break;
+			case 'gotoif':
+				d = (a.value.getAttribute('condition') !== "") ? "" : mxResources.get('noConditionSelected') + "\n";
+				break;
+			case 'saydigits':
+				d = (a.value.getAttribute('digits') !== "") ? "" : mxResources.get('noDigitsSelected') + "\n";
+				break;
+			case 'saynumber':
+				d = (a.value.getAttribute('number') !== "") ? "" : mxResources.get('noNumberSelected') + "\n";
+				break;
+			case 'sayphonetic':
+				d = (a.value.getAttribute('text') !== "") ? "" : mxResources.get('noTextSelected') + "\n";
+				break;
+			case 'record':
+				d = (a.value.getAttribute('filename') !== "") ? "" : mxResources.get('noFileSelected') + "\n";
+				break;
+			case 'goal':
+				d = (a.value.getAttribute('goalname') !== "") ? "" : mxResources.get('noGoalSelected') + "\n";
+				break;
+			case 'ext_dial':
+				d = (a.value.getAttribute('phone') !== "") ? "" : mxResources.get('noIdentifierSelected') + "\n";
+				break;
+			case 'dial':
+				d = (a.value.getAttribute('sip_id') > 0) ? "" : mxResources.get('noIdentifierSelected') + "\n";
+				break;
+			case 'queue':
+				d = (a.value.getAttribute('queue_id') !== "-1") ? "" : mxResources.get('noQueueSelected') + "\n";
+				break;
+			case 'callback':
+				d = (a.value.getAttribute('list_id') > 0) ? "" : mxResources.get('noListSelected') + "\n";
+				break;
+			case 'gotoiftime':
+				d = (a.value.getAttribute('interval_id') > 0) ? "" : mxResources.get('noIntervalSelected') + "\n";
+				break;
+			case 'gotoifmultitime':
+				d = (a.value.getAttribute('interval_id') != "") ? "" : mxResources.get('noIntervalSelected') + "\n";
+				break;
+			case 'voicemail':
+				d = (a.value.getAttribute('mailbox') !== "") ? "" : mxResources.get('noBoxNumberSelected') + "\n";
+				break;
+			case 'subproject':
+				d = (a.value.getAttribute('project_id') > 0) ? "" : mxResources.get('noProjectSelected') + "\n";
+				break;
+			case 'tts':
+				d = (a.value.getAttribute('text') !== "") ? "" : mxResources.get('noTextSelected') + "\n";
+				break;
+			case 'ispeechtts':
+				d = (a.value.getAttribute('text') !== "") ? "" : mxResources.get('noTextSelected') + "\n";
+				d += (a.value.getAttribute('key') !== "") ? "" : mxResources.get('noKeySelected') + "\n";
+				break;
+			case 'ispeechasr':
+				d = (a.value.getAttribute('key') !== "") ? "" : mxResources.get('noKeySelected') + "\n";
+				break;
+		}
+	}
+
+	if (graph.model.isEdge(a)) {
+		var z = graph.model.getCell(a.source.getId());
+		switch (z.value.nodeName) {
+			case 'background':
+				var patt = /^[0-9]{1,45}$|^[i]{1}$|^[t]{1}|^[#]{1}|^[*]{1}$/;
+				d = d + ((patt.test(String(a.getValue()))) ? "" : mxResources.get('numberOrInvalidOrTimeout') + "\n");
+				break;
+			case 'menu':
+				var patt = /^[0-9]{1,45}$|^[-]{1}$|^[i]{1}$|^[t]{1}|^[#]{1}|^[*]{1}$/;
+				var str = String(a.getValue());
+				var res = str.split(",");
+				res.forEach(function(entry) {
+					d = d + ((patt.test(entry)) ? "" : mxResources.get('numberOrAll') + "\n");
+				});
+				break;
+			case 'getdigits':
+				var patt = /^(x|i|-)$/;
+				var str = String(a.getValue());
+				d = d + ((patt.test(str)) ? "" : mxResources.get('numberOrInvalidOrDefault') + "\n");
+				break;
+			case 'gotoif':
+			case 'gotoiftime':
+			case 'gotoifmultitime':
+				var patt = /^(true){1}$|^(false){1}$/;
+				d = d + ((patt.test(String(a.getValue()))) ? "" : mxResources.get('trueOrFalse') + "\n");
+				break;
+
+		}
+	}
+	// -----------------------------------------------------------------
+
+
+	graph.isCellCollapsed(a) && !c && (d = d + ((mxResources.get(graph.containsValidationErrorsResource) || graph.containsValidationErrorsResource) + "\n"));
+	d = graph.model.isEdge(a) ?
+		d + (graph.getEdgeValidationError(a, graph.model.getTerminal(a, true), graph.model.getTerminal(a, false)) || "") : d + (graph.getCellValidationError(a) || "");
+	e = graph.validateCell(a, b);
+	e != null && (d = d + e);
+	graph.model.getParent(a) == null && graph.view.validate();
+	return d.length > 0 || !c ? d : null
+};
+EditorUi.prototype.validateBlock = function(blockName) {
+	var graph = this.editor.graph;
+	var parent = graph.getDefaultParent();
+	var count = 0;
+	// var model = graph.getModel();
+	//We can also use model.getChildren(parent)
+	parent.children.forEach(function(n) {
+		if (graph.model.isVertex(n) && n.value.nodeName == blockName) {
+			count++;
+		}
+	});
+	return count > 1 ? false : true;
+}
+EditorUi.prototype.publish = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		var response = this.validate();
+		if (response === null) {
+
+			var xml = mxUtils.getPrettyXml(this.editor.getGraphXml());
+			try {
+				if (useLocalStorage) {
+					if (localStorage.getItem(name) != null &&
+						!mxUtils.confirm(mxResources.get('replace', [name]))) {
+						return;
+					}
+
+					localStorage.setItem(name, xml);
+					this.editor.setStatus(mxResources.get('saved') + ' ' + new Date());
+				} else {
+					console.log(xml.length);
+					console.log(MAX_REQUEST_SIZE);
+					if (xml.length < MAX_REQUEST_SIZE) {
+						xml = encodeURIComponent(xml);
+
+						var xhr = new XMLHttpRequest();
+						xhr.open("PUT", SAVE_URL + this.editor.data.id, true);
+						xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+						xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+						xhr.onload = function(e) {
+							if (xhr.readyState === 4) {
+								if (xhr.status === 200) {
+									editor.setStatus('Published at ' + JSON.parse(xhr.response).updatedAt +
+										' (' + xhr.status + ' ' + xhr.statusText + ')');
+								} else {
+									editor.setStatus('Error saving project: ' +
+										JSON.parse(xhr.response).errors[0].message);
+								}
+							}
+						};
+						xhr.onerror = function(e) {
+							mxUtils.alert(xhr.statusText);
+						};
+						xhr.send('preproduction=' + xml + '&production=' + xml);
+					} else {
+						mxUtils.alert(mxResources.get('drawingTooLarge'));
+						mxUtils.popup(xml);
+
+						return;
+					}
+				}
+
+				this.editor.filename = name;
+				this.editor.modified = false;
+			} catch (e) {
+				this.editor.setStatus('Error publishing file');
+			}
+		} else {
+			editor.setStatus('Error publishing project: ');
+		}
+	} else {
+		this.editor.setStatus('Error publishing file');
+	}
+};
+
+
+/**
+ * Create variable
+ */
+EditorUi.prototype.variable = function(name) {
+	var editor = this.editor;
+
+	if (name != null) {
+		try {
+
+			var xhr = new XMLHttpRequest();
+			xhr.open("POST", VARIABLE_URL, true);
+			xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+			xhr.setRequestHeader('Authorization', 'Bearer ' + editor.data.token);
+			xhr.onload = function(e) {
+				if (xhr.readyState === 4) {
+					if (xhr.status === 201) {
+						editor.setStatus('Variable created at ' + JSON.parse(xhr.response).createdAt +
+							' (' + xhr.status + ' ' + xhr.statusText + ')');
+					} else {
+						editor.setStatus('Error creating variable: ' +
+							JSON.parse(xhr.response).errors[0].message);
+					}
+				}
+			};
+			xhr.onerror = function(e) {
+				mxUtils.alert(xhr.statusText);
+			};
+			xhr.send('name=' + name);
+		} catch (e) {
+			this.editor.setStatus('Error creating variable');
+		}
+	} else {
+		this.editor.setStatus('Error creating variable');
+	}
+};
+
+/**
+ * Returns the URL for a copy of this editor with no state.
+ */
+EditorUi.prototype.getUrl = function(pathname) {
+	var href = (pathname != null) ? pathname : window.location.pathname;
+	var parms = (href.indexOf('?') > 0) ? 1 : 0;
+
+	// Removes template URL parameter for new blank diagram
+	for (var key in urlParams) {
+		if (parms == 0) {
+			href += '?';
+		} else {
+			href += '&';
+		}
+
+		href += key + '=' + urlParams[key];
+		parms++;
+	}
+
+	return href;
+};
+
+/**
+ * Updates the states of the given undo/redo items.
+ */
+EditorUi.prototype.addUndoListener = function() {
+	var undo = this.actions.get('undo');
+	var redo = this.actions.get('redo');
+
+	var undoMgr = this.editor.undoManager;
+
+	var undoListener = function() {
+		undo.setEnabled(undoMgr.canUndo());
+		redo.setEnabled(undoMgr.canRedo());
+	};
+
+	undoMgr.addListener(mxEvent.ADD, undoListener);
+	undoMgr.addListener(mxEvent.UNDO, undoListener);
+	undoMgr.addListener(mxEvent.REDO, undoListener);
+	undoMgr.addListener(mxEvent.CLEAR, undoListener);
+
+	// Updates the button states once
+	undoListener();
+};
+
+/**
+ * Updates the states of the given toolbar items based on the selection.
+ */
+EditorUi.prototype.addSelectionListener = function() {
+	var selectionListener = mxUtils.bind(this, function() {
+		var graph = this.editor.graph;
+		var selected = !graph.isSelectionEmpty();
+		var vertexSelected = false;
+		var edgeSelected = false;
+
+		var cells = graph.getSelectionCells();
+
+		if (cells != null) {
+			for (var i = 0; i < cells.length; i++) {
+				var cell = cells[i];
+
+				if (graph.getModel().isEdge(cell)) {
+					edgeSelected = true;
+				}
+
+				if (graph.getModel().isVertex(cell)) {
+					vertexSelected = true;
+				}
+
+				if (edgeSelected && vertexSelected) {
+					break;
+				}
+			}
+		}
+
+		// Updates action states
+		var actions = ['cut', 'copy', 'delete', 'duplicate', 'bold', 'italic',
+			'style', 'underline', 'toFront', 'toBack', 'dashed', 'rounded',
+			'shadow',
+			'tilt', 'autosize'
+		];
+
+		for (var i = 0; i < actions.length; i++) {
+			this.actions.get(actions[i]).setEnabled(selected);
+		}
+
+		this.actions.get('curved').setEnabled(edgeSelected);
+		this.actions.get('rotation').setEnabled(vertexSelected);
+		this.actions.get('wordWrap').setEnabled(vertexSelected);
+		this.actions.get('group').setEnabled(graph.getSelectionCount() > 1);
+		this.actions.get('ungroup').setEnabled(graph.getSelectionCount() == 1 &&
+			graph.getModel().getChildCount(graph.getSelectionCell()) > 0);
+		var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
+		this.actions.get('removeFromGroup').setEnabled(oneVertexSelected &&
+			graph.getModel().isVertex(graph.getModel().getParent(graph.getSelectionCell()))
+		);
+
+		// Updates menu states
+		var menus = ['fontFamily', 'fontSize', 'alignment', 'position', 'text',
+			'format',
+			'arrange', 'linewidth', 'spacing'
+		];
+
+		for (var i = 0; i < menus.length; i++) {
+			this.menus.get(menus[i]).setEnabled(selected);
+		}
+
+		menus = ['line', 'lineend', 'linestart'];
+
+		for (var i = 0; i < menus.length; i++) {
+			this.menus.get(menus[i]).setEnabled(edgeSelected);
+		}
+
+		this.actions.get('setAsDefaultEdge').setEnabled(edgeSelected);
+
+		this.menus.get('align').setEnabled(graph.getSelectionCount() > 1);
+		this.menus.get('direction').setEnabled(vertexSelected || (edgeSelected &&
+			graph.isLoop(graph.view.getState(graph.getSelectionCell()))));
+		this.menus.get('navigation').setEnabled(graph.foldingEnabled && ((graph.view
+				.currentRoot != null) ||
+			(graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell()))
+		));
+		this.actions.get('home').setEnabled(graph.view.currentRoot != null);
+		this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
+		var groupEnabled = graph.getSelectionCount() == 1 && graph.isValidRoot(
+			graph.getSelectionCell());
+		this.actions.get('enterGroup').setEnabled(groupEnabled);
+		this.actions.get('expand').setEnabled(groupEnabled);
+		this.actions.get('collapse').setEnabled(groupEnabled);
+		this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
+		this.actions.get('openLink').setEnabled(graph.getSelectionCount() == 1 &&
+			graph.getLinkForCell(graph.getSelectionCell()) != null);
+	});
+
+	this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE,
+		selectionListener);
+	selectionListener();
+};
+
+/**
+ * Refreshes the viewport.
+ */
+EditorUi.prototype.refresh = function() {
+	var quirks = mxClient.IS_IE && (document.documentMode == null || document.documentMode ==
+		5);
+	var w = this.container.clientWidth;
+	var h = this.container.clientHeight;
+
+	if (this.container == document.body) {
+		w = document.body.clientWidth || document.documentElement.clientWidth;
+		h = (quirks) ? document.body.clientHeight || document.documentElement.clientHeight :
+			document.documentElement.clientHeight;
+	}
+
+	var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize -
+		20));
+	var effVsplitPosition = Math.max(0, Math.min(this.vsplitPosition, h - this.menubarHeight -
+		this.toolbarHeight - this.footerHeight - this.splitSize - 1));
+
+	this.menubarContainer.style.height = this.menubarHeight + 'px';
+	this.toolbarContainer.style.top = this.menubarHeight + 'px';
+	this.toolbarContainer.style.height = this.toolbarHeight + 'px';
+
+	var tmp = this.menubarHeight + this.toolbarHeight;
+
+	if (!mxClient.IS_QUIRKS) {
+		tmp += 1;
+	}
+
+	this.sidebarContainer.style.top = tmp + 'px';
+	this.sidebarContainer.style.width = effHsplitPosition + 'px';
+	this.outlineContainer.style.width = effHsplitPosition + 'px';
+	this.outlineContainer.style.height = effVsplitPosition + 'px';
+	this.outlineContainer.style.bottom = this.footerHeight + 'px';
+	this.diagramContainer.style.left = (effHsplitPosition + this.splitSize) +
+		'px';
+	this.diagramContainer.style.top = this.sidebarContainer.style.top;
+	this.footerContainer.style.height = this.footerHeight + 'px';
+	this.footerContainer.style.display = 'none';
+	this.hsplit.style.top = this.sidebarContainer.style.top;
+	this.hsplit.style.bottom = this.outlineContainer.style.bottom;
+	this.hsplit.style.left = effHsplitPosition + 'px';
+	this.vsplit.style.width = this.sidebarContainer.style.width;
+	this.vsplit.style.bottom = (effVsplitPosition + this.footerHeight) + 'px';
+
+	if (quirks) {
+		this.menubarContainer.style.width = w + 'px';
+		this.toolbarContainer.style.width = this.menubarContainer.style.width;
+		var sidebarHeight = (h - effVsplitPosition - this.splitSize - this.footerHeight -
+			this.menubarHeight - this.toolbarHeight);
+		this.sidebarContainer.style.height = sidebarHeight + 'px';
+		this.diagramContainer.style.width = (w - effHsplitPosition - this.splitSize) +
+			'px';
+		var diagramHeight = (h - this.footerHeight - this.menubarHeight - this.toolbarHeight);
+		this.diagramContainer.style.height = diagramHeight + 'px';
+		this.footerContainer.style.width = this.menubarContainer.style.width;
+		this.hsplit.style.height = diagramHeight + 'px';
+	} else {
+		this.sidebarContainer.style.bottom = (effVsplitPosition + this.splitSize +
+			this.footerHeight) + 'px';
+		this.diagramContainer.style.bottom = this.outlineContainer.style.bottom;
+	}
+};
+
+/**
+ * Creates the required containers.
+ */
+EditorUi.prototype.createDivs = function() {
+	this.menubarContainer = this.createDiv('geMenubarContainer');
+	this.toolbarContainer = this.createDiv('geToolbarContainer');
+	this.sidebarContainer = this.createDiv('geSidebarContainer');
+	this.outlineContainer = this.createDiv('geOutlineContainer');
+	this.diagramContainer = this.createDiv('geDiagramContainer');
+	this.footerContainer = this.createDiv('geFooterContainer');
+	this.hsplit = this.createDiv('geHsplit');
+	this.vsplit = this.createDiv('geVsplit');
+
+	// Sets static style for containers
+	this.menubarContainer.style.top = '0px';
+	this.menubarContainer.style.left = '0px';
+	this.menubarContainer.style.right = '0px';
+	this.toolbarContainer.style.left = '0px';
+	this.toolbarContainer.style.right = '0px';
+	this.sidebarContainer.style.left = '0px';
+	this.outlineContainer.style.left = '0px';
+	this.diagramContainer.style.right = '0px';
+	this.footerContainer.style.left = '0px';
+	this.footerContainer.style.right = '0px';
+	this.footerContainer.style.bottom = '0px';
+	this.vsplit.style.left = '0px';
+	this.vsplit.style.height = this.splitSize + 'px';
+	this.hsplit.style.width = this.splitSize + 'px';
+};
+
+/**
+ * Creates the required containers.
+ */
+EditorUi.prototype.createUi = function() {
+	// Creates menubar
+	this.menubar = this.menus.createMenubar(this.createDiv('geMenubar'));
+	this.menubarContainer.appendChild(this.menubar.container);
+
+	// Creates toolbar
+	this.toolbar = this.createToolbar(this.createDiv('geToolbar'));
+	this.toolbarContainer.appendChild(this.toolbar.container);
+
+	// Creates the sidebar
+	this.sidebar = this.createSidebar(this.sidebarContainer);
+
+	// Creates the footer
+	this.footerContainer.appendChild(this.createFooter());
+
+	// Adds status bar in menubar
+	this.statusContainer = this.createStatusContainer();
+
+	// Connects the status bar to the editor status
+	this.editor.addListener('statusChanged', mxUtils.bind(this, function() {
+		this.setStatusText(this.editor.getStatus());
+	}));
+
+	this.setStatusText(this.editor.getStatus());
+	this.menubar.container.appendChild(this.statusContainer);
+
+	// Inserts into DOM
+	this.container.appendChild(this.menubarContainer);
+	this.container.appendChild(this.toolbarContainer);
+	this.container.appendChild(this.sidebarContainer);
+	this.container.appendChild(this.outlineContainer);
+	this.container.appendChild(this.diagramContainer);
+	this.container.appendChild(this.footerContainer);
+	this.container.appendChild(this.hsplit);
+	this.container.appendChild(this.vsplit);
+
+	// HSplit
+	this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function(
+		value) {
+		this.hsplitPosition = value;
+		this.refresh();
+		this.editor.graph.sizeDidChange();
+		this.editor.outline.update(false);
+		this.editor.outline.outline.sizeDidChange();
+	}));
+
+	// VSplit
+	this.addSplitHandler(this.vsplit, false, this.footerHeight, mxUtils.bind(
+		this,
+		function(value) {
+			this.vsplitPosition = value;
+			this.refresh();
+			this.editor.outline.update(false);
+			this.editor.outline.outline.sizeDidChange();
+		}));
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createStatusContainer = function() {
+	var container = document.createElement('a');
+	container.className = 'geItem geStatus';
+
+	return container;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createStatusContainer = function() {
+	var container = document.createElement('a');
+	container.className = 'geItem geStatus';
+
+	return container;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.setStatusText = function(value) {
+	this.statusContainer.innerHTML = value;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createToolbar = function(container) {
+	return new Toolbar(this, container);
+};
+
+/**
+ * Creates a new sidebar for the given container.
+ */
+EditorUi.prototype.createSidebar = function(container) {
+	return new Sidebar(this, container);
+};
+
+/**
+ * Creates and returns a new footer.
+ */
+EditorUi.prototype.createFooter = function() {
+	return this.createDiv('geFooter');
+};
+
+/**
+ * Creates the actual toolbar for the toolbar container.
+ */
+EditorUi.prototype.createDiv = function(classname) {
+	var elt = document.createElement('div');
+	elt.className = classname;
+
+	return elt;
+};
+
+/**
+ * Creates the custom header.
+ */
+EditorUi.prototype.createHeader = function(classname) {
+	var elt = document.createElement(classname);
+	return elt;
+};
+
+/**
+ * Creates the custom header.
+ */
+EditorUi.prototype.createForm = function(classname) {
+	var elt = document.createElement('form');
+	elt.className = classname;
+
+	return elt;
+};
+
+/**
+ * Updates the states of the given undo/redo items.
+ */
+EditorUi.prototype.addSplitHandler = function(elt, horizontal, dx, onChange) {
+	var start = null;
+	var initial = null;
+
+	function getValue() {
+		return parseInt(((horizontal) ? elt.style.left : elt.style.bottom));
+	};
+
+	function moveHandler(evt) {
+		if (start != null) {
+			var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+			onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y -
+				pt.y)) - dx));
+			mxEvent.consume(evt);
+		}
+	};
+
+	function dropHandler(evt) {
+		moveHandler(evt);
+		start = null;
+		initial = null;
+	};
+
+	mxEvent.addGestureListeners(elt, function(evt) {
+		start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+		initial = getValue();
+		mxEvent.consume(evt);
+	});
+
+	mxEvent.addListener(document, 'mousemove', moveHandler);
+	mxEvent.addListener(document, 'touchmove', moveHandler);
+	mxEvent.addListener(document, 'mouseup', dropHandler);
+	mxEvent.addListener(document, 'touchend', dropHandler);
+};
+
+/**
+ * Displays a print dialog.
+ */
+EditorUi.prototype.showDialog = function(elt, w, h, modal, closable, onClose) {
+	this.hideDialog();
+	this.dialog = new Dialog(this, elt, w, (mxClient.IS_VML) ? h - 12 : h,
+		modal,
+		closable, onClose);
+};
+
+/**
+ * Displays a print dialog.
+ */
+EditorUi.prototype.hideDialog = function() {
+	if (this.dialog != null) {
+		this.dialog.close();
+		this.dialog = null;
+		this.editor.graph.container.focus();
+	}
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+EditorUi.prototype.saveFile = function(forceDialog) {
+	if (!forceDialog && this.editor.filename != null) {
+		this.save(this.editor.getOrCreateFilename());
+	} else {
+		this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
+	}
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+EditorUi.prototype.publishFile = function(forceDialog) {
+	if (!forceDialog && this.editor.filename != null) {
+		this.publish(this.editor.getOrCreateFilename());
+	} else {
+		//this.showDialog(new SaveDialog(this).container, 300, 100, true, true);
+	}
+};
+
+/**
+ * Executes the given layout.
+ */
+EditorUi.prototype.executeLayout = function(layout, animate, ignoreChildCount) {
+	var graph = this.editor.graph;
+	var cell = graph.getSelectionCell();
+
+	// Allow global overridding of animation
+	animate = this.animate != null ? this.animate : animate;
+
+	graph.getModel().beginUpdate();
+	try {
+		layout.execute(graph.getDefaultParent(), cell);
+	} catch (e) {
+		throw e;
+	} finally {
+		// Animates the changes in the graph model except
+		// for Camino, where animation is too slow
+		if (animate && navigator.userAgent.indexOf('Camino') < 0) {
+			// New API for animating graph layout results asynchronously
+			var morph = new mxMorphing(graph);
+			morph.addListener(mxEvent.DONE, mxUtils.bind(this, function() {
+				graph.getModel().endUpdate();
+			}));
+
+			morph.startAnimation();
+		} else {
+			graph.getModel().endUpdate();
+		}
+	}
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+EditorUi.prototype.createKeyHandler = function(editor) {
+	var graph = this.editor.graph;
+	var keyHandler = new mxKeyHandler(graph);
+
+	// Routes command-key to control-key on Mac
+	keyHandler.isControlDown = function(evt) {
+		return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
+	};
+
+	// Helper function to move cells with the cursor keys
+	function nudge(keyCode) {
+		if (!graph.isSelectionEmpty()) {
+			var dx = 0;
+			var dy = 0;
+
+			if (keyCode == 37) {
+				dx = -1;
+			} else if (keyCode == 38) {
+				dy = -1;
+			} else if (keyCode == 39) {
+				dx = 1;
+			} else if (keyCode == 40) {
+				dy = 1;
+			}
+
+			graph.moveCells(graph.getSelectionCells(), dx, dy);
+			graph.scrollCellToVisible(graph.getSelectionCell());
+		}
+	};
+
+	// Binds keystrokes to actions
+	var bindAction = mxUtils.bind(this, function(code, control, key, shift) {
+		var action = this.actions.get(key);
+
+		if (action != null) {
+			var f = function() {
+				if (action.enabled) {
+					action.funct();
+				}
+			};
+
+			if (control) {
+				if (shift) {
+					keyHandler.bindControlShiftKey(code, f);
+				} else {
+					keyHandler.bindControlKey(code, f);
+				}
+			} else {
+				if (shift) {
+					keyHandler.bindShiftKey(code, f);
+				} else {
+					keyHandler.bindKey(code, f);
+				}
+			}
+		}
+	});
+
+	var ui = this;
+	var keyHandleEscape = keyHandler.escape;
+	keyHandler.escape = function(evt) {
+		ui.hideDialog();
+		keyHandleEscape.apply(this, arguments);
+	};
+
+	// Ignores enter keystroke. Remove this line if you want the
+	// enter keystroke to stop editing.
+	keyHandler.enter = function() {};
+	keyHandler.bindKey(8, function() {
+		graph.foldCells(true);
+	}); // Backspace
+	keyHandler.bindKey(13, function() {
+		graph.foldCells(false);
+	}); // Enter
+	keyHandler.bindKey(33, function() {
+		graph.exitGroup();
+	}); // Page Up
+	keyHandler.bindKey(34, function() {
+		graph.enterGroup();
+	}); // Page Down
+	keyHandler.bindKey(36, function() {
+		graph.home();
+	}); // Home
+	keyHandler.bindKey(35, function() {
+		graph.refresh();
+	}); // End
+	keyHandler.bindKey(37, function() {
+		nudge(37);
+	}); // Left arrow
+	keyHandler.bindKey(38, function() {
+		nudge(38);
+	}); // Up arrow
+	keyHandler.bindKey(39, function() {
+		nudge(39);
+	}); // Right arrow
+	keyHandler.bindKey(40, function() {
+		nudge(40);
+	}); // Down arrow
+	keyHandler.bindKey(113, function() {
+		graph.startEditingAtCell();
+	});
+	bindAction(46, false, 'delete'); // Delete
+	bindAction(82, true, 'tilt'); // Ctrl+R
+	bindAction(83, true, 'save'); // Ctrl+S
+	bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
+	bindAction(107, false, 'zoomIn'); // Add
+	bindAction(109, false, 'zoomOut'); // Subtract
+	bindAction(65, true, 'selectAll'); // Ctrl+A
+	bindAction(86, true, 'selectVertices', true); // Ctrl+Shift+V
+	bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
+	bindAction(69, true, 'export'); // Ctrl+Shift+E
+	bindAction(66, true, 'toBack'); // Ctrl+B
+	bindAction(70, true, 'toFront'); // Ctrl+F
+	bindAction(68, true, 'duplicate'); // Ctrl+D
+	bindAction(90, true, 'undo'); // Ctrl+Z
+	bindAction(89, true, 'redo'); // Ctrl+Y
+	bindAction(88, true, 'cut'); // Ctrl+X
+	bindAction(67, true, 'copy'); // Ctrl+C
+	bindAction(81, true, 'connect'); // Ctrl+Q
+	bindAction(86, true, 'paste'); // Ctrl+V
+	bindAction(71, true, 'group'); // Ctrl+G
+	bindAction(71, true, 'grid', true); // Ctrl+Shift+G
+	bindAction(85, true, 'ungroup'); // Ctrl+U
+	bindAction(112, false, 'about'); // F1
+	bindAction(80, true, 'publish', true); // Ctrl+Shift+P
+
+	return keyHandler;
+};
+
+/**
+ * Giuseppe Careri
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createMultiplicities = function(graph, cells, source,
+	target) {
+	for (var i = 0; i < cells.length; i++) {
+
+		graph.push(new mxMultiplicity(true, cells[i], null, null, source[i][0],
+			source[i][1], null, mxResources.get(cells[i]) + mxResources.get('musthave') + source[i][0] + mxResources.get('outgoingedge'), null));
+		graph.push(new mxMultiplicity(false, cells[i], null, null, target[i][0],
+			target[i][1], null, mxResources.get(cells[i]) + mxResources.get('musthave') + target[i][0] + mxResources.get('incomingedge'), null));
+	};
+};
diff --git a/public/assets/plugins/square/js/Graph.js b/public/assets/plugins/square/js/Graph.js
index 476abae..46f34e4 100644
--- a/public/assets/plugins/square/js/Graph.js
+++ b/public/assets/plugins/square/js/Graph.js
@@ -1 +1,748 @@
\ No newline at end of file
+/**
+ * $Id: Graph.js,v 1.14 2013-02-16 10:19:54 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new graph instance. Note that the constructor does not take a
+ * container because the graph instance is needed for creating the UI, which
+ * in turn will create the container for the graph. Hence, the container is
+ * assigned later in EditorUi.
+ */
+Graph = function(container, model, renderHint, stylesheet)
+{
+	mxGraph.call(this, container, model, renderHint, stylesheet);
+	
+	this.setConnectable(true);
+	this.setDropEnabled(false);
+	this.setPanning(true);
+	this.setTooltips(!mxClient.IS_TOUCH);
+	this.setAllowLoops(false);
+	this.setMultigraph(false);
+	this.allowAutoPanning = true;
+	this.resetEdgesOnConnect = false;
+	this.constrainChildren = false;
+	
+	// Centers the port icon on the target port
+    this.connectionHandler.targetConnectImage = true;
+
+    // Does not allow dangling edges
+    this.setAllowDanglingEdges(false);
+
+	// Enables cloning of connection sources
+	this.connectionHandler.setCreateTarget(false);
+	
+	// Disables built-in connection starts
+	this.connectionHandler.isValidSource = function()
+	{
+		return mxConnectionHandler.prototype.isValidSource.apply(this, arguments) && urlParams['connect'] != '2';
+	};
+
+	// Sets the style to be used when an elbow edge is double clicked
+	this.alternateEdgeStyle = 'vertical';
+
+	if (stylesheet == null)
+	{
+		this.loadStylesheet();
+	}
+	
+	// Creates rubberband selection
+    var rubberband = new mxRubberband(this);
+    
+    this.getRubberband = function()
+    {
+    	return rubberband;
+    };
+    
+    // Shows hand cursor while panning
+	this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function()
+	{
+		this.container.style.cursor = 'pointer';
+	}));
+			
+	this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function()
+	{
+		this.container.style.cursor = 'default';
+	}));
+
+    // Adds support for HTML labels via style. Note: Currently, only the Java
+    // backend supports HTML labels but CSS support is limited to the following:
+    // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
+	this.isHtmlLabel = function(cell)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		return style['html'] == '1' || style['whiteSpace'] == 'wrap';
+	};
+	
+	// HTML entities are displayed as plain text in wrapped plain text labels
+	this.cellRenderer.getLabelValue = function(state)
+	{
+		var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);
+		
+		if (state.style['whiteSpace'] == 'wrap' && state.style['html'] != 1)
+		{
+			result = mxUtils.htmlEntities(result, false);
+		}
+		
+		return result;
+	};
+	
+	// Unlocks all cells
+	this.isCellLocked = function(cell)
+	{
+		return false;
+	};
+
+	// Tap and hold brings up context menu.
+	// Tolerance slightly below graph tolerance is better.
+	this.connectionHandler.tapAndHoldTolerance = 16;
+	
+	//  Tap and hold on background starts rubberband on cell starts connecting
+	var connectionHandlerTapAndHold = this.connectionHandler.tapAndHold;
+	this.connectionHandler.tapAndHold = function(me, state)
+	{
+		if (state == null)
+		{
+			if (!this.graph.panningHandler.active)
+			{
+				rubberband.start(me.getGraphX(), me.getGraphY());
+				this.graph.panningHandler.panningTrigger = false;
+			}
+		}
+		else if (tapAndHoldStartsConnection)
+		{
+			connectionHandlerTapAndHold.apply(this, arguments);	
+		}
+		else if (this.graph.isCellSelected(state.cell) && this.graph.getSelectionCount() > 1)
+		{
+			this.graph.removeSelectionCell(state.cell);
+		}
+	};
+
+	if (touchStyle)
+	{
+		this.initTouch();
+	}
+};
+
+// Graph inherits from mxGraph
+mxUtils.extend(Graph, mxGraph);
+
+/**
+ * Allows to all values in fit.
+ */
+Graph.prototype.minFitScale = null;
+
+/**
+ * Allows to all values in fit.
+ */
+Graph.prototype.maxFitScale = null;
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+Graph.prototype.loadStylesheet = function()
+{
+    var node = mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement();
+	var dec = new mxCodec(node.ownerDocument);
+	dec.decode(node, this.getStylesheet());
+};
+
+/**
+ * Inverts the elbow edge style without removing existing styles.
+ */
+Graph.prototype.flipEdge = function(edge)
+{
+	if (edge != null)
+	{
+		var state = this.view.getState(edge);
+		var style = (state != null) ? state.style : this.getCellStyle(edge);
+		
+		if (style != null)
+		{
+			var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+				mxConstants.ELBOW_HORIZONTAL);
+			var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+				mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+			this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+		}
+	}
+};
+
+/**
+ * Sets the default edge for future connections.
+ */
+Graph.prototype.setDefaultEdge = function(cell)
+{
+	if (cell != null && this.getModel().isEdge(cell))
+	{
+		// Take a snapshot of the cell at the moment of calling
+		var proto = this.getModel().cloneCells([cell])[0];
+		
+		// Delete existing points
+		if (proto.geometry != null)
+		{
+			proto.geometry.points = null;
+		}
+		
+		// Delete entry-/exitXY styles
+		var style = proto.getStyle();
+		style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_X, null);
+		style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_Y, null);
+		style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_X, null);
+		style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_Y, null);
+		proto.setStyle(style);
+		
+		// Uses edge template for connect preview
+		this.connectionHandler.createEdgeState = function(me)
+		{
+    		return this.graph.view.createState(proto);
+	    };
+
+	    // Creates new connections from edge template
+	    this.connectionHandler.factoryMethod = function()
+	    {
+    		return this.graph.cloneCells([proto])[0];
+	    };
+	}
+};
+
+/**
+ * Disables folding for non-swimlanes.
+ */
+Graph.prototype.isCellFoldable = function(cell)
+{
+	return this.foldingEnabled && this.isSwimlane(cell);
+};
+
+/**
+ * Disables drill-down for non-swimlanes.
+ */
+Graph.prototype.isValidRoot = function(cell)
+{
+	return this.isSwimlane(cell);
+};
+
+/**
+ * Overrides createGroupCell to set the group style for new groups to 'group'.
+ */
+Graph.prototype.createGroupCell = function()
+{
+	var group = mxGraph.prototype.createGroupCell.apply(this, arguments);
+	group.setStyle('group');
+	
+	return group;
+};
+
+/**
+ * Giuseppe Careri
+ * Overrides tooltips to show position and size
+ */
+Graph.prototype.getTooltipForCell = function(cell)
+{
+	var tip = '';
+	
+	if (this.getModel().isVertex(cell))
+	{
+		// var geo = this.getCellGeometry(cell);
+		
+		// var f2 = function(x)
+		// {
+		// 	return Math.round(parseFloat(x) * 100) / 100;
+		// };
+		
+		// if (geo != null)
+		// {
+		// 	if (tip == null)
+		// 	{
+		// 		tip = '';
+		// 	}
+		// 	else if (tip.length > 0)
+		// 	{
+		// 		tip += '\n';
+		// 	}
+			
+		// 	tip += 'X: ' + f2(geo.x) + '\nY: ' + f2(geo.y) + '\nW: ' + f2(geo.width) + '\nH: ' + f2(geo.height);
+		// }
+		
+		tip += mxResources.get('description_' + cell.value.nodeName);
+	}
+	else if (this.getModel().isEdge(cell))
+	{
+		tip = mxGraph.prototype.getTooltipForCell.apply(this, arguments);
+	}
+	
+	return tip;
+};
+
+/**
+ * Giuseppe Careri
+ * Returns the label for the given cell.
+ */
+Graph.prototype.convertValueToString = function(cell)
+{
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		return cell.value.getAttribute('label');
+	}
+	
+	return mxGraph.prototype.convertValueToString.apply(this, arguments);
+};
+
+/**
+ * Giuseppe Careri
+ * Handles label changes for XML user objects.
+ */
+Graph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		var tmp = cell.value.cloneNode(true);
+		tmp.setAttribute('label', value);
+		value = tmp;
+	}
+	
+	mxGraph.prototype.cellLabelChanged.apply(this, arguments);
+};
+
+/**
+ * Sets the link for the given cell.
+ */
+Graph.prototype.setLinkForCell = function(cell, link)
+{
+	var value = null;
+	
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		value = cell.value.cloneNode(true);
+	}
+	else
+	{
+		var doc = mxUtils.createXmlDocument();
+		
+		value = doc.createElement('UserObject');
+		value.setAttribute('label', cell.value);
+	}
+	
+	if (link != null && link.length > 0)
+	{
+		value.setAttribute('link', link);
+	}
+	else
+	{
+		value.removeAttribute('link');
+	}
+	
+	this.model.setValue(cell, value);
+};
+
+/**
+ * Returns the link for the given cell.
+ */
+Graph.prototype.getLinkForCell = function(cell)
+{
+	if (cell.value != null && typeof(cell.value) == 'object')
+	{
+		return cell.value.getAttribute('link');
+	}
+	
+	return null;
+};
+
+/**
+ * Customized graph for touch devices.
+ */
+Graph.prototype.initTouch = function()
+{
+	// Disables new connections via "hotspot"
+	this.connectionHandler.marker.isEnabled = function()
+	{
+		return this.graph.connectionHandler.first != null;
+	};
+
+	// Hides menu when editing starts
+	this.addListener(mxEvent.START_EDITING, function(sender, evt)
+	{
+		this.panningHandler.hideMenu();
+	});
+
+	// Context menu for touchstyle
+	var showMenu = false;
+	var menuCell = null;
+
+	// Checks if native hit detection did not return anything and does custom
+	// hit detection for edges to take into account the tolerance
+	this.updateMouseEvent = function(me)
+	{
+		mxGraph.prototype.updateMouseEvent.apply(this, arguments);
+
+		if (me.getState() == null)
+		{
+			var cell = this.getCellAt(me.graphX, me.graphY);
+			
+			if (this.getModel().isEdge(cell))
+			{
+				me.state = this.view.getState(cell);
+				
+				if (me.state != null && me.state.shape != null)
+				{
+					this.container.style.cursor = me.state.shape.node.style.cursor;
+				}
+			}
+		}
+		
+		if (me.getState() == null)
+		{
+			this.container.style.cursor = 'default';
+		}
+	};
+	
+	// Handles popup menu on touch devices (tap selected cell)
+	this.fireMouseEvent = function(evtName, me, sender)
+	{
+		if (evtName == mxEvent.MOUSE_DOWN)
+		{
+			if (!this.panningHandler.isMenuShowing())
+			{
+				menuCell = me.getCell();
+				showMenu = (menuCell != null) ? this.isCellSelected(menuCell) : this.isSelectionEmpty();
+			}
+			else
+			{
+				showMenu = false;
+				menuCell = null;
+			}
+		}
+		else if (evtName == mxEvent.MOUSE_UP)
+		{
+			if (showMenu && !this.isEditing())
+			{
+				if (!this.panningHandler.isMenuShowing())
+				{
+					var x = mxEvent.getClientX(me.getEvent());
+					var y = mxEvent.getClientY(me.getEvent());
+					
+					this.panningHandler.popup(x + 16, y, menuCell, me.getEvent());
+				}
+				
+				showMenu = false;
+				menuCell = null;
+				me.consume();
+				
+				return;
+			}
+			
+			showMenu = false;
+			menuCell = null;
+		}
+
+		mxGraph.prototype.fireMouseEvent.apply(this, arguments);
+
+		if (evtName == mxEvent.MOUSE_MOVE && me.isConsumed())
+		{
+			showMenu = false;
+			menuCell = null;
+		}
+	};
+};
+
+/**
+ * Implements touch devices.
+ */
+(function()
+{
+	// Enables rotation handle
+	mxVertexHandler.prototype.rotationEnabled = true;
+	
+	// Matches label positions of mxGraph 1.x
+	mxText.prototype.baseSpacingTop = 5;
+	mxText.prototype.baseSpacingBottom = 1;
+
+	// Touch-specific static overrides
+	if (touchStyle)
+	{
+		// Sets constants for touch style
+		mxConstants.HANDLE_SIZE = 16;
+		mxConstants.LABEL_HANDLE_SIZE = 7;
+		
+		// Larger tolerance and grid for real touch devices
+		if (mxClient.IS_TOUCH)
+		{
+			mxVertexHandler.prototype.tolerance = 4;
+			mxEdgeHandler.prototype.tolerance = 6;
+			Graph.prototype.tolerance = 14;
+			Graph.prototype.gridSize = 20;
+			
+			// One finger pans (no rubberband selection) must start regardless of mouse button
+			mxPanningHandler.prototype.selectOnPopup = false;
+			mxPanningHandler.prototype.useLeftButtonForPanning = true;
+			mxPanningHandler.prototype.isPanningTrigger = function(me)
+			{
+				var evt = me.getEvent();
+			 	
+			 	return (this.useLeftButtonForPanning && (this.ignoreCell || me.getState() == null)/* &&
+			 			mxEvent.isLeftMouseButton(evt)*/) || (mxEvent.isControlDown(evt) &&
+			 			mxEvent.isShiftDown(evt)) || (this.usePopupTrigger &&
+			 		   	mxEvent.isPopupTrigger(evt));
+			};
+		}
+		
+		// Don't clear selection if multiple cells selected
+		var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
+		mxGraphHandler.prototype.mouseDown = function(sender, me)
+		{
+			graphHandlerMouseDown.apply(this, arguments);
+
+			if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1)
+			{
+				this.delayedSelection = false;
+			}
+		};
+
+		// Changes order of panninghandler
+		Graph.prototype.createHandlers = function(container)
+		{
+			this.tooltipHandler = new mxTooltipHandler(this);
+			this.tooltipHandler.setEnabled(false);
+			// Selection cells first
+			this.selectionCellsHandler = new mxSelectionCellsHandler(this);
+			this.panningHandler = new mxPanningHandler(this);
+			this.panningHandler.panningEnabled = false;
+			this.connectionHandler = new mxConnectionHandler(this);
+			this.connectionHandler.setEnabled(false);
+			this.graphHandler = new mxGraphHandler(this);
+		};
+
+		// On connect the target is selected and we clone the cell of the preview edge for insert
+		mxConnectionHandler.prototype.selectCells = function(edge, target)
+		{
+			if (touchStyle && target != null)
+			{
+				this.graph.setSelectionCell(target);
+			}
+			else
+			{
+				this.graph.setSelectionCell(edge);
+			}
+		};
+
+		// Overrides double click handling to use the tolerance
+		// FIXME: Double click on edges in iPad needs focus on textarea
+		var graphDblClick = mxGraph.prototype.dblClick;
+		Graph.prototype.dblClick = function(evt, cell)
+		{
+			if (cell == null)
+			{
+				var pt = mxUtils.convertPoint(this.container,
+					mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+				cell = this.getCellAt(pt.x, pt.y);
+			}
+
+			graphDblClick.call(this, evt, cell);
+		};
+
+		// Rounded edge and vertex handles
+		var touchHandle = new mxImage(IMAGE_PATH + '/touch-handle.png', 16, 16);
+		mxVertexHandler.prototype.handleImage = touchHandle;
+		mxEdgeHandler.prototype.handleImage = touchHandle;
+		mxOutline.prototype.sizerImage = touchHandle;
+		
+		// Pre-fetches touch handle
+		new Image().src = touchHandle.src;
+
+		// Adds connect icon to selected vertices
+		var connectorSrc = IMAGE_PATH + '/touch-connector.png';
+		
+		var vertexHandlerInit = mxVertexHandler.prototype.init;
+		mxVertexHandler.prototype.init = function()
+		{
+			vertexHandlerInit.apply(this, arguments);
+
+			// Only show connector image on one cell and do not show on containers
+			if (showConnectorImg && this.graph.connectionHandler.isEnabled() &&
+				this.graph.isCellConnectable(this.state.cell) &&
+				!this.graph.isValidRoot(this.state.cell) &&
+				this.graph.getSelectionCount() == 1)
+			{
+				this.connectorImg = mxUtils.createImage(connectorSrc);
+				this.connectorImg.style.cursor = 'pointer';
+				this.connectorImg.style.width = '29px';
+				this.connectorImg.style.height = '29px';
+				this.connectorImg.style.position = 'absolute';
+				
+				if (!mxClient.IS_TOUCH)
+				{
+					this.connectorImg.setAttribute('title', mxResources.get('connect'));
+					mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
+				}
+
+				// Adds 2px tolerance
+				this.connectorImg.style.padding = '2px';
+				
+				// Starts connecting on touch/mouse down
+				mxEvent.addGestureListeners(this.connectorImg,
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.panningHandler.hideMenu();
+						var pt = mxUtils.convertPoint(this.graph.container,
+								mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+						this.graph.connectionHandler.start(this.state, pt.x, pt.y);
+						this.graph.isMouseDown = true;
+						mxEvent.consume(evt);
+					})
+				);
+
+				this.graph.container.appendChild(this.connectorImg);
+			}
+
+			this.redrawTools();
+		};
+		
+		var vertexHandlerRedraw = mxVertexHandler.prototype.redraw;
+		mxVertexHandler.prototype.redraw = function()
+		{
+			vertexHandlerRedraw.apply(this);
+			this.redrawTools();
+		};
+		
+		mxVertexHandler.prototype.redrawTools = function()
+		{
+			if (this.state != null && this.connectorImg != null)
+			{
+				// Top right for single-sizer
+				if (mxVertexHandler.prototype.singleSizer)
+				{
+					this.connectorImg.style.left = (this.state.x + this.state.width - this.connectorImg.offsetWidth / 2) + 'px';
+					this.connectorImg.style.top = (this.state.y - this.connectorImg.offsetHeight / 2) + 'px';
+				}
+				else
+				{
+					this.connectorImg.style.left = (this.state.x + this.state.width + mxConstants.HANDLE_SIZE / 2 + 4/* - 2 padding*/) + 'px';
+					this.connectorImg.style.top = (this.state.y + (this.state.height - this.connectorImg.offsetHeight) / 2) + 'px';
+				}
+			}
+		};
+		
+		var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
+		mxVertexHandler.prototype.destroy = function(sender, me)
+		{
+			vertexHandlerDestroy.apply(this, arguments);
+
+			if (this.connectorImg != null)
+			{
+				this.connectorImg.parentNode.removeChild(this.connectorImg);
+				this.connectorImg = null;
+			}
+		};
+		
+		// Pre-fetches touch connector
+		new Image().src = connectorSrc;
+	}
+	else
+	{
+		var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15);
+		mxConnectionHandler.prototype.connectImage = img;
+
+		// Pre-fetches img
+		new Image().src = img.src;
+		
+		if (urlParams['connect'] == '2') // not touchStyle
+		{
+			var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15);
+					
+			var vertexHandlerInit = mxVertexHandler.prototype.init;
+			mxVertexHandler.prototype.init = function()
+			{
+				vertexHandlerInit.apply(this, arguments);
+	
+				// Only show connector image on one cell and do not show on containers
+				if (showConnectorImg && this.graph.connectionHandler.isEnabled() &&
+					this.graph.isCellConnectable(this.state.cell) &&
+					!this.graph.isValidRoot(this.state.cell) &&
+					this.graph.getSelectionCount() == 1)
+				{
+					// Workaround for event redirection via image tag in quirks and IE8
+					if (mxClient.IS_IE && !mxClient.IS_SVG)
+					{
+						this.connectorImg = document.createElement('div');
+						this.connectorImg.style.backgroundImage = 'url(' + img.src + ')';
+						this.connectorImg.style.backgroundPosition = 'center';
+						this.connectorImg.style.backgroundRepeat = 'no-repeat';
+						this.connectorImg.style.width = (img.width + 4) + 'px';
+						this.connectorImg.style.height = (img.height + 4) + 'px';
+						this.connectorImg.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+					}
+					else
+					{
+						this.connectorImg = mxUtils.createImage(img.src);
+						this.connectorImg.style.width = img.width + 'px';
+						this.connectorImg.style.height = img.height + 'px';
+					}
+					
+					this.connectorImg.style.cursor = 'pointer';
+					this.connectorImg.style.position = 'absolute';
+					this.connectorImg.setAttribute('title', mxResources.get('connect'));
+					mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
+					
+					// Adds 2px tolerance
+					this.connectorImg.style.padding = '2px';
+					
+					// Starts connecting on touch/mouse down
+					mxEvent.addListener(this.connectorImg, 'mousedown',
+						mxUtils.bind(this, function(evt)
+						{
+							this.graph.panningHandler.hideMenu();
+							var pt = mxUtils.convertPoint(this.graph.container,
+									mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+							this.graph.connectionHandler.start(this.state, pt.x, pt.y);
+							this.graph.isMouseDown = true;
+							mxEvent.consume(evt);
+						})
+					);
+	
+					this.graph.container.appendChild(this.connectorImg);
+				}
+	
+				this.redrawTools();
+			};
+			
+			var vertexHandlerRedraw = mxVertexHandler.prototype.redraw;
+			mxVertexHandler.prototype.redraw = function()
+			{
+				vertexHandlerRedraw.apply(this);
+				this.redrawTools();
+			};
+			
+			mxVertexHandler.prototype.redrawTools = function()
+			{
+				if (this.state != null && this.connectorImg != null)
+				{
+					// Top right for single-sizer
+					if (mxVertexHandler.prototype.singleSizer)
+					{
+						this.connectorImg.style.left = (this.state.x + this.state.width - this.connectorImg.offsetWidth / 2) + 'px';
+						this.connectorImg.style.top = (this.state.y - this.connectorImg.offsetHeight / 2) + 'px';
+					}
+					else
+					{
+						this.connectorImg.style.left = (this.state.x + this.state.width + mxConstants.HANDLE_SIZE / 2 + 2/* - 2 padding*/) + 'px';
+						this.connectorImg.style.top = (this.state.y + (this.state.height - this.connectorImg.offsetHeight) / 2) + 'px';
+					}
+				}
+			};
+			
+			var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
+			mxVertexHandler.prototype.destroy = function(sender, me)
+			{
+				vertexHandlerDestroy.apply(this, arguments);
+	
+				if (this.connectorImg != null)
+				{
+					this.connectorImg.parentNode.removeChild(this.connectorImg);
+					this.connectorImg = null;
+				}
+			};
+		}
+	}
+})();
diff --git a/public/assets/plugins/square/js/Menus.js b/public/assets/plugins/square/js/Menus.js
index ca1246e..8ef4252 100644
--- a/public/assets/plugins/square/js/Menus.js
+++ b/public/assets/plugins/square/js/Menus.js
@@ -1 +1,716 @@
\ No newline at end of file
+/**
+ * $Id: Menus.js,v 1.13 2013-02-20 16:21:29 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new graph editor
+ */
+Menus = function(editorUi) {
+	this.editorUi = editorUi;
+	this.menus = new Object();
+	this.init();
+
+	// Pre-fetches checkmark image
+	new Image().src = IMAGE_PATH + '/checkmark.gif';
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.init = function() {
+	var graph = this.editorUi.editor.graph;
+
+	this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var fonts = ['Helvetica', 'Verdana', 'Times New Roman', 'Garamond',
+			'Comic Sans MS',
+			'Courier New', 'Georgia', 'Lucida Console', 'Tahoma'
+		];
+
+		for (var i = 0; i < fonts.length; i++) {
+			var tr = this.styleChange(menu, fonts[i], [mxConstants.STYLE_FONTFAMILY], [
+				fonts[i]
+			], null, parent);
+			tr.firstChild.nextSibling.style.fontFamily = fonts[i];
+		}
+
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('custom'), '', mxConstants.DEFAULT_FONTFAMILY,
+			mxConstants.STYLE_FONTFAMILY, parent);
+	})));
+	this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];
+
+		for (var i = 0; i < sizes.length; i++) {
+			this.styleChange(menu, sizes[i], [mxConstants.STYLE_FONTSIZE], [sizes[i]],
+				null, parent);
+		}
+
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('custom'), '(pt)', '12',
+			mxConstants.STYLE_FONTSIZE, parent);
+	})));
+	this.put('linewidth', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var sizes = [1, 2, 3, 4, 8, 12, 16, 24];
+
+		for (var i = 0; i < sizes.length; i++) {
+			this.styleChange(menu, sizes[i] + 'px', [mxConstants.STYLE_STROKEWIDTH], [
+				sizes[i]
+			], null, parent);
+		}
+
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('custom'), '(px)', '1',
+			mxConstants.STYLE_STROKEWIDTH, parent);
+	})));
+	this.put('line', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('straight'), [mxConstants.STYLE_EDGE], [
+			null
+		], null, parent);
+		this.styleChange(menu, mxResources.get('entityRelation'), [mxConstants.STYLE_EDGE], [
+			'entityRelationEdgeStyle'
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('horizontal'), [mxConstants.STYLE_EDGE,
+			mxConstants.STYLE_ELBOW
+		], ['elbowEdgeStyle', 'horizontal'], null, parent);
+		this.styleChange(menu, mxResources.get('vertical'), [mxConstants.STYLE_EDGE,
+			mxConstants.STYLE_ELBOW
+		], ['elbowEdgeStyle', 'vertical'], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('manual'), [mxConstants.STYLE_EDGE], [
+			'segmentEdgeStyle'
+		], null, parent);
+		this.styleChange(menu, mxResources.get('automatic'), [mxConstants.STYLE_EDGE], [
+			'orthogonalEdgeStyle'
+		], null, parent);
+	})));
+	this.put('lineend', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('classic'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_CLASSIC
+		], null, parent);
+		this.styleChange(menu, mxResources.get('openArrow'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_OPEN
+		], null, parent);
+		this.styleChange(menu, mxResources.get('block'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_BLOCK
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('oval'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_OVAL
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamond'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_DIAMOND
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamondThin'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.ARROW_DIAMOND_THIN
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('none'), [mxConstants.STYLE_ENDARROW], [
+			mxConstants.NONE
+		], null, parent);
+		menu.addSeparator(parent);
+		menu.addItem(mxResources.get('transparent'), null, function() {
+			graph.toggleCellStyles('endFill', true);
+		}, parent, null, true);
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('size'), '(px)', mxConstants.DEFAULT_MARKERSIZE,
+			mxConstants.STYLE_ENDSIZE, parent);
+	})));
+	this.put('linestart', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('classic'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_CLASSIC
+		], null, parent);
+		this.styleChange(menu, mxResources.get('openArrow'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_OPEN
+		], null, parent);
+		this.styleChange(menu, mxResources.get('block'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_BLOCK
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('oval'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_OVAL
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamond'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_DIAMOND
+		], null, parent);
+		this.styleChange(menu, mxResources.get('diamondThin'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.ARROW_DIAMOND_THIN
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('none'), [mxConstants.STYLE_STARTARROW], [
+			mxConstants.NONE
+		], null, parent);
+		menu.addSeparator(parent);
+		menu.addItem(mxResources.get('transparent'), null, function() {
+			graph.toggleCellStyles('startFill', true);
+		}, parent, null, true);
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('size'), '(px)', mxConstants.DEFAULT_MARKERSIZE,
+			mxConstants.STYLE_STARTSIZE, parent);
+	})));
+	this.put('spacing', new Menu(mxUtils.bind(this, function(menu, parent) {
+		// Uses shadow action and line menu to analyze selection
+		var vertexSelected = this.editorUi.actions.get('shadow').enabled;
+		var edgeSelected = this.get('line').enabled;
+
+		if (vertexSelected || menu.showDisabled) {
+			this.promptChange(menu, mxResources.get('top'), '(px)', '0',
+				mxConstants.STYLE_SPACING_TOP, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('right'), '(px)', '0',
+				mxConstants.STYLE_SPACING_RIGHT, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('bottom'), '(px)', '0',
+				mxConstants.STYLE_SPACING_BOTTOM, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('left'), '(px)', '0',
+				mxConstants.STYLE_SPACING_LEFT, parent, vertexSelected);
+			menu.addSeparator(parent);
+			this.promptChange(menu, mxResources.get('global'), '(px)', '0',
+				mxConstants.STYLE_SPACING, parent, vertexSelected);
+			this.promptChange(menu, mxResources.get('perimeter'), '(px)', '0',
+				mxConstants.STYLE_PERIMETER_SPACING, parent, vertexSelected);
+		}
+
+		if (edgeSelected || menu.showDisabled) {
+			menu.addSeparator(parent);
+			this.promptChange(menu, mxResources.get('sourceSpacing'), '(px)', '0',
+				mxConstants.STYLE_SOURCE_PERIMETER_SPACING, parent, edgeSelected);
+			this.promptChange(menu, mxResources.get('targetSpacing'), '(px)', '0',
+				mxConstants.STYLE_TARGET_PERIMETER_SPACING, parent, edgeSelected);
+		}
+	})));
+	this.put('format', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItem(menu, 'style', parent);
+	})));
+	this.put('text', new Menu(mxUtils.bind(this, function(menu, parent) {
+		var enabled = this.get('text').enabled;
+		menu.addSeparator(parent);
+		this.addSubmenu('fontFamily', menu, parent);
+		this.addSubmenu('fontSize', menu, parent);
+		this.addMenuItems(menu, ['-', 'bold', 'italic', 'underline', '-'],
+			parent);
+		this.addSubmenu('alignment', menu, parent);
+		this.addMenuItem(menu, 'wordWrap', parent);
+		menu.addSeparator(parent);
+		this.promptChange(menu, mxResources.get('textOpacity'), '(%)', '100',
+			mxConstants.STYLE_TEXT_OPACITY, parent, enabled);
+		menu.addItem(mxResources.get('hide'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_NOLABEL, false);
+		}, parent, null, enabled);
+	})));
+	this.put('alignment', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('leftAlign'), [mxConstants.STYLE_ALIGN], [
+			mxConstants.ALIGN_LEFT
+		], null, parent);
+		this.styleChange(menu, mxResources.get('center'), [mxConstants.STYLE_ALIGN], [
+			mxConstants.ALIGN_CENTER
+		], null, parent);
+		this.styleChange(menu, mxResources.get('rightAlign'), [mxConstants.STYLE_ALIGN], [
+			mxConstants.ALIGN_RIGHT
+		], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('topAlign'), [mxConstants.STYLE_VERTICAL_ALIGN], [
+			mxConstants.ALIGN_TOP
+		], null, parent);
+		this.styleChange(menu, mxResources.get('middle'), [mxConstants.STYLE_VERTICAL_ALIGN], [
+			mxConstants.ALIGN_MIDDLE
+		], null, parent);
+		this.styleChange(menu, mxResources.get('bottomAlign'), [mxConstants.STYLE_VERTICAL_ALIGN], [
+			mxConstants.ALIGN_BOTTOM
+		], null, parent);
+		menu.addSeparator(parent);
+		this.addSubmenu('position', menu, parent);
+		this.addSubmenu('spacing', menu, parent);
+		menu.addSeparator(parent);
+		var enabled = this.get('text').enabled;
+		menu.addItem(mxResources.get('vertical'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_HORIZONTAL, true);
+		}, parent, null, enabled);
+	})));
+	this.put('position', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.styleChange(menu, mxResources.get('left'), [mxConstants.STYLE_LABEL_POSITION,
+			mxConstants.STYLE_ALIGN
+		], [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_RIGHT], null, parent);
+		this.styleChange(menu, mxResources.get('center'), [mxConstants.STYLE_LABEL_POSITION,
+			mxConstants.STYLE_ALIGN
+		], [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_CENTER], null, parent);
+		this.styleChange(menu, mxResources.get('right'), [mxConstants.STYLE_LABEL_POSITION,
+			mxConstants.STYLE_ALIGN
+		], [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_LEFT], null, parent);
+		menu.addSeparator(parent);
+		this.styleChange(menu, mxResources.get('top'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+			mxConstants.STYLE_VERTICAL_ALIGN
+		], [mxConstants.ALIGN_TOP, mxConstants.ALIGN_BOTTOM], null, parent);
+		this.styleChange(menu, mxResources.get('middle'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+			mxConstants.STYLE_VERTICAL_ALIGN
+		], [mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_MIDDLE], null, parent);
+		this.styleChange(menu, mxResources.get('bottom'), [mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+			mxConstants.STYLE_VERTICAL_ALIGN
+		], [mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_TOP], null, parent);
+	})));
+	this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent) {
+		menu.addItem(mxResources.get('flipH'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false);
+		}, parent);
+		menu.addItem(mxResources.get('flipV'), null, function() {
+			graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false);
+		}, parent);
+		this.addMenuItems(menu, ['-', 'tilt', 'rotation'], parent);
+	})));
+	this.put('align', new Menu(mxUtils.bind(this, function(menu, parent) {
+		menu.addItem(mxResources.get('leftAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_LEFT);
+		}, parent);
+		menu.addItem(mxResources.get('center'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_CENTER);
+		}, parent);
+		menu.addItem(mxResources.get('rightAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_RIGHT);
+		}, parent);
+		menu.addSeparator(parent);
+		menu.addItem(mxResources.get('topAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_TOP);
+		}, parent);
+		menu.addItem(mxResources.get('middle'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_MIDDLE);
+		}, parent);
+		menu.addItem(mxResources.get('bottomAlign'), null, function() {
+			graph.alignCells(mxConstants.ALIGN_BOTTOM);
+		}, parent);
+	})));
+	this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent) {
+		// menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
+		// {
+		// 	if (!graph.isSelectionEmpty())
+		// 	{
+		// 		var layout = new mxCompactTreeLayout(graph, true);
+		// 		layout.edgeRouting = false;
+		// 		layout.levelDistance = 30;
+		//    		this.editorUi.executeLayout(layout, true, true);
+		// 	}
+		// }), parent);
+		// menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
+		// {
+		// 	if (!graph.isSelectionEmpty())
+		// 	{
+		// 		var layout = new mxCompactTreeLayout(graph, false);
+		// 		layout.edgeRouting = false;
+		// 		layout.levelDistance = 30;
+		//    		this.editorUi.executeLayout(layout, true, true);
+		// 	}
+		// }), parent);
+		// menu.addSeparator(parent);
+		menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this,
+			function() {
+				var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
+				this.editorUi.executeLayout(layout, true, true);
+			}), parent);
+		menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this,
+			function() {
+				var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
+				this.editorUi.executeLayout(layout, true, true);
+			}), parent);
+		//menu.addSeparator(parent);
+		// menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
+		// {
+		// 	var layout = new mxFastOrganicLayout(graph);
+		//   		this.editorUi.executeLayout(layout, true, true);
+		// }), parent);
+		// menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
+		// {
+		// 	var layout = new mxCircleLayout(graph);
+		//   		this.editorUi.executeLayout(layout, true, true, graph.getSelectionCells());
+		// }), parent);
+	})));
+	this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-',
+			'expand', 'collapse'
+		], parent);
+	})));
+	this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['toFront', 'toBack', '-'], parent);
+		//this.addSubmenu('direction', menu, parent);
+		this.addSubmenu('layout', menu, parent);
+		//this.addSubmenu('align', menu, parent);
+		menu.addSeparator(parent);
+		//this.addSubmenu('navigation', menu, parent);
+		this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup'],
+			parent);
+		//this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'autosize'], parent);
+	})));
+	this.put('view', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['actualSize'], parent);
+		menu.addSeparator();
+		var scales = [0.25, 0.5, 0.75, 1, 2, 4];
+
+		for (var i = 0; i < scales.length; i++) {
+			(function(scale) {
+				menu.addItem((scale * 100) + '%', null, function() {
+					graph.zoomTo(scale);
+				}, parent);
+			})(scales[i]);
+		}
+
+		this.addMenuItems(menu, ['-', 'zoomIn', 'zoomOut', '-', 'fitWindow',
+			'customZoom', '-', 'fitPage', 'fitPageWidth'
+		], parent);
+	})));
+	this.put('file', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', 'publish',
+			'-', 'variable', '-', 'import', 'export', '-', 'editFile', 'rename', '-',
+			'print'
+		], parent);
+	})));
+	this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste',
+			'delete', '-', 'duplicate', '-',
+			'selectVertices', 'selectEdges', 'selectAll'
+		]);
+	})));
+	this.put('help', new Menu(mxUtils.bind(this, function(menu, parent) {
+		this.addMenuItems(menu, ['help', '-', 'about']);
+	})));
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.put = function(name, menu) {
+	this.menus[name] = menu;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.get = function(name) {
+	return this.menus[name];
+};
+
+/**
+ * Adds the given submenu.
+ */
+Menus.prototype.addSubmenu = function(name, menu, parent) {
+	var enabled = this.get(name).enabled;
+
+	if (menu.showDisabled || enabled) {
+		var submenu = menu.addItem(mxResources.get(name), null, null, parent, null,
+			enabled);
+		this.addMenu(name, menu, submenu);
+	}
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Menus.prototype.addMenu = function(name, popupMenu, parent) {
+	var menu = this.get(name);
+
+	if (menu != null && (popupMenu.showDisabled || menu.enabled)) {
+		this.get(name).execute(popupMenu, parent);
+	}
+};
+
+/**
+ * Adds a style change item to the given menu.
+ */
+Menus.prototype.styleChange = function(menu, label, keys, values, sprite,
+	parent) {
+	return menu.addItem(label, null, mxUtils.bind(this, function() {
+		var graph = this.editorUi.editor.graph;
+
+		graph.getModel().beginUpdate();
+		try {
+			for (var i = 0; i < keys.length; i++) {
+				graph.setCellStyles(keys[i], values[i]);
+			}
+		} finally {
+			graph.getModel().endUpdate();
+		}
+	}), parent, sprite);
+};
+
+/**
+ * Adds a style change item with a prompt to the given menu.
+ */
+Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key,
+	parent, enabled) {
+	return menu.addItem(label, null, mxUtils.bind(this, function() {
+		var graph = this.editorUi.editor.graph;
+		var value = defaultValue;
+		var state = graph.getView().getState(graph.getSelectionCell());
+
+		if (state != null) {
+			value = state.style[key] || value;
+		}
+
+		value = mxUtils.prompt(mxResources.get('enterValue') + ((hint.length > 0) ?
+			(' ' + hint) : ''), value);
+
+		if (value != null && value.length > 0) {
+			graph.setCellStyles(key, value);
+		}
+	}), parent, null, enabled);
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.addMenuItem = function(menu, key, parent) {
+	var action = this.editorUi.actions.get(key);
+
+	if (action != null && (menu.showDisabled || action.enabled)) {
+		var item = menu.addItem(action.label, null, action.funct, parent, null,
+			action.enabled);
+
+		// Adds checkmark image
+		if (action.toggleAction && action.isSelected()) {
+			this.addCheckmark(item);
+		}
+
+		this.addShortcut(item, action);
+
+		return item;
+	}
+
+	return null;
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+Menus.prototype.addShortcut = function(item, action) {
+	if (action.shortcut != null) {
+		var td = item.firstChild.nextSibling.nextSibling;
+		var span = document.createElement('span');
+		span.style.color = 'gray';
+		mxUtils.write(span, action.shortcut);
+		td.appendChild(span);
+	}
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+Menus.prototype.addCheckmark = function(item) {
+	var td = item.firstChild.nextSibling;
+	td.style.backgroundImage = 'url(' + IMAGE_PATH + '/checkmark.gif)';
+	td.style.backgroundRepeat = 'no-repeat';
+	td.style.backgroundPosition = '2px 50%';
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.addMenuItems = function(menu, keys, parent) {
+	for (var i = 0; i < keys.length; i++) {
+		if (keys[i] == '-') {
+			menu.addSeparator(parent);
+		} else {
+			this.addMenuItem(menu, keys[i], parent);
+		}
+	}
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.createPopupMenu = function(menu, cell, evt) {
+	var graph = this.editorUi.editor.graph;
+	menu.smartSeparators = true;
+
+	if (graph.isSelectionEmpty()) {
+		this.addMenuItems(menu, ['undo', 'redo', '-', 'paste', '-']);
+	} else {
+		this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', '-', 'duplicate']);
+
+		if (graph.getSelectionCount() == 1 && graph.getModel().isEdge(graph.getSelectionCell())) {
+			this.addMenuItems(menu, ['setAsDefaultEdge']);
+		}
+
+		menu.addSeparator();
+	}
+
+	if (graph.getSelectionCount() > 0) {
+		this.addMenuItems(menu, ['toFront', 'toBack', '-']);
+
+		if (graph.getModel().isEdge(graph.getSelectionCell())) {
+			var isWaypoint = false;
+			var cell = graph.getSelectionCell();
+
+			if (cell != null && graph.getModel().isEdge(cell)) {
+				var handler = graph.selectionCellsHandler.getHandler(cell);
+
+				if (handler instanceof mxEdgeHandler && handler.bends != null && handler.bends
+					.length > 2) {
+					var index = handler.getHandleForEvent(new mxMouseEvent(evt));
+
+					// Configures removeWaypoint action before execution
+					var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
+					rmWaypointAction.handler = handler;
+					rmWaypointAction.index = index;
+
+					isWaypoint = index > 0 && index < handler.bends.length;
+				}
+			}
+
+			this.addMenuItems(menu, ['-', (isWaypoint) ? 'removeWaypoint' :
+				'addWaypoint'
+			]);
+		} else if (graph.getSelectionCount() > 1) {
+			menu.addSeparator();
+			this.addMenuItems(menu, ['group']);
+		}
+
+		menu.addSeparator();
+
+		if (graph.getSelectionCount() == 1) {
+			var link = graph.getLinkForCell(graph.getSelectionCell());
+
+			if (link != null) {
+				this.addMenuItems(menu, ['openLink']);
+			}
+		}
+	} else {
+		this.addMenuItems(menu, ['-', 'selectVertices', 'selectEdges', '-',
+			'selectAll'
+		]);
+	}
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+Menus.prototype.createMenubar = function(container) {
+	var menubar = new Menubar(this.editorUi, container);
+	var menus = ['file', 'edit', 'view', 'format', 'text', 'arrange',
+		'help'
+	];
+
+	for (var i = 0; i < menus.length; i++) {
+		menubar.addMenu(mxResources.get(menus[i]), this.get(menus[i]).funct);
+	}
+
+	return menubar;
+};
+
+/**
+ * Construcs a new menubar for the given editor.
+ */
+function Menubar(editorUi, container) {
+	this.editorUi = editorUi;
+	this.container = container;
+
+	// Global handler to hide the current menu
+	mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt) {
+		this.hideMenu();
+	}));
+};
+
+/**
+ * Adds the menubar elements.
+ */
+Menubar.prototype.hideMenu = function() {
+	if (this.currentMenu != null) {
+		this.currentMenu.hideMenu();
+	}
+};
+
+/**
+ * Adds a submenu to this menubar.
+ */
+Menubar.prototype.addMenu = function(label, funct) {
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geItem';
+	mxUtils.write(elt, label);
+
+	this.addMenuHandler(elt, funct);
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Adds a handler for showing a menu in the given element.
+ */
+Menubar.prototype.addMenuHandler = function(elt, funct) {
+	if (funct != null) {
+		var show = true;
+
+		var clickHandler = mxUtils.bind(this, function(evt) {
+			if (show && elt.enabled == null || elt.enabled) {
+				this.editorUi.editor.graph.panningHandler.hideMenu();
+				var menu = new mxPopupMenu(funct);
+				menu.div.className += ' geMenubarMenu';
+				menu.smartSeparators = true;
+				menu.showDisabled = true;
+				menu.autoExpand = true;
+
+				// Disables autoexpand and destroys menu when hidden
+				menu.hideMenu = mxUtils.bind(this, function() {
+					mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
+					menu.destroy();
+					this.currentMenu = null;
+					this.currentElt = null;
+				});
+
+				// Added width of the page-sidebar
+				var x = elt.offsetLeft + document.getElementById('page-sidebar').offsetWidth +
+					22;
+				// Added static height of the page-navbar
+				var y = elt.offsetTop + elt.offsetHeight + 140;
+
+				menu.popup(x, y, null, evt);
+
+				//menu.popup(elt.offsetLeft + 4, elt.offsetTop + elt.offsetHeight + 4, null, evt);
+				this.currentMenu = menu;
+				this.currentElt = elt;
+			}
+
+			show = true;
+			mxEvent.consume(evt);
+		});
+
+		// Shows menu automatically while in expanded state
+		mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt) {
+			if (this.currentMenu != null && this.currentElt != elt) {
+				this.hideMenu();
+				clickHandler(evt);
+			}
+		}));
+
+		// Hides menu if already showing
+		mxEvent.addListener(elt, 'mousedown', mxUtils.bind(this, function() {
+			show = this.currentElt != elt;
+		}));
+
+		mxEvent.addListener(elt, 'click', clickHandler);
+	}
+};
+
+/**
+ * Constructs a new action for the given parameters.
+ */
+function Menu(funct, enabled) {
+	mxEventSource.call(this);
+	this.funct = funct;
+	this.enabled = (enabled != null) ? enabled : true;
+};
+
+// Menu inherits from mxEventSource
+mxUtils.extend(Menu, mxEventSource);
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Menu.prototype.setEnabled = function(value) {
+	if (this.enabled != value) {
+		this.enabled = value;
+		this.fireEvent(new mxEventObject('stateChanged'));
+	}
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Menu.prototype.execute = function(menu, parent) {
+	this.funct(menu, parent);
+};
diff --git a/public/assets/plugins/square/js/Shapes.js b/public/assets/plugins/square/js/Shapes.js
index 8c205d8..42e13ca 100644
--- a/public/assets/plugins/square/js/Shapes.js
+++ b/public/assets/plugins/square/js/Shapes.js
@@ -1 +1,1234 @@
\ No newline at end of file
+/**
+ * $Id: Shapes.js,v 1.13 2013-02-02 06:44:30 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+
+/**
+ * Registers shapes.
+ */
+(function()
+{
+	// Cube Shape, supports size style
+	function CubeShape() { };
+	CubeShape.prototype = new mxCylinder();
+	CubeShape.prototype.constructor = CubeShape;
+	CubeShape.prototype.size = 20;
+	CubeShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = Math.min(w, Math.min(h, mxUtils.getValue(this.style, 'size', this.size)));
+
+		if (isForeground)
+		{
+			path.moveTo(s, h);
+			path.lineTo(s, s);
+			path.lineTo(0, 0);
+			path.moveTo(s, s);
+			path.lineTo(w, s);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w - s, 0);
+			path.lineTo(w, s);
+			path.lineTo(w, h);
+			path.lineTo(s, h);
+			path.lineTo(0, h - s);
+			path.lineTo(0, 0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['cube'] = CubeShape;
+
+	// Note Shape, supports size style
+	function NoteShape() { };
+	NoteShape.prototype = new mxCylinder();
+	NoteShape.prototype.constructor = NoteShape;
+	NoteShape.prototype.size = 30;
+	NoteShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = Math.min(w, Math.min(h, mxUtils.getValue(this.style, 'size', this.size)));
+
+		if (isForeground)
+		{
+			path.moveTo(w - s, 0);
+			path.lineTo(w - s, s);
+			path.lineTo(w, s);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w - s, 0);
+			path.lineTo(w, s);
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.lineTo(0, 0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['note'] = NoteShape;
+
+	// Folder Shape, supports tabWidth, tabHeight styles
+	function FolderShape() { };
+	FolderShape.prototype = new mxCylinder();
+	FolderShape.prototype.constructor = FolderShape;
+	FolderShape.prototype.tabWidth = 60;
+	FolderShape.prototype.tabHeight = 20;
+	FolderShape.prototype.tabPosition = 'right';
+	FolderShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var tw = mxUtils.getValue(this.style, 'tabWidth', this.tabWidth);
+		var th = mxUtils.getValue(this.style, 'tabHeight', this.tabHeight);
+		var tp = mxUtils.getValue(this.style, 'tabPosition', this.tabPosition);
+		var dx = Math.min(w, tw);
+		var dy = Math.min(h, th);
+
+		if (isForeground)
+		{
+			if (tp == 'left')
+			{
+				path.moveTo(0, dy);
+				path.lineTo(dx, dy);
+			}
+			// Right is default
+			else
+			{
+				path.moveTo(w - dx, dy);
+				path.lineTo(w, dy);
+			}
+			
+			path.end();
+		}
+		else
+		{
+			if (tp == 'left')
+			{
+				path.moveTo(0, 0);
+				path.lineTo(dx, 0);
+				path.lineTo(dx, dy);
+				path.lineTo(w, dy);
+			}
+			// Right is default
+			else
+			{
+				path.moveTo(0, dy);
+				path.lineTo(w - dx, dy);
+				path.lineTo(w - dx, 0);
+				path.lineTo(w, 0);
+			}
+			
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.lineTo(0, dy);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['folder'] = FolderShape;
+
+	// Card Shape, supports size style
+	function CardShape() { };
+	CardShape.prototype = new mxCylinder();
+	CardShape.prototype.constructor = CardShape;
+	CardShape.prototype.size = 30;
+	CardShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = Math.min(w, Math.min(h, mxUtils.getValue(this.style, 'size', this.size)));
+
+		if (!isForeground)
+		{
+			path.moveTo(s, 0);
+			path.lineTo(w, 0);
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.lineTo(0, s);
+			path.lineTo(s, 0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['card'] = CardShape;
+
+	// Tape Shape, supports size style
+	function TapeShape() { };
+	TapeShape.prototype = new mxCylinder();
+	TapeShape.prototype.constructor = TapeShape;
+	TapeShape.prototype.size = 0.4;
+	TapeShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s = mxUtils.getValue(this.style, 'size', this.size);
+		var dy = h * s;
+		var fy = 1.4;
+
+		if (!isForeground)
+		{
+			path.moveTo(0, dy / 2);
+			path.quadTo(w / 4, dy * fy, w / 2, dy / 2);
+			path.quadTo(w * 3 / 4, dy * (1 - fy), w, dy / 2);
+			path.lineTo(w, h - dy / 2);
+			path.quadTo(w * 3 / 4, h - dy * fy, w / 2, h - dy / 2);
+			path.quadTo(w / 4, h - dy * (1 - fy), 0, h - dy / 2);
+			path.lineTo(0, dy / 2);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['tape'] = TapeShape;
+
+	// Tape Shape, supports size style
+	function StepShape() { };
+	StepShape.prototype = new mxCylinder();
+	StepShape.prototype.constructor = StepShape;
+	StepShape.prototype.size = 0.2;
+	StepShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var s =  w * mxUtils.getValue(this.style, 'size', this.size);
+
+		if (!isForeground)
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w - s, 0);
+			path.lineTo(w, h / 2);
+			path.lineTo(w - s, h);
+			path.lineTo(0, h);
+			path.lineTo(s, h / 2);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['step'] = StepShape;
+
+	// Plus Shape
+	function PlusShape()
+	{
+		mxRectangleShape.call(this);
+	};
+	mxUtils.extend(PlusShape, mxRectangleShape);
+	PlusShape.prototype.isHtmlAllowed = function()
+	{
+		return false;
+	};
+	PlusShape.prototype.paintForeground = function(c, x, y, w, h)
+	{
+		var border = Math.min(w / 5, h / 5) + 1;
+		
+		c.begin();
+		c.moveTo(x + w / 2, y + border);
+		c.lineTo(x + w / 2, y + h - border);
+		c.moveTo(x + border, y + h / 2);
+		c.lineTo(x + w - border, y + h / 2);
+		c.end();
+		c.stroke();
+		mxRectangleShape.prototype.paintForeground.apply(this, arguments);
+	};
+
+	mxCellRenderer.prototype.defaultShapes['plus'] = PlusShape;
+
+	// CompositeShape
+	function ExtendedShape()
+	{
+		mxRectangleShape.call(this);
+	};
+	mxUtils.extend(ExtendedShape, mxRectangleShape);
+	ExtendedShape.prototype.isHtmlAllowed = function()
+	{
+		return false;
+	};
+	ExtendedShape.prototype.paintForeground = function(c, x, y, w, h)
+	{
+		if (this.style != null)
+		{
+			if (this.style['double'] == 1)
+			{
+				var inset = Math.max(2, this.strokewidth + 1);
+	
+				mxRectangleShape.prototype.paintBackground.call(this, c, x + inset, y + inset, w - 2 * inset, h - 2 * inset);
+				mxRectangleShape.prototype.paintForeground.apply(this, arguments);
+				
+				x += inset;
+				y += inset;
+				w -= 2 * inset;
+				h -= 2 * inset;
+			}
+			
+			c.setDashed(false);
+			
+			// Draws the symbols defined in the style. The symbols are
+			// numbered from 1...n. Possible postfixes are align,
+			// verticalAlign, spacing, arcSpacing, width, height
+			var counter = 0;
+			var shape = null;
+			
+			do
+			{
+				shape = mxCellRenderer.prototype.defaultShapes[this.style['symbol' + counter]];
+				
+				if (shape != null)
+				{
+					var align = this.style['symbol' + counter + 'Align'];
+					var valign = this.style['symbol' + counter + 'VerticalAlign'];
+					var width = this.style['symbol' + counter + 'Width'];
+					var height = this.style['symbol' + counter + 'Height'];
+					var spacing = this.style['symbol' + counter + 'Spacing'] || 0;
+					var arcspacing = this.style['symbol' + counter + 'ArcSpacing'];
+					
+					if (arcspacing != null)
+					{
+						spacing += this.getArcSize(w + this.strokewidth, h + this.strokewidth) * arcspacing;
+					}
+					
+					var x2 = x;
+					var y2 = y;
+					
+					if (align == mxConstants.ALIGN_CENTER)
+					{
+						x2 += (w - width) / 2;
+					}
+					else if (align == mxConstants.ALIGN_RIGHT)
+					{
+						x2 += w - width - spacing;
+					}
+					else
+					{
+						x2 += spacing;
+					}
+					
+					if (valign == mxConstants.ALIGN_MIDDLE)
+					{
+						y2 += (h - height) / 2;
+					}
+					else if (valign == mxConstants.ALIGN_BOTTOM)
+					{
+						y2 += h - height - spacing;
+					}
+					else
+					{
+						y2 += spacing;
+					}
+					
+					c.save();
+					
+					// Small hack to pass style along into subshape
+					var tmp = new shape();
+					// TODO: Clone style and override settings (eg. strokewidth)
+					tmp.style = this.style;
+					shape.prototype.paintVertexShape.call(tmp, c, x2, y2, width, height);
+					c.restore();
+				}
+				
+				counter++;
+			}
+			while (shape != null);
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['ext'] = ExtendedShape;
+	
+	// Tape Shape, supports size style
+	function MessageShape() { };
+	MessageShape.prototype = new mxCylinder();
+	MessageShape.prototype.constructor = MessageShape;
+	MessageShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		if (isForeground)
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w / 2, h / 2);
+			path.lineTo(w, 0);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(0, 0);
+			path.lineTo(w, 0);
+			path.lineTo(w, h);
+			path.lineTo(0, h);
+			path.close();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['message'] = MessageShape;
+	
+	// New Actor Shape
+	function UmlActorShape() { };
+	UmlActorShape.prototype = new mxCylinder();
+	UmlActorShape.prototype.constructor = UmlActorShape;
+	UmlActorShape.prototype.addPipe = true;
+	UmlActorShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var width = w / 3;
+		var height = h / 4;
+		
+		if (!isForeground)
+		{
+			path.moveTo(w / 2, height);
+			path.curveTo(w / 2 - width, height, w / 2 - width, 0, w / 2, 0);
+			path.curveTo(w / 2 + width, 0, w / 2 + width, height, w / 2, height);
+			path.close();
+
+			path.moveTo(w / 2, height);
+			path.lineTo(w / 2, 2 * h / 3);
+			
+			// Arms
+			path.moveTo(w / 2, h / 3);
+			path.lineTo(0, h / 3);
+			path.moveTo(w / 2, h / 3);
+			path.lineTo(w, h / 3);
+			
+			// Legs
+			path.moveTo(w / 2, 2 * h / 3);
+			path.lineTo(0, h);
+			path.moveTo(w / 2, 2 * h / 3);
+			path.lineTo(w, h);
+			path.end();
+		}
+	};
+
+	// Replaces existing actor shape
+	mxCellRenderer.prototype.defaultShapes['umlActor'] = UmlActorShape;
+
+	// New Actor Shape
+	function LollipopShape() { };
+	LollipopShape.prototype = new mxCylinder();
+	LollipopShape.prototype.constructor = LollipopShape;
+	LollipopShape.prototype.size = 10;
+	LollipopShape.prototype.addPipe = true;
+	LollipopShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var ss = mxUtils.getValue(this.style, 'size', this.size);
+		var width = ss * 2 / 3;
+		var height = ss;
+		
+		if (!isForeground)
+		{
+			path.moveTo(w / 2, height);
+			path.curveTo(w / 2 - width, height, w / 2 - width, 0, w / 2, 0);
+			path.curveTo(w / 2 + width, 0, w / 2 + width, height, w / 2, height);
+			path.close();
+
+			path.moveTo(w / 2, height);
+			path.lineTo(w / 2, h);
+			path.end();
+		}
+	};
+
+	// Replaces existing actor shape
+	mxCellRenderer.prototype.defaultShapes['lollipop'] = LollipopShape;
+	
+	// Folder Shape, supports tabWidth, tabHeight styles
+	function ComponentShape() { };
+	ComponentShape.prototype = new mxCylinder();
+	ComponentShape.prototype.constructor = ComponentShape;
+	ComponentShape.prototype.jettyWidth = 32;
+	ComponentShape.prototype.jettyHeight = 12;
+	ComponentShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+	{
+		var dx = mxUtils.getValue(this.style, 'jettyWidth', this.jettyWidth);
+		var dy = mxUtils.getValue(this.style, 'jettyHeight', this.jettyHeight);
+		var x0 = dx / 2;
+		var x1 = x0 + dx / 2;
+		var y0 = 0.3 * h - dy / 2;
+		var y1 = 0.7 * h - dy / 2;
+
+		if (isForeground)
+		{
+			path.moveTo(x0, y0);
+			path.lineTo(x1, y0);
+			path.lineTo(x1, y0 + dy);
+			path.lineTo(x0, y0 + dy);
+			path.moveTo(x0, y1);
+			path.lineTo(x1, y1);
+			path.lineTo(x1, y1 + dy);
+			path.lineTo(x0, y1 + dy);
+			path.end();
+		}
+		else
+		{
+			path.moveTo(x0, 0);
+			path.lineTo(w, 0);
+			path.lineTo(w, h);
+			path.lineTo(x0, h);
+			path.lineTo(x0, y1 + dy);
+			path.lineTo(0, y1 + dy);
+			path.lineTo(0, y1);
+			path.lineTo(x0, y1);
+			path.lineTo(x0, y0 + dy);
+			path.lineTo(0, y0 + dy);
+			path.lineTo(0, y0);
+			path.lineTo(x0, y0);
+			path.close();
+			path.end();
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['component'] = ComponentShape;
+	
+	// State Shapes derives from double ellipse
+	function StateShape() { };
+	StateShape.prototype = new mxDoubleEllipse();
+	StateShape.prototype.constructor = StateShape;
+	StateShape.prototype.outerStroke = true;
+	StateShape.prototype.paintVertexShape = function(c, x, y, w, h)
+	{
+		var inset = Math.min(4, Math.min(w / 5, h / 5));
+		
+		if (w > 0 && h > 0)
+		{
+			c.ellipse(x + inset, y + inset, w - 2 * inset, h - 2 * inset);
+			c.fillAndStroke();
+		}
+		
+		c.setShadow(false);
+
+		if (this.outerStroke)
+		{
+			c.ellipse(x, y, w, h);
+			c.stroke();			
+		}
+	};
+
+	mxCellRenderer.prototype.defaultShapes['endState'] = StateShape;
+
+	function StartStateShape() { };
+	StartStateShape.prototype = new StateShape();
+	StartStateShape.prototype.constructor = StartStateShape;
+	StartStateShape.prototype.outerStroke = false;
+	
+	mxCellRenderer.prototype.defaultShapes['startState'] = StartStateShape;
+
+	// Image export for state shapes
+	var imageExportInitShapes = mxImageExport.prototype.initShapes;
+	mxImageExport.prototype.initShapes = function()
+	{
+		imageExportInitShapes.apply(this, arguments);
+
+		function createStateShape(outerStroke)
+		{
+			return {
+				drawShape: function(canvas, state, bounds, background)
+				{
+					var x = bounds.x;
+					var y = bounds.y;
+					var w = bounds.width;
+					var h = bounds.height;
+					
+					if (background)
+					{
+						var inset = Math.min(4, Math.min(w / 5, h / 5));
+						x += inset;
+						y += inset;
+						w -= 2 * inset;
+						h -= 2 * inset;
+						
+						if (w > 0 && h > 0)
+						{
+							canvas.ellipse(x, y, w, h);
+						}
+						
+						return true;
+					}
+					else
+					{
+						canvas.fillAndStroke();
+		
+						if (outerStroke)
+						{
+							canvas.ellipse(x, y, w, h);
+							canvas.stroke();
+						}
+					}
+				}
+			};
+		};
+		
+		this.shapes['endState'] = createStateShape(true);
+		this.shapes['startState'] = createStateShape(false);
+	};
+
+	// Defines custom edge shape
+	function LinkShape()
+	{
+		mxArrow.call(this);
+	};
+	mxUtils.extend(LinkShape, mxArrow);
+	LinkShape.prototype.paintEdgeShape = function(c, pts)
+	{
+		var width = 10;
+
+		// Base vector (between end points)
+		var p0 = pts[0];
+		var pe = pts[pts.length - 1];
+		
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+		var dist = Math.sqrt(dx * dx + dy * dy);
+		var length = dist;
+		
+		// Computes the norm and the inverse norm
+		var nx = dx / dist;
+		var ny = dy / dist;
+		var basex = length * nx;
+		var basey = length * ny;
+		var floorx = width * ny/3;
+		var floory = -width * nx/3;
+		
+		// Computes points
+		var p0x = p0.x - floorx / 2;
+		var p0y = p0.y - floory / 2;
+		var p1x = p0x + floorx;
+		var p1y = p0y + floory;
+		var p2x = p1x + basex;
+		var p2y = p1y + basey;
+		var p3x = p2x + floorx;
+		var p3y = p2y + floory;
+		// p4 not necessary
+		var p5x = p3x - 3 * floorx;
+		var p5y = p3y - 3 * floory;
+		
+		c.begin();
+		c.moveTo(p1x, p1y);
+		c.lineTo(p2x, p2y);
+		c.moveTo(p5x + floorx, p5y + floory);
+		c.lineTo(p0x, p0y);
+		c.stroke();
+	};
+
+	// Registers the link shape
+	mxCellRenderer.prototype.defaultShapes['link'] = LinkShape;
+
+	// Registers and defines the custom marker
+	mxMarker.addMarker('dash', function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var nx = unitX * (size + sw + 1);
+		var ny = unitY * (size + sw + 1);
+
+		return function()
+		{
+			canvas.begin();
+			canvas.moveTo(pe.x - nx / 2 - ny / 2, pe.y - ny / 2 + nx / 2);
+			canvas.lineTo(pe.x + ny / 2 - 3 * nx / 2, pe.y - 3 * ny / 2 - nx / 2);
+			canvas.stroke();
+		};
+	});
+
+	// Implements custom handlers
+	var SPECIAL_HANDLE_INDEX = -99;
+
+	// Handlers are only added if mxVertexHandler is defined (ie. not in embedded graph)
+	if (typeof(mxVertexHandler) != 'undefined')
+	{
+		function mxExtVertexHandler(state)
+		{
+			mxVertexHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxExtVertexHandler, mxVertexHandler);
+	
+		mxExtVertexHandler.prototype.useGridForSpecialHandle = false;
+		
+		mxExtVertexHandler.prototype.init = function()
+		{
+			this.horizontal = mxUtils.getValue(this.state.style, mxConstants.STYLE_HORIZONTAL, true);
+			var graph = this.state.view.graph;
+	
+			if (this.handleImage != null)
+			{
+				var bounds = new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height);
+				this.specialHandle = new mxImageShape(bounds, this.handleImage.src);
+			}
+			else
+			{
+				var size = 10;
+				var bounds = new mxRectangle(0, 0, size, size);
+				this.specialHandle = new mxRhombus(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+			}
+			
+			this.specialHandle.dialect = (graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+			this.specialHandle.init(graph.getView().getOverlayPane());
+			this.specialHandle.node.style.cursor = this.getSpecialHandleCursor();
+	
+			mxEvent.redirectMouseEvents(this.specialHandle.node, graph, this.state);
+			mxVertexHandler.prototype.init.apply(this, arguments);
+		};
+		
+		mxExtVertexHandler.prototype.getSpecialHandleCursor = function()
+		{
+			return 'default';
+		};
+		
+		mxExtVertexHandler.prototype.redraw = function()
+		{
+			mxVertexHandler.prototype.redraw.apply(this, arguments);
+	
+			var size = this.specialHandle.bounds.width;
+			this.specialHandle.bounds = this.getSpecialHandleBounds(size);
+			this.specialHandle.redraw();
+		};
+
+		mxExtVertexHandler.prototype.destroy = function()
+		{
+			mxVertexHandler.prototype.destroy.apply(this, arguments);
+			
+			if (this.specialHandle != null)
+			{
+				this.specialHandle.destroy();
+				this.specialHandle = null;
+			}
+		};
+		
+		mxExtVertexHandler.prototype.getHandleForEvent = function(me)
+		{
+			if (me.isSource(this.specialHandle))
+			{
+				return SPECIAL_HANDLE_INDEX;
+			}
+			
+			return mxVertexHandler.prototype.getHandleForEvent.apply(this, arguments);
+		};
+
+		mxExtVertexHandler.prototype.mouseMove = function(sender, me)
+		{
+			if (!me.isConsumed() && this.index == SPECIAL_HANDLE_INDEX)
+			{
+				var point = new mxPoint(me.getGraphX(), me.getGraphY());
+				this.constrainPoint(point);
+				var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+				var scale = this.graph.getView().scale;
+				
+				if (gridEnabled && this.useGridForSpecialHandle)
+				{
+					point.x = this.graph.snap(point.x / scale) * scale;
+					point.y = this.graph.snap(point.y / scale) * scale;
+				}
+				
+				this.updateStyle(point);			
+				this.moveSizerTo(this.specialHandle, point.x, point.y);
+				this.state.view.graph.cellRenderer.redraw(this.state, true);
+				me.consume();
+			}
+			else
+			{
+				mxVertexHandler.prototype.mouseMove.apply(this, arguments);
+			}
+		};
+
+		mxExtVertexHandler.prototype.mouseUp = function(sender, me)
+		{
+			if (!me.isConsumed() && this.index == SPECIAL_HANDLE_INDEX)
+			{
+				this.applyStyle();
+				this.reset();
+				me.consume();
+			}
+			else
+			{
+				mxVertexHandler.prototype.mouseUp.apply(this, arguments);
+			}
+		};
+
+		mxExtVertexHandler.prototype.getSpecialHandleBounds = function(size)
+		{
+			var rotation = this.state.shape.getShapeRotation();
+			var alpha = mxUtils.toRadians(rotation);
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+			
+			var bounds = new mxRectangle(this.state.x, this.state.y, this.state.width, this.state.height);
+			
+			if (this.state.shape.isPaintBoundsInverted())
+			{
+				var t = (bounds.width - bounds.height) / 2;
+				bounds.x += t;
+				bounds.y -= t;
+				var tmp = bounds.width;
+				bounds.width = bounds.height;
+				bounds.height = tmp;
+			}
+	
+			var pt = this.getSpecialHandlePoint(bounds);
+
+			if (this.state.shape.flipH)
+			{
+				pt.x = 2 * bounds.x + bounds.width - pt.x;
+			}
+			
+			if (this.state.shape.flipV)
+			{
+				pt.y = 2 * bounds.y + bounds.height - pt.y;
+			}
+			
+			pt = mxUtils.getRotatedPoint(pt, cos, sin,
+				new mxPoint(this.state.getCenterX(), this.state.getCenterY()));
+
+			return new mxRectangle(pt.x - size / 2, pt.y - size / 2, size, size);
+		};
+		
+		mxExtVertexHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			// Hook for subclassers
+			return null;
+		};
+	
+		mxExtVertexHandler.prototype.updateStyle = function(point)
+		{
+			// Hook for subclassers
+		};
+		
+		mxExtVertexHandler.prototype.constrainPoint = function(point)
+		{
+			point.x = Math.max(this.state.x, Math.min(this.state.x + this.state.width, point.x));
+			point.y = Math.max(this.state.y, Math.min(this.state.y + this.state.height, point.y));
+		};
+		
+		mxExtVertexHandler.prototype.applyStyle = function()
+		{
+			// Hook for subclassers
+		};
+
+		// Folder Handler
+		function mxFolderHandler(state)
+		{
+			mxExtVertexHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxFolderHandler, mxExtVertexHandler);
+
+		mxFolderHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var tw = Math.min(bounds.width, mxUtils.getValue(this.state.style, 'tabWidth', 60) * scale);
+			var th = Math.min(bounds.height, mxUtils.getValue(this.state.style, 'tabHeight', 20) * scale);
+			
+			var tp = mxUtils.getValue(this.state.style, 'tabPosition', 'right');
+			var x = (tp == 'left') ? bounds.x + tw : bounds.x + bounds.width - tw;
+	
+			return new mxPoint(x, bounds.y + th);
+		};
+		
+		mxFolderHandler.prototype.updateStyle = function(point)
+		{
+			var rotation = this.state.shape.getShapeRotation();
+			var alpha = mxUtils.toRadians(rotation);
+			var cos = Math.cos(-alpha);
+			var sin = Math.sin(-alpha);
+
+			var bounds = new mxRectangle(this.state.x, this.state.y, this.state.width, this.state.height);
+			
+			if (this.state.shape.isPaintBoundsInverted())
+			{
+				var t = (bounds.width - bounds.height) / 2;
+				bounds.x += t;
+				bounds.y -= t;
+				var tmp = bounds.width;
+				bounds.width = bounds.height;
+				bounds.height = tmp;
+			}
+	
+			var pt = new mxPoint(point.x, point.y);
+			pt = mxUtils.getRotatedPoint(pt, cos, sin,
+				new mxPoint(this.state.getCenterX(), this.state.getCenterY()));
+
+			if (this.state.shape.flipH)
+			{
+				pt.x = 2 * bounds.x + bounds.width - pt.x;
+			}
+			
+			if (this.state.shape.flipV)
+			{
+				pt.y = 2 * bounds.y + bounds.height - pt.y;
+			}
+			
+			var result = this.updateStyleUnrotated(pt, bounds);
+		
+			// Modifies point to use rotated coordinates of return value
+			if (result != null)
+			{
+				if (this.state.shape.flipH)
+				{
+					result.x = 2 * bounds.x + bounds.width - result.x;
+				}
+				
+				if (this.state.shape.flipV)
+				{
+					result.y = 2 * bounds.y + bounds.height - result.y;
+				}
+				
+				cos = Math.cos(alpha);
+				sin = Math.sin(alpha);
+				result = mxUtils.getRotatedPoint(result, cos, sin,
+						new mxPoint(this.state.getCenterX(), this.state.getCenterY()));
+				point.x = result.x;
+				point.y = result.y;
+			}
+		};
+		
+		mxFolderHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var tp = mxUtils.getValue(this.state.style, 'tabPosition', 'right');
+			var tw = (tp == 'left') ? pt.x - bounds.x : bounds.x + bounds.width - pt.x;
+			var th = pt.y - bounds.y;
+			
+			var scale = this.graph.getView().scale;
+			this.state.style['tabWidth'] = Math.round(Math.max(1, tw) / scale);
+			this.state.style['tabHeight'] =  Math.round(Math.max(1, th) / scale);
+		};
+		
+		mxFolderHandler.prototype.applyStyle = function()
+		{
+			var model = this.graph.getModel();
+			model.beginUpdate();
+			try
+			{
+				this.state.view.graph.setCellStyles('tabWidth', this.state.style['tabWidth'], [this.state.cell]);
+				this.state.view.graph.setCellStyles('tabHeight', this.state.style['tabHeight'], [this.state.cell]);
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		};
+		
+		// Swimlane Handler
+		function mxSwimlaneHandler(state)
+		{
+			mxFolderHandler.call(this, state);
+		};
+		
+		mxUtils.extend(mxSwimlaneHandler, mxFolderHandler);
+		
+		mxSwimlaneHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var startSize = mxUtils.getValue(this.state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
+
+			return new mxPoint(bounds.x + bounds.width / 2, bounds.y + Math.min(bounds.height, startSize * scale));
+		};
+		
+		mxSwimlaneHandler.prototype.updateStyleUnrotated = function(point, bounds)
+		{
+			point.x = bounds.x + bounds.width / 2;
+			startSize = point.y - bounds.y;
+			var scale = this.graph.getView().scale;
+			this.state.style['startSize'] = Math.round(Math.max(1, startSize) / scale);
+			
+			return point;
+		};
+		
+		mxSwimlaneHandler.prototype.applyStyle = function()
+		{
+			this.state.view.graph.setCellStyles('startSize', this.state.style['startSize'], [this.state.cell]);
+		};
+
+		// Cube Handler
+		function mxCubeHandler(state)
+		{
+			mxFolderHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxCubeHandler, mxFolderHandler);
+		
+		mxCubeHandler.prototype.defaultValue = 20;
+	
+		mxCubeHandler.prototype.scaleFactor = 1;
+		
+		mxCubeHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var sz = Math.min(bounds.width, Math.min(bounds.height,
+				mxUtils.getValue(this.state.style, 'size', this.defaultValue) * scale / this.scaleFactor));
+			
+			return new mxPoint(bounds.x + sz, bounds.y + sz);
+		};
+	
+		mxCubeHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.max(0, Math.min(Math.min(bounds.width / this.scaleFactor, pt.x - bounds.x),
+					Math.min(bounds.height / this.scaleFactor, pt.y - bounds.y)));
+			var scale = this.graph.getView().scale;
+			this.state.style['size'] = Math.round(Math.max(1, size) / scale) * this.scaleFactor;
+			
+			// Stays on the diagonal
+			return new mxPoint(bounds.x + size, bounds.y + size);
+		};
+		
+		mxCubeHandler.prototype.applyStyle = function()
+		{
+			this.state.view.graph.setCellStyles('size', this.state.style['size'], [this.state.cell]);
+		};
+		
+		// Card Handler
+		function mxCardHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxCardHandler, mxCubeHandler);
+		
+		mxCardHandler.prototype.defaultValue = 30;
+	
+		mxCardHandler.prototype.scaleFactor = 2;
+		
+		// Note Handler
+		function mxNoteHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxNoteHandler, mxCubeHandler);
+		
+		mxNoteHandler.prototype.defaultValue = 30;
+	
+		mxNoteHandler.prototype.scaleFactor = 1;
+		
+		mxNoteHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var scale = this.graph.getView().scale;
+			var sz = Math.min(bounds.width, Math.min(bounds.height,
+					mxUtils.getValue(this.state.style, 'size', this.defaultValue) * scale / this.scaleFactor));
+			
+			return new mxPoint(bounds.x + bounds.width - sz, bounds.y + sz);
+		};
+		
+		mxNoteHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.max(0, Math.min(Math.min(bounds.width / this.scaleFactor, pt.x - bounds.x + bounds.width),
+					Math.min(bounds.height / this.scaleFactor, pt.y - bounds.y)));
+			var scale = this.graph.getView().scale;
+			this.state.style['size'] = Math.round(Math.max(1, size) / scale) * this.scaleFactor;
+			
+			// Stays on the diagonal
+			return new mxPoint(bounds.x + bounds.width - size, bounds.y + size);
+		};
+		
+		// Step Handler
+		function mxStepHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxStepHandler, mxCubeHandler);
+		
+		mxStepHandler.prototype.defaultValue = 0.2;
+	
+		mxStepHandler.prototype.scaleFactor = 1;
+		
+		mxStepHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var sz = mxUtils.getValue(this.state.style, 'size', this.defaultValue);
+			
+			return new mxPoint(bounds.x + bounds.width * sz, bounds.y + bounds.height / 2);
+		};
+	
+		mxStepHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.min(1, (pt.x - bounds.x) / bounds.width);
+			this.state.style['size'] = size;
+			
+			return new mxPoint(bounds.x + size * bounds.width, bounds.y + bounds.height / 2);
+		};
+		
+		// Tape Handler
+		function mxTapeHandler(state)
+		{
+			mxCubeHandler.call(this, state);
+		};
+	
+		mxUtils.extend(mxTapeHandler, mxCubeHandler);
+		
+		mxTapeHandler.prototype.defaultValue = 0.4;
+	
+		mxTapeHandler.prototype.scaleFactor = 1;
+		
+		mxTapeHandler.prototype.getSpecialHandlePoint = function(bounds)
+		{
+			var sz = mxUtils.getValue(this.state.style, 'size', this.defaultValue);
+	
+			return new mxPoint(bounds.x + bounds.width / 2, bounds.y + sz * bounds.height / 2);
+		};
+	
+		mxTapeHandler.prototype.updateStyleUnrotated = function(pt, bounds)
+		{
+			var size = Math.max(0, Math.min(1, ((pt.y - bounds.y) / bounds.height) * 2));
+			this.state.style['size'] = size;
+			
+			return new mxPoint(bounds.x + bounds.width / 2, bounds.y + size * bounds.height / 2);
+		};
+		
+		var handlers = {'swimlane': mxSwimlaneHandler, 'folder': mxFolderHandler, 'cube': mxCubeHandler,
+				'card': mxCardHandler, 'note': mxNoteHandler, 'step': mxStepHandler, 'tape': mxTapeHandler};
+
+		var mxGraphCreateHandler = mxGraph.prototype.createHandler;
+		mxGraph.prototype.createHandler = function(state)
+		{
+			if (state != null)
+			{
+				var ctor = handlers[state.style['shape']];
+
+				if (ctor != null)
+				{
+					return new ctor(state);
+				}
+			}
+			
+			return mxGraphCreateHandler.apply(this, arguments);
+		};
+	}
+	
+	// Constraints
+	mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+	{
+		if (terminal != null && terminal.shape != null)
+		{
+			if (terminal.shape.stencil != null)
+			{
+				if (terminal.shape.stencil != null)
+				{
+					return terminal.shape.stencil.constraints;
+				}
+			}
+			else if (terminal.shape.constraints != null)
+			{
+				return terminal.shape.constraints;
+			}
+		}
+
+		return null;
+	};
+
+	mxRectangleShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.75, 0), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.25), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.75), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.25), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.75), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.75, 1), true)];
+	mxLabel.prototype.constraints = mxRectangleShape.prototype.constraints;
+	mxImageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	mxSwimlane.prototype.constraints = mxRectangleShape.prototype.constraints;
+	PlusShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	NoteShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	CardShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	CubeShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	FolderShape.prototype.constraints = mxRectangleShape.prototype.constraints;
+	mxCylinder.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.15, 0.05), false),
+                                        new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+                                        new mxConnectionConstraint(new mxPoint(0.85, 0.05), false),
+      	              		 new mxConnectionConstraint(new mxPoint(0, 0.3), true),
+      	              		 new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+      	              		 new mxConnectionConstraint(new mxPoint(0, 0.7), true),
+      	            		 new mxConnectionConstraint(new mxPoint(1, 0.3), true),
+      	            		 new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+      	            		 new mxConnectionConstraint(new mxPoint(1, 0.7), true),
+      	            		 new mxConnectionConstraint(new mxPoint(0.15, 0.95), false),
+      	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+      	            		 new mxConnectionConstraint(new mxPoint(0.85, 0.95), false)];
+	UmlActorShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0.1), false),
+	                                          new mxConnectionConstraint(new mxPoint(0.5, 0), false),
+	                                          new mxConnectionConstraint(new mxPoint(0.75, 0.1), false),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 1/3), false),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 1), false),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 1/3), false),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 1), false),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.5, 0.5), false)];
+	ComponentShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                          new mxConnectionConstraint(new mxPoint(0.75, 0), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.3), true),
+	        	              		 new mxConnectionConstraint(new mxPoint(0, 0.7), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.25), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(1, 0.75), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	        	            		 new mxConnectionConstraint(new mxPoint(0.75, 1), true)];
+	mxActor.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+   	              		 new mxConnectionConstraint(new mxPoint(0.25, 0.2), false),
+   	              		 new mxConnectionConstraint(new mxPoint(0.1, 0.5), false),
+   	              		 new mxConnectionConstraint(new mxPoint(0, 0.75), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.75, 0.25), false),
+   	            		 new mxConnectionConstraint(new mxPoint(0.9, 0.5), false),
+   	            		 new mxConnectionConstraint(new mxPoint(1, 0.75), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+   	            		 new mxConnectionConstraint(new mxPoint(0.75, 1), true)];
+	TapeShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.35), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.5), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.65), false),
+	                                   new mxConnectionConstraint(new mxPoint(1, 0.35), false),
+		                                new mxConnectionConstraint(new mxPoint(1, 0.5), false),
+		                                new mxConnectionConstraint(new mxPoint(1, 0.65), false),
+										new mxConnectionConstraint(new mxPoint(0.25, 1), false),
+										new mxConnectionConstraint(new mxPoint(0.75, 0), false)];
+	// TODO: Relative ports
+	StepShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0), true),
+                                       new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+                                       new mxConnectionConstraint(new mxPoint(0.75, 0), true),
+                                       new mxConnectionConstraint(new mxPoint(0.25, 1), true),
+  	        	            		 	new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+  	        	            		 	new mxConnectionConstraint(new mxPoint(0.75, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.1, 0.25), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.2, 0.5), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.1, 0.75), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.9, 0.25), false),
+		                                new mxConnectionConstraint(new mxPoint(1, 0.5), false),
+		                                new mxConnectionConstraint(new mxPoint(0.9, 0.75), false)];
+	mxLine.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.5), false),
+	                                new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
+	                                new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
+									new mxConnectionConstraint(new mxPoint(1, 0.5), false)];
+	LollipopShape.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.5, 0), false),
+										new mxConnectionConstraint(new mxPoint(0.5, 1), false)];
+	mxEllipse.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0), true), new mxConnectionConstraint(new mxPoint(1, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0, 1), true), new mxConnectionConstraint(new mxPoint(1, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.5, 0), true), new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	          	              		   new mxConnectionConstraint(new mxPoint(0, 0.5), true), new mxConnectionConstraint(new mxPoint(1, 0.5))];
+	mxDoubleEllipse.prototype.constraints = mxEllipse.prototype.constraints;
+	mxRhombus.prototype.constraints = mxEllipse.prototype.constraints;
+	mxTriangle.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0, 0.25), true),
+	                                    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.75), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(1, 0.5), true)];
+	mxHexagon.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.375, 0), true),
+	                                    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.625, 0), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.125, 0.25), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.125, 0.75), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.875, 0.25), false),
+	                                   new mxConnectionConstraint(new mxPoint(0, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(1, 0.5), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.875, 0.75), false),
+	                                   new mxConnectionConstraint(new mxPoint(0.375, 1), true),
+	                                    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
+	                                   new mxConnectionConstraint(new mxPoint(0.625, 1), true)];
+	mxCloud.prototype.constraints = [new mxConnectionConstraint(new mxPoint(0.25, 0.25), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.4, 0.1), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.16, 0.55), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.07, 0.4), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.31, 0.8), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.13, 0.77), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.8, 0.8), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.55, 0.95), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.875, 0.5), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.96, 0.7), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.625, 0.2), false),
+	                                 new mxConnectionConstraint(new mxPoint(0.88, 0.25), false)];
+	mxArrow.prototype.constraints = null;
+})();
diff --git a/public/assets/plugins/square/js/Sidebar.js b/public/assets/plugins/square/js/Sidebar.js
index 5051f3a..bdcb8ea 100644
--- a/public/assets/plugins/square/js/Sidebar.js
+++ b/public/assets/plugins/square/js/Sidebar.js
@@ -1 +1,904 @@
\ No newline at end of file
+'use strict';
+
+/**
+ * Construcs a new sidebar for the given editor.
+ */
+function Sidebar(editorUi, container) {
+	this.editorUi = editorUi;
+	this.container = container;
+	this.palettes = new Object();
+	this.showTooltips = true;
+	this.graph = new Graph(document.createElement('div'), null, null, this.editorUi
+		.editor.graph.getStylesheet());
+	this.graph.resetViewOnRootChange = false;
+	this.graph.foldingEnabled = false;
+	this.graph.setConnectable(false);
+	this.graph.autoScroll = false;
+	this.graph.setTooltips(false);
+	this.graph.setEnabled(false);
+
+	// Container must be in the DOM for correct HTML rendering
+	this.graph.container.style.visibility = 'hidden';
+	this.graph.container.style.position = 'absolute';
+	document.body.appendChild(this.graph.container);
+
+	if (!mxClient.IS_TOUCH) {
+		mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function() {
+			this.showTooltips = true;
+		}));
+
+		// Enables tooltips after scroll
+		mxEvent.addListener(container, 'scroll', mxUtils.bind(this, function() {
+			this.showTooltips = true;
+		}));
+
+		mxEvent.addListener(document, 'mousedown', mxUtils.bind(this, function() {
+			this.showTooltips = false;
+			this.hideTooltip();
+		}));
+
+		mxEvent.addListener(document, 'mousemove', mxUtils.bind(this, function(evt) {
+			var src = mxEvent.getSource(evt);
+
+			while (src != null) {
+				if (src == this.currentElt) {
+					return;
+				}
+
+				src = src.parentNode;
+			}
+
+			this.hideTooltip();
+		}));
+
+		// Handles mouse leaving the window
+		mxEvent.addListener(document, 'mouseout', mxUtils.bind(this, function(evt) {
+			if (evt.toElement == null && evt.relatedTarget == null) {
+				this.hideTooltip();
+			}
+		}));
+	}
+
+	this.init();
+
+	// Pre-fetches tooltip image
+	new Image().src = IMAGE_PATH + '/tooltip.png';
+};
+
+/**
+ * Adds all palettes to the sidebar.
+ */
+Sidebar.prototype.init = function() {
+	var dir = STENCIL_PATH;
+	console.log(dir);
+
+	this.addImagePalette('entry', mxResources.get('entry'), dir + '/entry/',
+		'_128x128.png', ['start', 'finally', 'end'], [mxResources.get('start'),
+			mxResources.get('finally'), mxResources.get('end')
+		]);
+	this.addImagePalette('call_management', mxResources.get('call_management'),
+		dir + '/call_management/', '_128x128.png', ['answer', 'hangup', 'dial',
+			'ext_dial', 'queue', 'voicemail'
+			// , 'callback'
+		], [mxResources.get('answer'), mxResources.get('hangup'), mxResources.get(
+				'dial'), mxResources.get('ext_dial'), mxResources.get('queue'),
+			mxResources.get('voicemail')
+			// , mxResources.get('callback')
+		]);
+	this.addImagePalette('variable', mxResources.get('variable'), dir +
+		'/variable/', '_128x128.png', ['set', 'math'], [mxResources.get('set'),
+			mxResources.get('math')
+		]);
+	this.addImagePalette('playback', mxResources.get('playback'), dir +
+		'/playback/', '_128x128.png', ['playback', 'menu',
+			'saydigits', 'saynumber', 'sayphonetic', 'tts', 'ispeechtts', 'getdigits'
+		], [mxResources.get('playback'), mxResources
+			.get('menu'), mxResources.get('saydigits'), mxResources.get('saynumber'),
+			mxResources.get('sayphonetic'), mxResources.get('tts'), mxResources.get('ispeechtts'), mxResources.get('getdigits')
+		]);
+	this.addImagePalette('recording', mxResources.get('recording'), dir +
+		'/recording/', '_128x128.png', ['record'], [mxResources.get('record')]);
+	this.addImagePalette('integrationServer', mxResources.get('integrationServer'),
+		dir + '/integration_server/', '_128x128.png', ['database', 'ispeechasr'], [
+			mxResources.get('database'), mxResources.get('ispeechasr')
+		]);
+	this.addImagePalette('callflow', mxResources.get('callflow'), dir +
+		'/callflow/', '_128x128.png', ['gotoc', 'gotoif', 'gotoiftime', 'vswitch'], [
+			mxResources.get('gotoc'), mxResources.get('gotoif'), mxResources.get(
+				'gotoiftime'), mxResources.get('vswitch')
+		]);
+	this.addImagePalette('stats', mxResources.get('stats'), dir + '/stats/',
+		'_128x128.png', [
+			// 'queuelog',
+			'goal'
+		], [
+			// mxResources.get('queuelog'),
+			mxResources.get('goal')
+		]);
+	this.addImagePalette('general', mxResources.get('general'), dir + '/general/',
+		'_128x128.png', ['noop', 'system', 'agi', 'subproject', 'custom_app'], [mxResources.get(
+			'noop'), mxResources.get('system'), mxResources.get('agi'), mxResources.get(
+			'subproject'), mxResources.get(
+			'custom_app')]);
+
+};
+
+/**
+ * Specifies if tooltips should be visible. Default is true.
+ */
+Sidebar.prototype.enableTooltips = !mxClient.IS_TOUCH;
+
+/**
+ * Specifies the delay for the tooltip. Default is 16 px.
+ */
+Sidebar.prototype.tooltipBorder = 16;
+
+/**
+ * Specifies the delay for the tooltip. Default is 3 px.
+ */
+Sidebar.prototype.thumbBorder = 3;
+
+/**
+ * Specifies the delay for the tooltip. Default is 300 ms.
+ */
+Sidebar.prototype.tooltipDelay = 300;
+
+/**
+ * Specifies if edges should be used as templates if clicked. Default is true.
+ */
+Sidebar.prototype.installEdges = true;
+
+/**
+ * Specifies the URL of the gear image.
+ */
+Sidebar.prototype.gearImage = STENCIL_PATH + '/clipart/Gear_128x128.png';
+
+/**
+ * Specifies the width of the thumbnails.
+ */
+Sidebar.prototype.thumbWidth = 34;
+
+/**
+ * Specifies the height of the thumbnails.
+ */
+Sidebar.prototype.thumbHeight = 34;
+
+/**
+ * Specifies the padding for the thumbnails. Default is 3.
+ */
+Sidebar.prototype.thumbPadding = 2;
+
+/**
+ * Specifies the size of the sidebar titles.
+ */
+Sidebar.prototype.sidebarTitleSize = 9;
+
+/**
+ * Specifies if titles in the sidebar should be enabled.
+ */
+Sidebar.prototype.sidebarTitles = false;
+
+/**
+ * Specifies if titles in the tooltips should be enabled.
+ */
+Sidebar.prototype.tooltipTitles = false;
+
+/**
+ * Adds all palettes to the sidebar.
+ */
+Sidebar.prototype.showTooltip = function(elt, cells, title, showLabel) {
+	if (this.enableTooltips && this.showTooltips) {
+		if (this.currentElt != elt) {
+			if (this.thread != null) {
+				window.clearTimeout(this.thread);
+				this.thread = null;
+			}
+
+			var show = mxUtils.bind(this, function() {
+				// Lazy creation of the DOM nodes and graph instance
+				if (this.tooltip == null) {
+					this.tooltip = document.createElement('div');
+					this.tooltip.className = 'geSidebarTooltip';
+					document.body.appendChild(this.tooltip);
+
+					this.graph2 = new Graph(this.tooltip, null, null, this.editorUi.editor.graph
+						.getStylesheet());
+					this.graph2.view.setTranslate(this.tooltipBorder, this.tooltipBorder);
+					this.graph2.resetViewOnRootChange = false;
+					this.graph2.foldingEnabled = false;
+					this.graph2.autoScroll = false;
+					this.graph2.setTooltips(false);
+					this.graph2.setConnectable(false);
+					this.graph2.setEnabled(false);
+
+					if (!mxClient.IS_SVG) {
+						this.graph2.view.canvas.style.position = 'relative';
+					}
+
+					this.tooltipImage = mxUtils.createImage(IMAGE_PATH + '/tooltip.png');
+					this.tooltipImage.style.position = 'absolute';
+					this.tooltipImage.style.width = '14px';
+					this.tooltipImage.style.height = '27px';
+
+					document.body.appendChild(this.tooltipImage);
+				}
+
+				this.tooltip.style.display = 'block';
+				this.graph2.labelsVisible = (showLabel == null || showLabel);
+				this.graph2.model.clear();
+				this.graph2.addCells(cells);
+
+				var bounds = this.graph2.getGraphBounds();
+				var width = bounds.width + 2 * this.tooltipBorder;
+				var height = bounds.height + 2 * this.tooltipBorder;
+
+				if (mxClient.IS_QUIRKS) {
+					width += 4;
+					height += 4;
+					this.tooltip.style.overflow = 'hidden';
+				} else {
+					this.tooltip.style.overflow = 'visible';
+				}
+
+				this.tooltipImage.style.visibility = 'visible';
+				this.tooltip.style.width = width + 'px';
+
+				// Adds title for entry
+				if (this.tooltipTitles && title != null && title.length > 0) {
+					if (this.tooltipTitle == null) {
+						this.tooltipTitle = document.createElement('div');
+						this.tooltipTitle.style.borderTop = '1px solid gray';
+						this.tooltipTitle.style.textAlign = 'center';
+						this.tooltipTitle.style.width = '100%';
+
+						// Oversize titles are cut-off currently. Should make tooltip wider later.
+						this.tooltipTitle.style.overflow = 'hidden';
+
+						if (mxClient.IS_SVG) {
+							this.tooltipTitle.style.paddingTop = '2px';
+						} else {
+							this.tooltipTitle.style.position = 'absolute';
+							this.tooltipTitle.style.paddingTop = '6px';
+						}
+
+						this.tooltip.appendChild(this.tooltipTitle);
+					} else {
+						this.tooltipTitle.innerHTML = '';
+					}
+
+					this.tooltipTitle.style.display = '';
+					mxUtils.write(this.tooltipTitle, title);
+
+					var dy = this.tooltipTitle.offsetHeight + 10;
+					height += dy;
+
+					if (mxClient.IS_SVG) {
+						this.tooltipTitle.style.marginTop = (-dy) + 'px';
+					} else {
+						height -= 6;
+						this.tooltipTitle.style.top = (height - dy) + 'px';
+					}
+				} else if (this.tooltipTitle != null && this.tooltipTitle.parentNode !=
+					null) {
+					this.tooltipTitle.style.display = 'none';
+				}
+
+				this.tooltip.style.height = height + 'px';
+				var x0 = -Math.min(0, bounds.x - this.tooltipBorder);
+				var y0 = -Math.min(0, bounds.y - this.tooltipBorder);
+
+				var left = this.container.clientWidth + this.editorUi.splitSize + 3;
+				var top = Math.max(0, (this.container.offsetTop + elt.offsetTop - this.container
+					.scrollTop - height / 2 + 16));
+
+				if (mxClient.IS_SVG) {
+					this.graph2.view.canvas.setAttribute('transform', 'translate(' + x0 +
+						',' + y0 + ')');
+				} else {
+					this.graph2.view.drawPane.style.left = x0 + 'px';
+					this.graph2.view.drawPane.style.top = y0 + 'px';
+				}
+
+				// Workaround for ignored position CSS style in IE9
+				// (changes to relative without the following line)
+				// Added width of the page-sidebar
+				left += document.getElementById('page-sidebar').offsetWidth;
+				// Added static height of the page-navbar
+				top += 90;
+
+				this.tooltip.style.position = 'absolute';
+				this.tooltip.style.left = left + 'px';
+				this.tooltip.style.top = top + 'px';
+				this.tooltipImage.style.left = (left - 13) + 'px';
+				this.tooltipImage.style.top = (top + height / 2 - 13) + 'px';
+			});
+
+			if (this.tooltip != null && this.tooltip.style.display != 'none') {
+				show();
+			} else {
+				this.thread = window.setTimeout(show, this.tooltipDelay);
+			}
+
+			this.currentElt = elt;
+		}
+	}
+};
+
+/**
+ * Hides the current tooltip.
+ */
+Sidebar.prototype.hideTooltip = function() {
+	if (this.thread != null) {
+		window.clearTimeout(this.thread);
+		this.thread = null;
+	}
+
+	if (this.tooltip != null) {
+		this.tooltip.style.display = 'none';
+		this.tooltipImage.style.visibility = 'hidden';
+		this.currentElt = null;
+	}
+};
+
+/**
+ * Creates and returns the given title element.
+ */
+Sidebar.prototype.createTitle = function(label) {
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geTitle';
+	mxUtils.write(elt, label);
+
+	return elt;
+};
+
+/**
+ * Creates a thumbnail for the given cells.
+ */
+Sidebar.prototype.createThumb = function(cells, width, height, parent, title,
+	showLabel) {
+	this.graph.labelsVisible = (showLabel == null || showLabel);
+	this.graph.view.scaleAndTranslate(1, 0, 0);
+	this.graph.addCells(cells);
+	var bounds = this.graph.getGraphBounds();
+	var corr = this.thumbBorder;
+	var s = Math.min((width - 2) / (bounds.width - bounds.x + corr), (height - 2) /
+		(bounds.height - bounds.y + corr));
+	var x0 = -Math.min(bounds.x, 0);
+	var y0 = -Math.min(bounds.y, 0);
+	this.graph.view.scaleAndTranslate(s, x0, y0);
+
+	bounds = this.graph.getGraphBounds();
+	var dx = Math.max(0, Math.floor((width - bounds.width - bounds.x) / 2));
+	var dy = Math.max(0, Math.floor((height - bounds.height - bounds.y) / 2));
+
+	var node = null;
+
+	// For supporting HTML labels in IE9 standards mode the container is cloned instead
+	if (this.graph.dialect == mxConstants.DIALECT_SVG && !mxClient.NO_FO) {
+		node = this.graph.view.getCanvas().ownerSVGElement.cloneNode(true);
+	}
+	// LATER: Check if deep clone can be used for quirks if container in DOM
+	else {
+		node = this.graph.container.cloneNode(false);
+		node.innerHTML = this.graph.container.innerHTML;
+	}
+
+	this.graph.getModel().clear();
+
+	// Catch-all event handling
+	if (mxClient.IS_IE6) {
+		parent.style.backgroundImage = 'url(' + this.editorUi.editor.transparentImage +
+			')';
+	}
+
+	var dd = 3;
+	node.style.position = 'relative';
+	node.style.overflow = 'hidden';
+	node.style.cursor = 'pointer';
+	node.style.left = (dx + dd) + 'px';
+	node.style.top = (dy + dd) + 'px';
+	node.style.width = width + 'px';
+	node.style.height = height + 'px';
+	node.style.visibility = '';
+	node.style.minWidth = '';
+	node.style.minHeight = '';
+
+	parent.appendChild(node);
+
+	// Adds title for sidebar entries
+	if (this.sidebarTitles && title != null) {
+		var border = (mxClient.IS_QUIRKS) ? 2 * this.thumbPadding + 2 : 0;
+		parent.style.height = (this.thumbHeight + border + this.sidebarTitleSize + 8) +
+			'px';
+
+		var div = document.createElement('div');
+		div.style.fontSize = this.sidebarTitleSize + 'px';
+		div.style.textAlign = 'center';
+		div.style.whiteSpace = 'nowrap';
+
+		if (mxClient.IS_IE) {
+			div.style.height = (this.sidebarTitleSize + 12) + 'px';
+		}
+
+		div.style.paddingTop = '4px';
+		mxUtils.write(div, title);
+		parent.appendChild(div);
+	}
+};
+
+/**
+ * Creates and returns a new palette item for the given image.
+ */
+Sidebar.prototype.createItem = function(cells, title, showLabel) {
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geItem';
+	elt.style.overflow = 'hidden';
+	var border = (mxClient.IS_QUIRKS) ? 8 + 2 * this.thumbPadding : 6;
+	elt.style.width = (this.thumbWidth + border) + 'px';
+	elt.style.height = (this.thumbHeight + border) + 'px';
+	elt.style.padding = this.thumbPadding + 'px';
+
+	// Blocks default click action
+	mxEvent.addListener(elt, 'click', function(evt) {
+		mxEvent.consume(evt);
+	});
+
+	this.createThumb(cells, this.thumbWidth, this.thumbHeight, elt, title,
+		showLabel);
+
+	return elt;
+};
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createDropHandler = function(cells, allowSplit) {
+	return function(graph, evt, target, x, y) {
+		if (graph.isEnabled()) {
+			cells = graph.getImportableCells(cells);
+
+			if (cells.length > 0) {
+				var validDropTarget = (target != null) ?
+					graph.isValidDropTarget(target, cells, evt) : false;
+				var select = null;
+
+				if (target != null && !validDropTarget) {
+					target = null;
+				}
+
+				// Splits the target edge or inserts into target group
+				if (allowSplit && graph.isSplitEnabled() && graph.isSplitTarget(target,
+						cells, evt)) {
+					graph.splitEdge(target, cells, null, x, y);
+					select = cells;
+				} else if (cells.length > 0) {
+					select = graph.importCells(cells, x, y, target);
+				}
+
+				if (select != null && select.length > 0) {
+					graph.scrollCellToVisible(select[0]);
+					graph.setSelectionCells(select);
+				}
+			}
+		}
+	};
+};
+
+/**
+ * Creates and returns a preview element for the given width and height.
+ */
+Sidebar.prototype.createDragPreview = function(width, height) {
+	var elt = document.createElement('div');
+	elt.style.border = '1px dashed black';
+	elt.style.width = width + 'px';
+	elt.style.height = height + 'px';
+
+	return elt;
+};
+
+/**
+ * Creates a drag source for the given element.
+ */
+Sidebar.prototype.createDragSource = function(elt, dropHandler, preview) {
+	var dragSource = mxUtils.makeDraggable(elt, this.editorUi.editor.graph,
+		dropHandler,
+		preview, 0, 0, this.editorUi.editor.graph.autoscroll, true, true);
+
+	// Allows drop into cell only if target is a valid root
+	dragSource.getDropTarget = function(graph, x, y) {
+		var target = mxDragSource.prototype.getDropTarget.apply(this, arguments);
+
+		if (!graph.isValidRoot(target)) {
+			target = null;
+		}
+
+		return target;
+	};
+
+	return dragSource;
+};
+
+/**
+ * Adds a handler for inserting the cell with a single click.
+ */
+Sidebar.prototype.addClickHandler = function(elt, ds) {
+	var graph = this.editorUi.editor.graph;
+	var first = null;
+
+	mxEvent.addGestureListeners(elt, function(evt) {
+		first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	});
+
+	var oldMouseUp = ds.mouseUp;
+	ds.mouseUp = function(evt) {
+		if (!mxEvent.isPopupTrigger(evt) && this.currentGraph == null && first !=
+			null) {
+			var tol = graph.tolerance;
+
+			if (Math.abs(first.x - mxEvent.getClientX(evt)) <= tol &&
+				Math.abs(first.y - mxEvent.getClientY(evt)) <= tol) {
+				var gs = graph.getGridSize();
+				ds.drop(graph, evt, null, gs, gs);
+			}
+		}
+
+		oldMouseUp.apply(this, arguments);
+		first = null;
+	};
+};
+
+/**
+ * Giuseppe Careri
+ * Create xml document vertex
+ */
+Sidebar.prototype.createXmlDocument = function(element, label, variables,
+	values) {
+	var doc = mxUtils.createXmlDocument();
+	var node = doc.createElement(element)
+	node.setAttribute('label', label);
+
+	variables.forEach(function(variable, index) {
+		node.setAttribute(variable, values[index] ? values[index] : '');
+	});
+
+	return node;
+}
+
+/**
+ * Giuseppe Careri
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createVertexTemplate = function(style, width, height, value,
+	title, showLabel) {
+	var variables = [];
+	var values = [];
+
+	switch (value) {
+		case 'answer':
+			break;
+		case 'ispeechasr':
+			variables = ['key', 'model', 'ispeech_asr_language'];
+			values = ['', 0, 'it-IT'];
+			break;
+		case 'playback':
+			variables = ['file_id', 'opts'];
+			values = ['0'];
+			break;
+		case 'background':
+			variables = ['file_id', 'response', 'digit'];
+			values = ['0', '5', '1'];
+			break;
+		case 'menu':
+			variables = ['file_id', 'response', 'digit', 'retry', 'variable_id'];
+			values = ['0', '5', '1', '1', '0'];
+			break;
+		case 'getdigits':
+			variables = ['file_id', 'response', 'mindigit', 'maxdigit', 'retry', 'variable_id'];
+			values = ['0', '5', '1', '10', '1', '0'];
+			break;
+		case 'custom_app':
+			variables = ['application', 'options'];
+			values = ['', ''];
+			break;
+		case 'set':
+			variables = ['variable_id', 'variable_value'];
+			values = ['0', ''];
+			break;
+		case 'database':
+			variables = ['odbc_id', 'query', 'variable_id'];
+			values = ['0', '', '0'];
+			break;
+		case 'noop':
+			variables = ['output'];
+			values = [];
+			break;
+		case 'gotoc':
+			variables = ['context', 'extension'];
+			values = [];
+			break;
+		case 'system':
+			variables = ['variable_id', 'command'];
+			values = [];
+			break;
+		case 'agi':
+			variables = ['args', 'command'];
+			values = [];
+			break;
+		case 'gotoif':
+			variables = ['condition'];
+			values = [];
+			break;
+		case 'gotoiftime':
+			variables = ['interval_id'];
+			values = ['0'];
+			break;
+		case 'gotoifmultitime':
+			variables = ['interval_id'];
+			values = ['0'];
+			break;
+		case 'vswitch':
+			variables = ['variable_id'];
+			values = ['0'];
+			break;
+		case 'saydigits':
+			variables = ['digits', 'escape_digits'];
+			values = ['', '#'];
+			break;
+		case 'saynumber':
+			variables = ['number', 'escape_digits'];
+			values = ['', '#'];
+			break;
+		case 'sayphonetic':
+			variables = ['text', 'escape_digits'];
+			values = ['', '#'];
+			break;
+		case 'record':
+			variables = ['file', 'timeout', 'escape_digits'];
+			values = ['', '-1', '#'];
+			break;
+		case 'dial':
+			variables = ['sip_id', 'timeout', 'custom_app', 'url'];
+			values = ['0', '60'];
+			break;
+		case 'ext_dial':
+			variables = ['phone', 'trunk_id', 'timeout', 'opts', 'url'];
+			values = ['', '0', '60'];
+			break;
+		case 'tts':
+			variables = ['text', 'google_tts_language'];
+			values = ['', 'it'];
+			break;
+		case 'ispeechtts':
+			variables = ['text', 'key', 'ispeech_tts_language'];
+			values = ['', '', 'euritalianfemale'];
+			break;
+		case 'queue':
+			variables = ['queue_id', 'opts', 'url', 'file_id', 'timeout', 'agi',
+				'macro', 'gosub', 'position'
+			];
+			values = ['-1', '', '', '0', '300'];
+			break;
+		case 'voicemail':
+			variables = ['mailbox', 'options'];
+			values = ['', ''];
+			break;
+		case 'subproject':
+			variables = ['project_id'];
+			values = ['0'];
+			break;
+		case 'math':
+			variables = ['operation', 'variable_id'];
+			values = ['', '0'];
+			break;
+		case 'queuelog':
+			variables = ['queuename', 'agent', 'queue_event', 'data1', 'data2', 'data3',
+				'data4', 'data5'
+			];
+			values = [];
+			break;
+		case 'goal':
+			variables = ['goalname'];
+			values = [];
+			break;
+		case 'callback':
+			variables = ['list_id'];
+			values = ['0', '{CALLERID(num)}'];
+			break;
+		default:
+			variables = [];
+			values = [];
+			break;
+	}
+
+	var node = this.createXmlDocument(value, title, variables, values);
+
+	//var cells = [new mxCell((value != null) ? value : '', new mxGeometry(0, 0, width, height), style)];
+	var cells = [new mxCell(node, new mxGeometry(0, 0, width, height), style)];
+	cells[0].vertex = true;
+
+	return this.createVertexTemplateFromCells(cells, width, height, title,
+		showLabel);
+};
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createVertexTemplateFromCells = function(cells, width, height,
+	title, showLabel) {
+	var elt = this.createItem(cells, title, showLabel);
+	var ds = this.createDragSource(elt, this.createDropHandler(cells, true), this
+		.createDragPreview(width, height));
+	this.addClickHandler(elt, ds);
+
+	// Uses guides for vertices only if enabled in graph
+	ds.isGuidesEnabled = mxUtils.bind(this, function() {
+		return this.editorUi.editor.graph.graphHandler.guidesEnabled;
+	});
+
+	// Shows a tooltip with the rendered cell
+	if (!touchStyle) {
+		mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt) {
+			this.showTooltip(elt, cells, title, showLabel);
+		}));
+	}
+
+	return elt;
+};
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createEdgeTemplate = function(style, width, height, value,
+	title, showLabel) {
+	var cells = [new mxCell((value != null) ? value : '', new mxGeometry(0, 0,
+		width, height), style)];
+	cells[0].geometry.setTerminalPoint(new mxPoint(0, height), true);
+	cells[0].geometry.setTerminalPoint(new mxPoint(width, 0), false);
+	cells[0].geometry.relative = true;
+	cells[0].edge = true;
+
+	return this.createEdgeTemplateFromCells(cells, width, height, title,
+		showLabel);
+};
+
+/**
+ * Creates a drop handler for inserting the given cells.
+ */
+Sidebar.prototype.createEdgeTemplateFromCells = function(cells, width, height,
+	title, showLabel) {
+	var elt = this.createItem(cells, title, showLabel);
+	this.createDragSource(elt, this.createDropHandler(cells, false), this.createDragPreview(
+		width, height));
+
+	// Installs the default edge
+	var graph = this.editorUi.editor.graph;
+	mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt) {
+		if (this.installEdges) {
+			graph.setDefaultEdge(cells[0]);
+		}
+
+		// Highlights the entry for 200ms
+		elt.style.backgroundColor = '#ffffff';
+
+		window.setTimeout(function() {
+			elt.style.backgroundColor = '';
+		}, 300);
+
+		mxEvent.consume(evt);
+	}));
+
+	// Shows a tooltip with the rendered cell
+	if (!touchStyle) {
+		mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt) {
+			this.showTooltip(elt, cells, title, showLabel);
+		}));
+	}
+
+	return elt;
+};
+
+/**
+ * Adds the given palette.
+ */
+Sidebar.prototype.addPalette = function(id, title, expanded, onInit) {
+	var elt = this.createTitle(title);
+	this.container.appendChild(elt);
+
+	var div = document.createElement('div');
+	div.className = 'geSidebar';
+
+	if (expanded) {
+		onInit(div);
+		onInit = null;
+	} else {
+		div.style.display = 'none';
+	}
+
+	this.addFoldingHandler(elt, div, onInit);
+
+	var outer = document.createElement('div');
+	outer.appendChild(div);
+	this.container.appendChild(outer);
+
+	// Keeps references to the DOM nodes
+	if (id != null) {
+		this.palettes[id] = [elt, outer];
+	}
+};
+
+/**
+ * Create the given title element.
+ */
+Sidebar.prototype.addFoldingHandler = function(title, content, funct) {
+	var initialized = false;
+
+	title.style.backgroundImage = (content.style.display == 'none') ?
+		'url(' + IMAGE_PATH + '/collapsed.gif)' : 'url(' + IMAGE_PATH +
+		'/expanded.gif)';
+	title.style.backgroundRepeat = 'no-repeat';
+	title.style.backgroundPosition = '100% 50%';
+
+	mxEvent.addListener(title, 'click', function(evt) {
+		if (content.style.display == 'none') {
+			if (!initialized) {
+				initialized = true;
+
+				if (funct != null) {
+					title.style.cursor = 'wait';
+					window.setTimeout(function() {
+						funct(content);
+						title.style.cursor = '';
+					}, 0);
+				}
+			}
+
+			title.style.backgroundImage = 'url(' + IMAGE_PATH + '/expanded.gif)';
+			content.style.display = 'block';
+		} else {
+			title.style.backgroundImage = 'url(' + IMAGE_PATH + '/collapsed.gif)';
+			content.style.display = 'none';
+		}
+
+		mxEvent.consume(evt);
+	});
+};
+
+/**
+ * Removes the palette for the given ID.
+ */
+Sidebar.prototype.removePalette = function(id) {
+	var elts = this.palettes[id];
+
+	if (elts != null) {
+		this.palettes[id] = null;
+
+		for (var i = 0; i < elts.length; i++) {
+			this.container.removeChild(elts[i]);
+		}
+
+		return true;
+	}
+
+	return false;
+};
+
+/**
+ * Adds the given image palette.
+ */
+Sidebar.prototype.addImagePalette = function(id, title, prefix, postfix, items,
+	titles) {
+	// Giuseppe Careri
+	// Expanded Sidebar only entry
+	this.addPalette(id, title, (id === 'entry'), mxUtils.bind(this, function(
+		content) {
+		var showTitles = titles != null;
+
+		for (var i = 0; i < items.length; i++) {
+			var icon = prefix + items[i] + postfix;
+			content.appendChild(this.createVertexTemplate('image;image=' + icon, 80,
+				80, items[i], (showTitles) ? titles[i] : null, showTitles));
+		}
+	}));
+};
diff --git a/public/assets/plugins/square/js/Toolbar.js b/public/assets/plugins/square/js/Toolbar.js
index 91ab96d..7eab562 100644
--- a/public/assets/plugins/square/js/Toolbar.js
+++ b/public/assets/plugins/square/js/Toolbar.js
@@ -1 +1,407 @@
\ No newline at end of file
+/**
+ * $Id: Toolbar.js,v 1.3 2013-02-20 16:21:29 gaudenz Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Construcs a new toolbar for the given editor.
+ */
+function Toolbar(editorUi, container)
+{
+	this.editorUi = editorUi;
+	this.container = container;
+	this.init();
+
+	// Global handler to hide the current menu
+	mxEvent.addGestureListeners(document, mxUtils.bind(this, function(evt)
+	{
+		this.hideMenu();
+	}));
+};
+
+/**
+ * Adds the toolbar elements.
+ */
+Toolbar.prototype.init = function()
+{
+	this.addItems(['save', 'publish', '-', 'print', 'undo', 'redo', '-', 'copy', 'cut', 'paste', 'delete', '-', 'duplicate', '-', 'actualSize', 'zoomIn', 'zoomOut', '-']);
+	var fontElt = this.addMenu('Helvetica', mxResources.get('fontFamily'), true, 'fontFamily');
+	fontElt.style.whiteSpace = 'nowrap';
+	fontElt.style.overflow = 'hidden';
+	fontElt.style.width = '70px';
+	this.addSeparator();
+	var sizeElt = this.addMenu('12', mxResources.get('fontSize'), true, 'fontSize');
+	sizeElt.style.whiteSpace = 'nowrap';
+	sizeElt.style.overflow = 'hidden';
+	sizeElt.style.width = '30px';
+
+	this.addItems(['-', 'bold', 'italic', 'underline']);
+	// Giuseppe Careri
+	// var align = this.addMenuFunction('geSprite-left', mxResources.get('align'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_LEFT], 'geIcon geSprite geSprite-left', null).setAttribute('title', mxResources.get('left'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_CENTER], 'geIcon geSprite geSprite-center', null).setAttribute('title', mxResources.get('center'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_RIGHT], 'geIcon geSprite geSprite-right', null).setAttribute('title', mxResources.get('right'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_TOP], 'geIcon geSprite geSprite-top', null).setAttribute('title', mxResources.get('top'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_MIDDLE], 'geIcon geSprite geSprite-middle', null).setAttribute('title', mxResources.get('middle'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_BOTTOM], 'geIcon geSprite geSprite-bottom', null).setAttribute('title', mxResources.get('bottom'));
+	// }));
+	// this.addItems(['fontColor', '-']);
+	// var line = this.addMenuFunction('geSprite-straight', mxResources.get('line'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], [null], 'geIcon geSprite geSprite-straight', null).setAttribute('title', mxResources.get('straight'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], ['entityRelationEdgeStyle'], 'geIcon geSprite geSprite-entity', null).setAttribute('title', mxResources.get('entityRelation'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW], ['elbowEdgeStyle', 'horizontal'], 'geIcon geSprite geSprite-helbow', null).setAttribute('title', mxResources.get('horizontal'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW], ['elbowEdgeStyle', 'vertical'], 'geIcon geSprite geSprite-velbow', null).setAttribute('title', mxResources.get('vertical'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], ['segmentEdgeStyle'], 'geIcon geSprite geSprite-segment', null).setAttribute('title', mxResources.get('manual'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_EDGE], ['orthogonalEdgeStyle'], 'geIcon geSprite geSprite-orthogonal', null).setAttribute('title', mxResources.get('automatic'));
+	// }));
+	// var linestart = this.addMenuFunction('geSprite-startclassic', mxResources.get('lineend'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.NONE, 0], 'geIcon geSprite geSprite-noarrow', null).setAttribute('title', mxResources.get('none'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC, 1], 'geIcon geSprite geSprite-startclassic', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OPEN, 1], 'geIcon geSprite geSprite-startopen', null).setAttribute('title', mxResources.get('openArrow'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK, 1], 'geIcon geSprite geSprite-startblock', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OVAL, 1], 'geIcon geSprite geSprite-startoval', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND, 1], 'geIcon geSprite geSprite-startdiamond', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND_THIN, 1], 'geIcon geSprite geSprite-startthindiamond', null).setAttribute('title', mxResources.get('diamondThin'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC, 0], 'geIcon geSprite geSprite-startclassictrans', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK, 0], 'geIcon geSprite geSprite-startblocktrans', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OVAL, 0], 'geIcon geSprite geSprite-startovaltrans', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND, 0], 'geIcon geSprite geSprite-startdiamondtrans', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND_THIN, 0], 'geIcon geSprite geSprite-startthindiamondtrans', null).setAttribute('title', mxResources.get('diamondThin'));
+	// }));
+	// var lineend = this.addMenuFunction('geSprite-endclassic', mxResources.get('lineend'), false, mxUtils.bind(this, function(menu)
+	// {
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.NONE, 0], 'geIcon geSprite geSprite-noarrow', null).setAttribute('title', mxResources.get('none'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC, 1], 'geIcon geSprite geSprite-endclassic', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OPEN, 1], 'geIcon geSprite geSprite-endopen', null).setAttribute('title', mxResources.get('openArrow'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK, 1], 'geIcon geSprite geSprite-endblock', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OVAL, 1], 'geIcon geSprite geSprite-endoval', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND, 1], 'geIcon geSprite geSprite-enddiamond', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND_THIN, 1], 'geIcon geSprite geSprite-endthindiamond', null).setAttribute('title', mxResources.get('diamondThin'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC, 0], 'geIcon geSprite geSprite-endclassictrans', null).setAttribute('title', mxResources.get('classic'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK, 0], 'geIcon geSprite geSprite-endblocktrans', null).setAttribute('title', mxResources.get('block'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OVAL, 0], 'geIcon geSprite geSprite-endovaltrans', null).setAttribute('title', mxResources.get('oval'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND, 0], 'geIcon geSprite geSprite-enddiamondtrans', null).setAttribute('title', mxResources.get('diamond'));
+	// 	this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND_THIN, 0], 'geIcon geSprite geSprite-endthindiamondtrans', null).setAttribute('title', mxResources.get('diamondThin'));
+	// }));
+	// this.addItems(['-', 'strokeColor', 'image', 'fillColor']);
+	// this.addItem('geSprite-gradientcolor', 'gradientColor').setAttribute('title', mxResources.get('gradient'));
+	// this.addItems(['shadow']);
+
+	var graph = this.editorUi.editor.graph;
+
+	// Update font size and font family labels
+	var update = mxUtils.bind(this, function()
+	{
+		var ff = 'Helvetica';
+		var fs = '12';
+    	var state = graph.getView().getState(graph.getSelectionCell());
+
+    	if (state != null)
+    	{
+    		ff = state.style[mxConstants.STYLE_FONTFAMILY] || ff;
+    		fs = state.style[mxConstants.STYLE_FONTSIZE] || fs;
+
+    		if (ff.length > 10)
+    		{
+    			ff = ff.substring(0, 8) + '...';
+    		}
+
+    		fontElt.innerHTML = ff;
+    		sizeElt.innerHTML = fs;
+    	}
+	});
+
+    graph.getSelectionModel().addListener(mxEvent.CHANGE, update);
+    graph.getModel().addListener(mxEvent.CHANGE, update);
+
+	// Giuseppe Careri
+	// Updates button states
+  //this.addEdgeSelectionHandler([line, linestart, lineend]);
+	//this.addSelectionHandler([align]);
+};
+
+/**
+ * Hides the current menu.
+ */
+Toolbar.prototype.hideMenu = function()
+{
+	if (this.currentMenu != null)
+	{
+		this.currentMenu.hideMenu();
+		this.currentMenu.destroy();
+		this.currentMenu = null;
+	}
+};
+
+/**
+ * Adds a label to the toolbar.
+ */
+Toolbar.prototype.addMenu = function(label, tooltip, showLabels, name)
+{
+	var menu = this.editorUi.menus.get(name);
+	var elt = this.addMenuFunction(label, tooltip, showLabels, menu.funct);
+
+	menu.addListener('stateChanged', function()
+	{
+		elt.setEnabled(menu.enabled);
+	});
+
+	return elt;
+};
+
+/**
+ * Adds a label to the toolbar.
+ */
+Toolbar.prototype.addMenuFunction = function(label, tooltip, showLabels, funct)
+{
+	var elt = (showLabels) ? this.createLabel(label) : this.createButton(label);
+	this.initElement(elt, tooltip);
+	this.addMenuHandler(elt, showLabels, funct);
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Adds a separator to the separator.
+ */
+Toolbar.prototype.addSeparator = function()
+{
+	var elt = document.createElement('div');
+	elt.className = 'geSeparator';
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Adds given action item
+ */
+Toolbar.prototype.addItems = function(keys)
+{
+	for (var i = 0; i < keys.length; i++)
+	{
+		var key = keys[i];
+
+		if (key == '-')
+		{
+			this.addSeparator();
+		}
+		else
+		{
+			this.addItem('geSprite-' + key.toLowerCase(), key);
+		}
+	}
+};
+
+/**
+ * Adds given action item
+ */
+Toolbar.prototype.addItem = function(sprite, key)
+{
+	var action = this.editorUi.actions.get(key);
+	var elt = null;
+
+	if (action != null)
+	{
+		elt = this.addButton(sprite, action.label, action.funct);
+		elt.setEnabled(action.enabled);
+
+		action.addListener('stateChanged', function()
+		{
+			elt.setEnabled(action.enabled);
+		});
+	}
+
+	return elt;
+};
+
+/**
+ * Adds a button to the toolbar.
+ */
+Toolbar.prototype.addButton = function(classname, tooltip, funct)
+{
+	var elt = this.createButton(classname);
+
+	this.initElement(elt, tooltip);
+	this.addClickHandler(elt, funct);
+	this.container.appendChild(elt);
+
+	return elt;
+};
+
+/**
+ * Updates the states of the given toolbar items based on the selection.
+ */
+Toolbar.prototype.addSelectionHandler = function(items)
+{
+	var graph = this.editorUi.editor.graph;
+
+	var selectionListener = function()
+    {
+    	var selected = !graph.isSelectionEmpty();
+
+    	for (var i = 0; i < items.length; i++)
+    	{
+    		items[i].setEnabled(selected);
+    	}
+    };
+
+    graph.getSelectionModel().addListener(mxEvent.CHANGE, selectionListener);
+    selectionListener();
+};
+
+/**
+ * Updates the states of the given toolbar items based on the selection.
+ */
+Toolbar.prototype.addEdgeSelectionHandler = function(items)
+{
+	var graph = this.editorUi.editor.graph;
+
+	var selectionListener = function()
+    {
+		var edgeSelected = false;
+
+		if (!graph.isSelectionEmpty())
+		{
+			var cells = graph.getSelectionCells();
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (graph.getModel().isEdge(cells[i]))
+				{
+					edgeSelected = true;
+					break;
+				}
+			}
+		}
+
+    	for (var i = 0; i < items.length; i++)
+    	{
+    		items[i].setEnabled(edgeSelected);
+    	}
+    };
+
+    graph.getSelectionModel().addListener(mxEvent.CHANGE, selectionListener);
+    selectionListener();
+};
+
+/**
+ * Initializes the given toolbar element.
+ */
+Toolbar.prototype.initElement = function(elt, tooltip)
+{
+	elt.setAttribute('tabindex', '0');
+
+	// Adds tooltip
+	if (tooltip != null)
+	{
+		elt.setAttribute('title', tooltip);
+	}
+
+	this.addEnabledState(elt);
+};
+
+/**
+ * Adds enabled state with setter to DOM node (avoids JS wrapper).
+ */
+Toolbar.prototype.addEnabledState = function(elt)
+{
+	var classname = elt.className;
+
+	elt.setEnabled = function(value)
+	{
+		elt.enabled = value;
+
+		if (value)
+		{
+			elt.className = classname;
+		}
+		else
+		{
+			elt.className = classname + ' geDisabled';
+		}
+	};
+
+	elt.setEnabled(true);
+};
+
+/**
+ * Adds enabled state with setter to DOM node (avoids JS wrapper).
+ */
+Toolbar.prototype.addClickHandler = function(elt, funct)
+{
+	if (funct != null)
+	{
+		mxEvent.addListener(elt, 'click', function(evt)
+		{
+			if (elt.enabled)
+			{
+				funct(evt);
+			}
+
+			mxEvent.consume(evt);
+		});
+	}
+};
+
+/**
+ * Creates and returns a new button.
+ */
+Toolbar.prototype.createButton = function(classname)
+{
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geButton';
+
+	var inner = document.createElement('div');
+	inner.className = 'geSprite ' + classname;
+	elt.appendChild(inner);
+
+	return elt;
+};
+
+/**
+ * Creates and returns a new button.
+ */
+Toolbar.prototype.createLabel = function(label, tooltip)
+{
+	var elt = document.createElement('a');
+	elt.setAttribute('href', 'javascript:void(0);');
+	elt.className = 'geLabel';
+	mxUtils.write(elt, label);
+
+	return elt;
+};
+
+/**
+ * Adds a handler for showing a menu in the given element.
+ */
+Toolbar.prototype.addMenuHandler = function(elt, showLabels, funct, showAll)
+{
+	if (funct != null)
+	{
+		var graph = this.editorUi.editor.graph;
+		var menu = null;
+
+		mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)
+		{
+			if (elt.enabled == null || elt.enabled)
+			{
+				graph.panningHandler.hideMenu();
+				menu = new mxPopupMenu(funct);
+				menu.div.className += ' geToolbarMenu';
+				menu.showDisabled = showAll;
+				menu.labels = showLabels;
+				menu.autoExpand = true;
+
+				menu.popup(elt.offsetLeft, elt.offsetTop + elt.offsetHeight + 34, null, evt);
+				this.currentMenu = menu;
+			}
+
+			mxEvent.consume(evt);
+		}));
+	}
+};
diff --git a/public/assets/plugins/square/js/custom.js b/public/assets/plugins/square/js/custom.js
index 51b02d8..39607c6 100644
--- a/public/assets/plugins/square/js/custom.js
+++ b/public/assets/plugins/square/js/custom.js
@@ -1 +1,283 @@
\ No newline at end of file
+var urlParams = (function(url) {
+	var result = new Object();
+	var idx = url.lastIndexOf('?');
+
+	if (idx > 0) {
+		var params = url.substring(idx + 1).split('&');
+
+		for (var i = 0; i < params.length; i++) {
+			idx = params[i].indexOf('=');
+
+			if (idx > 0) {
+				result[params[i].substring(0, idx)] = params[i].substring(idx + 1);
+			}
+		}
+	}
+
+	return result;
+})(window.location.href);
+
+var t0 = new Date();
+
+// Public global variables
+var MAX_REQUEST_SIZE = 10485760;
+var MAX_WIDTH = 12000;
+var MAX_HEIGHT = 12000;
+
+// CUSTOM_PARAMETERS - URLs for save and export
+var EXPORT_URL = 'http://exp.draw.io/ImageExport2/export';
+var SHARE_HOST = 'http://localhost:3000';
+
+var SAVE_URL = 'api/square/projects/';
+var VARIABLE_URL = '/api/variables';
+
+// Paths and files
+var STENCIL_PATH = '/assets/plugins/square/stencils';
+var SHAPES_PATH = '/assets/plugins/square/shapes';
+var IMAGE_PATH = '/assets/plugins/mxgraph/images';
+// Path for images inside the diagram
+//var GRAPH_IMAGE_PATH = 'assets/plugins/mxgraph/img';
+//var ICONFINDER_PATH = 'iconfinder';
+var STYLE_PATH = '/assets/plugins/mxgraph/styles';
+var CSS_PATH = '/assets/plugins/mxgraph/styles';
+var OPEN_FORM = 'api/square/projects/open';
+var TEMPLATE_PATH = '/assets/plugins/square/templates';
+
+// Directory for i18 files and basename for main i18n file
+var RESOURCES_PATH = '/assets/plugins/square/resources';
+var RESOURCE_BASE = RESOURCES_PATH + '/dia';
+
+// Specifies connection mode for touch devices (at least one should be true)
+var tapAndHoldStartsConnection = true;
+var showConnectorImg = true;
+
+var mxLoadResources = false;
+// var mxLanguage = 'it';
+console.log('custom', localStorage.getItem("NG_TRANSLATE_LANG_KEY"));
+switch (localStorage.getItem("NG_TRANSLATE_LANG_KEY")) {
+	case 'en_EN':
+		mxLanguage = 'en';
+		break;
+	case 'it':
+		mxLanguage = 'it';
+		break;
+	default:
+		mxLanguage = 'en';
+}
+console.log('mxLanguage', mxLanguage);
+
+var mxLanguages = ['ar', 'bs', 'cs', 'da', 'de', 'el', 'es', 'es-ar', 'fr',
+	'hu', 'id', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt-br', 'pt', 'ro', 'ru',
+	'sr', 'sv', 'th', 'tr', 'uk', 'zh'
+];
+var mxBasePath = 'assets/plugins/mxgraph';
+var geBasePath = 'assets/plugins/mxgraph/js';
+
+var ISPEECHLANG = {
+	usenglishfemale: "US English Female",
+	usenglishmale: "US English Male",
+	ukenglishfemale: "UK English Female",
+	ukenglishmale: "UK English Male",
+	auenglishfemale: "Australian English",
+	usspanishfemale: "US Spanish Female",
+	usspanishmale: "US Spanish Male",
+	chchinesefemale: "Chinese Female",
+	chchinesemale: "Chinese Male",
+	hkchinesefemale: "Hong Kong Cantonese Female",
+	twchinesefemale: "Taiwan Chinese Female",
+	jpjapanesefemale: "Japanese Female",
+	jpjapanesemale: "Japanese Male",
+	krkoreanfemale: "Korean Female",
+	krkoreanmale: "Korean Male",
+	caenglishfemale: "Canadian English Female",
+	huhungarianfemale: "Hungarian Female",
+	brportuguesefemale: "Brazilian Portuguese Female",
+	eurportuguesefemale: "European Portuguese Female",
+	eurportuguesemale: "European Portuguese Male",
+	eurspanishfemale: "European Spanish Female",
+	eurspanishmale: "European Spanish Male",
+	eurcatalanfemale: "European Catalan Female",
+	eurczechfemale: "European Czech Female",
+	eurdanishfemale: "European Danish Female",
+	eurfinnishfemale: "European Finnish Female",
+	eurfrenchfemale: "European French Female",
+	eurfrenchmale: "European French Male",
+	eurnorwegianfemale: "European Norwegian Female",
+	eurdutchfemale: "European Dutch Female",
+	eurpolishfemale: "European Polish Female",
+	euritalianfemale: "European Italian Female",
+	eurturkishfemale: "European Turkish Female	",
+	eurturkishmale: "European Turkish Male",
+	eurgermanfemale: "European German Female",
+	eurgermanmale: "European German Male",
+	rurussianfemale: "Russian Female",
+	rurussianmale: "Russian Male",
+	swswedishfemale: "Swedish Female",
+	cafrenchfemale: "Canadian French Female",
+	cafrenchmale: "Canadian French Male"
+};
+
+var ISPEECHASRLANG = {
+	"en-US": "English (US)",
+	"en-CA": "English (Canada)",
+	"en-GB": "English (UK)",
+	"en-AU": "English (Australia)",
+	"es-ES": "Spanish (Spain)",
+	"es-MX": "Spanish (Mexico)",
+	"it-IT": "Italian (Italy) / Finnish (Finland)",
+	"fr-FR": "French (France)",
+	"fr-CA": "French (Canada)",
+	"pl-PL": "Polish (Poland)",
+	"pt-PT": "Portuguese (Portugal)",
+	"ca-ES": "Catalan (Catalan)",
+	"zh-TW": "Chinese (Taiwan)",
+	"da-DK": "Danish (Denmark)",
+	"fr-FR": "German (Germany)",
+	"ja-JP": "Japanese (Japan)",
+	"ko-KR": "Korean (Korea)",
+	"nl-NL": "Dutch (Netherlands)",
+	"nb-NO": "Norwegian (Norway)",
+	"pt-BR": "Portuguese (Brazil)",
+	"ru-RU": "Russian (Russia)",
+	"sv-SE": "Swedish (Sweden)",
+	"zh-CN": "Chinese (China)",
+	"zh-HK": "Chinese (Hong Kong)"
+};
+
+var ISPEECHASRMODEL = {
+	0: "Default",
+	date: "Date",
+	usmoney: "US Money",
+	numbersto9: "Numbers to 9",
+	numbersto99: "Numbers to 99",
+	numbersto999: "Numbers to 999",
+	time: "Time",
+	phonenumber: "Phone number",
+	streets: "Streets",
+	citystate: "US City/States"
+};
+
+var GOOGLETTSLANG = {
+	"af": "Afrikaans",
+	"sq": "Albanian",
+	"am": "Amharic",
+	"ar": "Arabic",
+	"hy": "Armenian",
+	"az": "Azerbaijani",
+	"eu": "Basque",
+	"be": "Belarusian",
+	"bn": "Bengali",
+	"bh": "Bihari",
+	"bs": "Bosnian",
+	"br": "Breton",
+	"bg": "Bulgarian",
+	"km": "Cambodian",
+	"ca": "Catalan",
+	"zh-CN": "Chinese (Simplified)",
+	"zh-TW": "Chinese (Traditional)",
+	"co": "Corsican",
+	"hr": "Croatian",
+	"cs": "Czech",
+	"da": "Danish",
+	"nl": "Dutch",
+	"en": "English",
+	"eo": "Esperanto",
+	"et": "Estonian",
+	"fo": "Faroese",
+	"tl": "Filipino",
+	"fi": "Finnish",
+	"fr": "French",
+	"fy": "Frisian",
+	"gl": "Galician",
+	"ka": "Georgian",
+	"de": "German",
+	"el": "Greek",
+	"gn": "Guarani",
+	"gu": "Gujarati",
+	"xx-hacker": "Hacker",
+	"ha": "Hausa",
+	"iw": "Hebrew",
+	"hi": "Hindi",
+	"hu": "Hungarian",
+	"is": "Icelandic",
+	"id": "Indonesian",
+	"ia": "Interlingua",
+	"ga": "Irish",
+	"it": "Italian",
+	"jw": "Javanese",
+	"ja": "Japanese",
+	"kn": "Kannada",
+	"kk": "Kazakh",
+	"rw": "Kinyarwanda",
+	"rn": "Kirundi",
+	"xx-klingon": "Klingon",
+	"ku": "Kurdish",
+	"ko": "Korean",
+	"ky": "Kyrgyz",
+	"lo": "Laothian",
+	"la": "Latin",
+	"lv": "Latvian",
+	"ln": "Lingala",
+	"lt": "Lithuanian",
+	"mk": "Macedonian",
+	"mg": "Malagasy",
+	"ms": "Malay",
+	"ml": "Malayalam",
+	"mt": "Maltese",
+	"mi": "Maori",
+	"mr": "Marathi",
+	"mo": "Moldavian",
+	"mn": "Mongolian",
+	"sr-ME": "Montenegrin",
+	"ne": "Nepali",
+	"no": "Norwegian",
+	"nn": "Norwegian (Nynorsk)",
+	"oc": "Occitan",
+	"or": "Oriya",
+	"om": "Oromo",
+	"ps": "Pashto",
+	"fa": "Persian",
+	"xx-pirate": "Pirate",
+	"pl": "Polish",
+	"pt-BR": "Portuguese (Brazil)",
+	"pt-PT": "Portuguese (Portugal)",
+	"pa": "Punjabi",
+	"qu": "Quechua",
+	"ro": "Romanian",
+	"rm": "Romansh",
+	"ru": "Russian",
+	"gd": "Scots Gaelic",
+	"sr": "Serbian",
+	"sh": "Serbo-Croatian",
+	"st": "Sesotho",
+	"sn": "Shona",
+	"sd": "Sindhi",
+	"si": "Sinhalese",
+	"sk": "Slovak",
+	"sl": "Slovenian",
+	"so": "Somali",
+	"es": "Spanish",
+	"su": "Sundanese",
+	"sw": "Swahili",
+	"sv": "Swedish",
+	"tg": "Tajik",
+	"ta": "Tamil",
+	"tt": "Tatar",
+	"te": "Telugu",
+	"th": "Thai",
+	"ti": "Tigrinya",
+	"to": "Tonga",
+	"tr": "Turkish",
+	"tk": "Turkmen",
+	"tw": "Twi",
+	"ug": "Uighur",
+	"uk": "Ukrainian",
+	"ur": "Urdu",
+	"uz": "Uzbek",
+	"vi": "Vietnamese",
+	"cy": "Welsh",
+	"xh": "Xhosa",
+	"yi": "Yiddish",
+	"yo": "Yoruba",
+	"zu": "Zulu"
+};
diff --git a/server/api/action/action.controller.js b/server/api/action/action.controller.js
index 172b49f..1090bff 100644
--- a/server/api/action/action.controller.js
+++ b/server/api/action/action.controller.js
@@ -1 +1,113 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/actions              ->  index
+ * POST    /api/actions              ->  create
+ * GET     /api/actions/:id          ->  show
+ * PUT     /api/actions/:id          ->  update
+ * DELETE  /api/actions/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var Action = require('../../models').Action;
+var sequelize = require('../../models').sequelize;
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Actions
+exports.index = function(req, res) {
+  Action.findAll()
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single Action from the DB
+exports.show = function(req, res) {
+  // Action.findById(req.params.id)
+  //   .then(handleEntityNotFound(res))
+  //   .then(responseWithResult(res))
+  //   .catch(handleError(res));
+}
+
+// Creates a new Action in the DB
+exports.create = function(req, res, next) {
+  req.body.UserId = req.user.id;
+  return sequelize.transaction().then(function(t) {
+    return Action
+      .create(_.merge(req.body, req.params), {
+        transaction: t,
+        body: req.body
+      }).then(function(action) {
+        t.commit();
+        return res.status(201).send(action);
+      }).catch(function(err) {
+        t.rollback();
+        console.log(err);
+        return handleError(res, err);
+      });
+  });
+}
+
+// Updates an existing Action in the DB
+exports.update = function(req, res) {
+  // if (req.body.id) {
+  //   delete req.body.id;
+  // }
+  // Action.findById(req.params.id)
+  //   .then(handleEntityNotFound(res))
+  //   .then(saveUpdates(req.body))
+  //   .then(responseWithResult(res))
+  //   .catch(handleError(res));
+}
+
+// Deletes a Action from the DB
+exports.destroy = function(req, res) {
+  // Action.findById(req.params.id)
+  //   .then(handleEntityNotFound(res))
+  //   .then(removeEntity(res))
+  //   .catch(handleError(res));
+}
diff --git a/server/api/action/action.socket.js b/server/api/action/action.socket.js
index 738937e..36c2faa 100644
--- a/server/api/action/action.socket.js
+++ b/server/api/action/action.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Action = require('../../models').Action;
+
+exports.register = function (socket) {
+  Action.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  Action.afterUpdate(function (doc) {
+    onSave(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('Action:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('Action:remove', doc);
+}
diff --git a/server/api/action/index.js b/server/api/action/index.js
index 8c29829..8a3909e 100644
--- a/server/api/action/index.js
+++ b/server/api/action/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+
+var auth = require('../../auth/auth.service');
+var controller = require('./action.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+// router.get('/:id', auth.isAuthenticated(), controller.show);
+// router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:name', auth.isAuthenticated(), controller.create);
+// router.put('/:id', auth.isAuthenticated(), controller.update);
+// router.patch('/:id', auth.isAuthenticated(), controller.update);
+// router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/agent/agent.controller.js b/server/api/agent/agent.controller.js
index ae71220..e5aa7d5 100644
--- a/server/api/agent/agent.controller.js
+++ b/server/api/agent/agent.controller.js
@@ -1 +1,152 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var sequelize = require('../../models').sequelize;
+
+var Util = require('../../config/util');
+
+var Agent = require('../../models').User;
+var Team = require('../../models').Team;
+var MailRoom = require('../../models').MailRoom;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return Agent
+    .scope('agent', {
+        method: ['checkPauseStatus', req.query]
+      }, {
+        method: ['checkOnlineStatus', req.query]
+      }, {
+        method: ['checkSipStatus', req.query]
+      },
+      'queues')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      return res.status(200).send(result);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Get a single agent
+exports.show = function(req, res) {
+  return Agent
+    .scope('agent')
+    .findById(req.params.id)
+    .then(function(agent) {
+      if (!agent) {
+        return res.sendStatus(404);
+      }
+      return res.send(agent);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new agent in the DB.
+exports.create = function(req, res) {
+  var _agent;
+
+  return Agent
+    .scope('agent')
+    .create(req.body)
+    .then(function(agent) {
+      _agent = agent;
+
+      return Team
+        .findOne({
+          where: {
+            defaultEntry: true
+          }
+        });
+    })
+    .then(function(team) {
+      return team
+        .addUser(_agent.id);
+    })
+    .then(function() {
+      return res.status(201).send(_agent);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing agent in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return Agent
+    .findById(req.params.id)
+    .then(function(agent) {
+      if (!agent) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(agent, req.body);
+      return updated
+        .save();
+    })
+    .then(function(agent) {
+      return res.status(200).send(agent);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.destroy = function(req, res) {
+  var _agent;
+
+  return Agent
+    .findById(req.params.id)
+    .then(function(agent) {
+      _agent = agent;
+      if (!agent) {
+        return res.sendStatus(404);
+      }
+      return agent
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(sequelize.ForeignKeyConstraintError, function(err) {
+      return Util.errorManager.handleForeignKeyConstraintError(res, 'agent', _agent.name, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return Agent
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/agent/agent.socket.js b/server/api/agent/agent.socket.js
index 223f027..5535cb7 100644
--- a/server/api/agent/agent.socket.js
+++ b/server/api/agent/agent.socket.js
@@ -1 +1,33 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var agent = require('../../models').User;
+
+exports.register = function(socket) {
+  agent.afterCreate(function(doc) {
+    if (doc.role === 'agent') {
+      onSave(socket, doc);
+    }
+  });
+  agent.afterUpdate(function(doc) {
+    if (doc.role === 'agent') {
+      onSave(socket, doc);
+    }
+  });
+  agent.afterDestroy(function(doc) {
+    if (doc.role === 'agent') {
+      onRemove(socket, doc);
+    }
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('agent:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('agent:remove', doc);
+}
diff --git a/server/api/agent/index.js b/server/api/agent/index.js
index a3e6327..ebfd805 100644
--- a/server/api/agent/index.js
+++ b/server/api/agent/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./agent.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/ami/ami.ami.js b/server/api/ami/ami.ami.js
index 39048f1..2684197 100644
--- a/server/api/ami/ami.ami.js
+++ b/server/api/ami/ami.ami.js
@@ -1 +1,37 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Ami = require('../../models').Ami;
+var async = require('async');
+var _ = require('lodash');
+
+var ami = null;
+
+exports.register = function(ami) {
+  this.ami = ami;
+}
+
+exports.action = function(action, cb) {
+  if (this.ami && this.ami.isConnected()) {
+    this.ami.action(action, function(err, res) {
+      if (err) {
+        cb(err);
+      }
+      var data = _.merge(res, action);
+      Ami
+        .create(data)
+        .then(function(ami) {
+          cb(null, ami)
+        })
+        .catch(function(err) {
+          return cb(err);
+        });
+
+    });
+  } else {
+    cb(new Error('Asterisk Manager disconnected..'));
+  }
+}
diff --git a/server/api/ami/ami.controller.js b/server/api/ami/ami.controller.js
index dbb160a..dc409ab 100644
--- a/server/api/ami/ami.controller.js
+++ b/server/api/ami/ami.controller.js
@@ -1 +1,101 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Ami = require('../../models').Ami;
+
+// Get list of amis
+exports.index = function(req, res) {
+  Ami
+    .findAll()
+    .then(function(amis) {
+      return res.status(200).send(amis);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single ami
+exports.show = function(req, res) {
+  Ami
+    .findById(req.params.id)
+    .then(function(ami) {
+      if (!ami) {
+        return res.sendStatus(404);
+      }
+      return res.send(ami);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new ami in the DB.
+exports.create = function(req, res) {
+
+  req.body.data = JSON.stringify(req.body);
+  req.body.UserId = req.user.id;
+
+  require('./ami.ami').action(req.body, function(err, result) {
+    if (err) {
+      handleError(res, err);
+    } else {
+      return res.status(201).send(result);
+    }
+  });
+
+};
+
+// Updates an existing ami in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  Ami
+    .findById(req.params.id)
+    .then(function(ami) {
+      if (!ami) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(ami, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(ami);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a ami from the DB.
+exports.destroy = function(req, res) {
+  Ami
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(ami) {
+      if (!ami) {
+        return res.sendStatus(404);
+      }
+      ami.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/ami/ami.socket.js b/server/api/ami/ami.socket.js
index 89060f8..6047274 100644
--- a/server/api/ami/ami.socket.js
+++ b/server/api/ami/ami.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Ami = require('../../models').Ami;
+
+exports.register = function(socket) {
+  Ami.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  Ami.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('ami:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('ami:remove', doc);
+}
diff --git a/server/api/ami/ami.spec.js b/server/api/ami/ami.spec.js
index 69a54d3..a7502d7 100644
--- a/server/api/ami/ami.spec.js
+++ b/server/api/ami/ami.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/ami', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/ami')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/ami/index.js b/server/api/ami/index.js
index a02fd0b..b926b75 100644
--- a/server/api/ami/index.js
+++ b/server/api/ami/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./ami.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+// router.get('/:id', controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+// router.put('/:id', controller.update);
+// router.patch('/:id', controller.update);
+// router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/automation/automation.controller.js b/server/api/automation/automation.controller.js
index 77fdce9..762c4c9 100644
--- a/server/api/automation/automation.controller.js
+++ b/server/api/automation/automation.controller.js
@@ -1 +1,200 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var Util = require('../../config/util');
+var sequelize = require('../../models').sequelize;
+
+var Automation = require('../../models').Automation;
+var ToolsCondition = require('../../models').ToolsCondition;
+var ToolsAction = require('../../models').ToolsAction;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return Automation
+    .scope('all')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single Automation
+exports.show = function(req, res) {
+  Automation
+    .scope('all')
+    .findById(req.params.id)
+    .then(function(Automation) {
+      if (!Automation) {
+        return res.sendStatus(404);
+      }
+      return res.send(Automation);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new Automation in the DB.
+exports.create = function(req, res, next) {
+  Automation
+    .create(req.body, {
+      include: [{
+        model: ToolsCondition,
+        as: 'All'
+      }, {
+        model: ToolsCondition,
+        as: 'Any'
+      }, {
+        model: ToolsAction,
+        as: 'Actions'
+      }]
+    })
+    .then(function() {
+      return res.sendStatus(201);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing Automation in the DB.
+exports.update = function(req, res, next) {
+  var _automation;
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return sequelize.transaction(function(t) {
+      return Automation
+        .findById(req.params.id)
+        .then(function(automation) {
+          if (!automation) {
+            return res.sendStatus(404);
+          }
+          var updated = _.merge(automation, req.body);
+          return updated
+            .save({
+              transaction: t
+            });
+        })
+        .then(function(automation) {
+          _automation = automation;
+          if (req.body.Any) {
+            return ToolsCondition
+              .destroy({
+                where: {
+                  AutomationAnyId: req.params.id
+                },
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.Any && Array.isArray(req.body.Any) && req.body.Any.length) {
+            _.forEach(req.body.Any, function(elem) {
+              elem.AutomationAnyId = req.params.id;
+            });
+            return ToolsCondition
+              .bulkCreate(req.body.Any, {
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.All) {
+            return ToolsCondition
+              .destroy({
+                where: {
+                  AutomationAllId: req.params.id
+                },
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.All && Array.isArray(req.body.All) && req.body.All.length) {
+            _.forEach(req.body.All, function(elem) {
+              elem.AutomationAllId = req.params.id;
+            });
+            return ToolsCondition
+              .bulkCreate(req.body.All, {
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.Actions) {
+            return ToolsAction
+              .destroy({
+                where: {
+                  AutomationId: req.params.id
+                },
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.Actions && Array.isArray(req.body.Actions) && req.body.Actions.length) {
+            _.forEach(req.body.Actions, function(elem) {
+              elem.AutomationId = req.params.id;
+            });
+            return ToolsAction
+              .bulkCreate(req.body.Actions, {
+                transaction: t
+              });
+          }
+        });
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+
+// Deletes a Automation from the DB.
+exports.destroy = function(req, res) {
+  Automation
+    .findById(req.params.id)
+    .then(function(Automation) {
+      if (!Automation) {
+        return res.sendStatus(404);
+      }
+      Automation.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+}
+
+exports.bulkDestroy = function(req, res) {
+  Automation
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/automation/automation.socket.js b/server/api/automation/automation.socket.js
index e3bb830..2c55b60 100644
--- a/server/api/automation/automation.socket.js
+++ b/server/api/automation/automation.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Automation = require('../../models').Automation;
+
+exports.register = function(socket) {
+  Automation.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Automation.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('automation:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('automation:remove', doc);
+}
diff --git a/server/api/automation/automation.spec.js b/server/api/automation/automation.spec.js
index 30c39d6..ab8c5b4 100644
--- a/server/api/automation/automation.spec.js
+++ b/server/api/automation/automation.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/automations', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/automations')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/automation/index.js b/server/api/automation/index.js
index c0fe121..98acf5a 100644
--- a/server/api/automation/index.js
+++ b/server/api/automation/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./automation.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+
+module.exports = router;
diff --git a/server/api/canned_answer/canned_answer.controller.js b/server/api/canned_answer/canned_answer.controller.js
index 01f0dc6..2078d7a 100644
--- a/server/api/canned_answer/canned_answer.controller.js
+++ b/server/api/canned_answer/canned_answer.controller.js
@@ -1 +1,128 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/answers              ->  index
+ * POST    /api/answers              ->  create
+ * GET     /api/answers/:id          ->  show
+ * PUT     /api/answers/:id          ->  update
+ * DELETE  /api/answers/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var CannedAnswer = require('../../models').CannedAnswer;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return CannedAnswer
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Gets a single CannedAnswer from the DB
+exports.show = function(req, res) {
+  CannedAnswer.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new CannedAnswer in the DB
+exports.create = function(req, res) {
+  CannedAnswer.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing CannedAnswer in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  CannedAnswer.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a CannedAnswer from the DB
+exports.destroy = function(req, res) {
+  CannedAnswer.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return CannedAnswer
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
diff --git a/server/api/canned_answer/canned_answer.socket.js b/server/api/canned_answer/canned_answer.socket.js
index c172bfb..1998ba4 100644
--- a/server/api/canned_answer/canned_answer.socket.js
+++ b/server/api/canned_answer/canned_answer.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var CannedAnswer = require('../../models').CannedAnswer;
+
+exports.register = function(socket) {
+  CannedAnswer.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  CannedAnswer.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('canned_answer:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('canned_answer:remove', doc);
+}
diff --git a/server/api/canned_answer/index.js b/server/api/canned_answer/index.js
index c4e2ebc..b9d0dcd 100644
--- a/server/api/canned_answer/index.js
+++ b/server/api/canned_answer/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./canned_answer.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chanspy/chanspy.controller.js b/server/api/chanspy/chanspy.controller.js
index ac84376..e3dafb3 100644
--- a/server/api/chanspy/chanspy.controller.js
+++ b/server/api/chanspy/chanspy.controller.js
@@ -1 +1,107 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ChanSpy = require('../../models').ChanSpy;
+var Util = require('../../config/util');
+
+// Get list of chanspy
+exports.index = function(req, res, next) {
+  return ChanSpy
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Get a single chanspy
+exports.show = function(req, res) {
+  return ChanSpy
+    .findById(req.params.id)
+    .then(function(chanspy) {
+      if (!chanspy) {
+        return res.sendStatus(404);
+      }
+      return res.send(chanspy);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new chanspy in the DB.
+exports.create = function(req, res) {
+  return ChanSpy
+    .create(req.body)
+    .then(function(chanspy) {
+      return res.status(201).send(chanspy);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing chanspy in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return ChanSpy
+    .findById(req.params.id)
+    .then(function(chanspy) {
+      if (!chanspy) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(chanspy, req.body);
+      return updated
+        .save();
+    })
+    .then(function(chanspy) {
+      return res.status(200).send(chanspy);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a chanspy from the DB.
+exports.destroy = function(req, res) {
+  return ChanSpy
+    .findById(req.params.id)
+    .then(function(chanspy) {
+      if (!chanspy) {
+        return res.sendStatus(404);
+      }
+      return chanspy.destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  return ChanSpy
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chanspy/chanspy.socket.js b/server/api/chanspy/chanspy.socket.js
index 5c58e38..9fb1324 100644
--- a/server/api/chanspy/chanspy.socket.js
+++ b/server/api/chanspy/chanspy.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ChanSpy = require('../../models').ChanSpy;
+
+exports.register = function(socket) {
+  ChanSpy.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ChanSpy.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('chanspy:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chanspy:remove', doc);
+}
diff --git a/server/api/chanspy/index.js b/server/api/chanspy/index.js
index cad059c..97d091d 100644
--- a/server/api/chanspy/index.js
+++ b/server/api/chanspy/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./chanspy.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), auth.hasRole('admin'), controller.index);
+router.get('/:id', auth.isAuthenticated(), auth.hasRole('admin'), controller.show);
+router.post('/', auth.isAuthenticated(), auth.hasRole('admin'), controller.create);
+router.put('/:id', auth.isAuthenticated(), auth.hasRole('admin'), controller.update);
+router.patch('/:id', auth.isAuthenticated(), auth.hasRole('admin'), controller.update);
+router.delete('/', auth.isAuthenticated(), auth.hasRole('admin'), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), auth.hasRole('admin'), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_application/chat_application.controller.js b/server/api/chat_application/chat_application.controller.js
index d5f832f..9c2085d 100644
--- a/server/api/chat_application/chat_application.controller.js
+++ b/server/api/chat_application/chat_application.controller.js
@@ -1 +1,105 @@
\ No newline at end of file
+'use strict';
+var _ = require('lodash');
+var ChatApplication = require('../../models').ChatApplication;
+// Get list of chat_applications
+exports.index = function(req, res) {
+  ChatApplication
+    .findAll({
+      where: req.query,
+      order: 'priority',
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(chat_applications) {
+      return res.status(200).send(chat_applications);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Get a single chat_application
+exports.show = function(req, res) {
+  ChatApplication
+    .findById(req.params.id)
+    .then(function(chat_application) {
+      if (!chat_application) {
+        return res.sendStatus(404);
+      }
+      return res.send(chat_application);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Creates a new chat_application in the DB.
+exports.create = function(req, res, next) {
+  ChatApplication.max('priority', {
+    where: {
+      ChatWebsiteId: req.body.ChatWebsiteId
+    }
+  }).then(function(max) {
+    // SET PRIORITY
+    req.body.priority = max ? ++max : 1;
+    ChatApplication
+      .create(req.body)
+      .then(function(mailApplication) {
+        return res.status(201).send(mailApplication);
+      })
+      .catch(function(err) {
+        return next(err);
+      });
+  }).catch(function(err) {
+    next(err);
+  });
+};
+// Updates an existing chat_application in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  ChatApplication
+    .findById(req.params.id)
+    .then(function(chat_application) {
+      if (!chat_application) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(chat_application, req.body);
+      updated
+        .save()
+        .then(function() {
+          return res.status(200).send(chat_application);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Deletes a chat_application from the DB.
+exports.destroy = function(req, res) {
+  ChatApplication
+    .findById(req.params.id)
+    .then(function(chat_application) {
+      if (!chat_application) {
+        return res.sendStatus(404);
+      }
+      chat_application
+        .destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chat_application/chat_application.socket.js b/server/api/chat_application/chat_application.socket.js
index 12e8ace..8776f3a 100644
--- a/server/api/chat_application/chat_application.socket.js
+++ b/server/api/chat_application/chat_application.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ChatApplication = require('../../models').ChatApplication;
+
+exports.register = function(socket) {
+  ChatApplication.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  ChatApplication.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('chat_application:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_application:remove', doc);
+}
diff --git a/server/api/chat_application/chat_application.spec.js b/server/api/chat_application/chat_application.spec.js
index b8faed0..215f533 100644
--- a/server/api/chat_application/chat_application.spec.js
+++ b/server/api/chat_application/chat_application.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/chat/applications', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/chat/applications')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/chat_application/index.js b/server/api/chat_application/index.js
index 377975f..d85a510 100644
--- a/server/api/chat_application/index.js
+++ b/server/api/chat_application/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./chat_application.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/chat_enquiry/enquiry.controller.js b/server/api/chat_enquiry/enquiry.controller.js
index f707fe5..d72924b 100644
--- a/server/api/chat_enquiry/enquiry.controller.js
+++ b/server/api/chat_enquiry/enquiry.controller.js
@@ -1 +1,106 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var ChatEnquiry = require('../../models').ChatEnquiry;
+var ChatVisitor = require('../../models').ChatVisitor;
+var ChatWebsite = require('../../models').ChatWebsite;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return ChatEnquiry
+    .findAndCountAll(_.merge(Util.getQuery(req.query), {
+      include: [{
+        model: ChatWebsite,
+        attributes: ['id', 'name']
+      }]
+    }))
+    .then(function(result) {
+      return res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single enquiry
+exports.show = function(req, res) {
+  return ChatEnquiry
+    .findById(req.params.id, {
+      include: [{
+        model: ChatWebsite,
+        attributes: ['id', 'name']
+      }, {
+        model: ChatVisitor
+      }]
+    })
+    .then(function(enquiry) {
+      if (!enquiry) {
+        return res.sendStatus(404);
+      }
+      return res.send(enquiry);
+    })
+    .catch(function(err) {
+      console.error(err);
+      return handleError(res, err);
+    });
+};
+
+// Creates a new enquiry in the DB.
+exports.create = function(req, res) {
+  return ChatEnquiry
+    .create(req.body)
+    .then(function(enquiry) {
+      return res.status(201).send(enquiry);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing enquiry in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ChatEnquiry
+    .findById(req.params.id)
+    .then(function(enquiry) {
+      if (!enquiry) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(enquiry, req.body);
+      return updated
+        .save();
+    })
+    .then(function(enquiry) {
+      return res.status(200).send(enquiry);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a enquiry from the DB.
+exports.destroy = function(req, res) {
+  return ChatEnquiry
+    .findById(req.params.id)
+    .then(function(enquiry) {
+      if (!enquiry) {
+        return res.sendStatus(404);
+      }
+      return enquiry
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chat_enquiry/enquiry.socket.js b/server/api/chat_enquiry/enquiry.socket.js
index da618f0..b7b69e1 100644
--- a/server/api/chat_enquiry/enquiry.socket.js
+++ b/server/api/chat_enquiry/enquiry.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var enquiry = require('../../models').ChatEnquiry;
+
+exports.register = function(socket) {
+  enquiry.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  enquiry.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('enquiry:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('enquiry:remove', doc);
+}
diff --git a/server/api/chat_enquiry/index.js b/server/api/chat_enquiry/index.js
index 6912a2a..84a2544 100644
--- a/server/api/chat_enquiry/index.js
+++ b/server/api/chat_enquiry/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./enquiry.controller');
+var license = require('../../config/license/license.service');
+var router = express.Router();
+
+router.get('/', license.isValidDate(), license.hasChat(), controller.index);
+router.get('/:id', license.isValidDate(), license.hasChat(), controller.show);
+router.post('/', license.isValidDate(), license.hasChat(), controller.create);
+router.put('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.patch('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.delete('/:id', license.isValidDate(), license.hasChat(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_message/chat_message.controller.js b/server/api/chat_message/chat_message.controller.js
index 18c7966..958ca19 100644
--- a/server/api/chat_message/chat_message.controller.js
+++ b/server/api/chat_message/chat_message.controller.js
@@ -1 +1,267 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var md5 = require('md5');
+
+var User = require('../../models').User;
+var ChatRoom = require('../../models').ChatRoom;
+var ChatMessage = require('../../models').ChatMessage;
+var ChatVisitor = require('../../models').ChatVisitor;
+
+// Get list of chat_messages
+exports.index = function (req, res) {
+  ChatMessage
+    .findAll()
+    .then(function (chat_messages) {
+      return res.status(200).send(chat_messages);
+    })
+    .catch(function (err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single chatMessage
+exports.show = function (req, res) {
+  ChatMessage
+    .findById(req.params.id)
+    .then(function (chatMessage) {
+      if (!chatMessage) {
+        return res.sendStatus(404);
+      }
+      return res.send(chatMessage);
+    })
+    .catch(function (err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new chatMessage in the DB.
+exports.create = function (req, res, next) {
+  // if (req.body.ChatRoomId && req.body.to) {
+  //   return handleError(res, new Error('Select ChatRoomId or to'));
+  // } else if (req.body.to) {
+  //
+  //   switch (req.body.type) {
+  //     case 'internal':
+  //       User
+  //         .findOne({
+  //           where: {
+  //             $or: [{
+  //               id: {
+  //                 $like: req.body.to
+  //               }
+  //             }, {
+  //               name: req.body.to
+  //             }]
+  //           }
+  //         })
+  //         .then(function(user) {
+  //           if (user) {
+  //
+  //             var users = _.sortBy([user, req.user], 'id');
+  //             var participants = {
+  //               users: {
+  //                 ids: [users[0].id, users[1].id]
+  //               },
+  //               visitors: {
+  //                 ids: []
+  //               }
+  //             };
+  //
+  //             var token = md5(JSON.stringify(participants)).toString(
+  //               'base64');
+  //
+  //             participants.users.fullnames = [users[0].fullname, users[1].fullname];
+  //             participants.visitors.fullnames = [];
+  //
+  //             ChatRoom
+  //               .findOrCreate({
+  //                 where: {
+  //                   token: token
+  //                 },
+  //                 defaults: {
+  //                   type: req.body.type,
+  //                   token: token,
+  //                   participants: JSON.stringify(participants)
+  //                 }
+  //               })
+  //               .spread(function(chatRoom, created) {
+  //
+  //                 chatRoom
+  //                   .updateAttributes({
+  //                     participants: JSON.stringify(participants)
+  //                   });
+  //
+  //                 ChatMessage
+  //                   .create({
+  //                     body: req.body.body,
+  //                     fullname: req.user.fullname,
+  //                     email: req.user.email,
+  //                     ChatRoomId: chatRoom.id,
+  //                     userId: req.user.id
+  //                   })
+  //                   .then(function(chatMessage) {
+  //                     return res.status(201).send(chatMessage);
+  //                   })
+  //                   .catch(function(err) {
+  //                     return handleError(res, err);
+  //                   });
+  //               });
+  //
+  //           } else {
+  //             return handleError(res, new Error('User not found'));
+  //           }
+  //         })
+  //         .catch(function(err) {
+  //           return handleError(res, err);
+  //         });
+  //
+  //       break;
+  //     case 'external':
+  //       ChatVisitor
+  //         .findOne({
+  //           where: {
+  //             $or: [{
+  //               id: {
+  //                 $like: req.body.to
+  //               }
+  //             }, {
+  //               fullname: req.body.to
+  //             }]
+  //           }
+  //         })
+  //         .then(function(chatVisitor) {
+  //           if (chatVisitor) {
+  //
+  //             var participants = {
+  //               users: {
+  //                 ids: [req.user.id]
+  //               },
+  //               visitors: {
+  //                 ids: [chatVisitor.id]
+  //               }
+  //             };
+  //
+  //             var token = md5(JSON.stringify(participants)).toString(
+  //               'base64');
+  //
+  //             participants.users.fullnames = [req.user.fullname];
+  //             participants.visitors.fullnames = [chatVisitor.fullname];
+  //
+  //             ChatRoom
+  //               .findOrCreate({
+  //                 where: {
+  //                   token: token
+  //                 },
+  //                 defaults: {
+  //                   type: req.body.type,
+  //                   token: token,
+  //                   participants: JSON.stringify(participants)
+  //                 }
+  //               })
+  //               .spread(function(chatRoom, created) {
+  //
+  //                 chatRoom
+  //                   .updateAttributes({
+  //                     participants: JSON.stringify(participants)
+  //                   });
+  //
+  //                 ChatMessage
+  //                   .create({
+  //                     body: req.body.body,
+  //                     fullname: req.user.fullname,
+  //                     email: req.user.email,
+  //                     ChatRoomId: chatRoom.id,
+  //                     userId: req.user.id
+  //                   })
+  //                   .then(function(chatMessage) {
+  //                     return res.status(201).send(chatMessage);
+  //                   })
+  //                   .catch(function(err) {
+  //                     return handleError(res, err);
+  //                   });
+  //               });
+  //           }
+  //         });
+  //       break;
+  //     default:
+  //       return handleError(res, new Error('Room type unsupported'));
+  //   }
+  //
+  //
+  // } else if (req.body.ChatRoomId) {
+  //
+  //   ChatMessage
+  //     .create(_.merge({
+  //       userId: req.user.id,
+  //       email: req.user.email,
+  //       fullname: req.user.fullname
+  //     }, req.body))
+  //     .then(function(chatMessage) {
+  //       return res.status(201).send(chatMessage);
+  //     })
+  //     .catch(function(err) {
+  //       return handleError(res, err);
+  //     });
+  // } else {
+  //   return handleError(res, new Error('Select ChatRoomId or to'));
+  // }
+  ChatMessage
+    .create(_.merge(req.body, {
+      UserId: req.user.id
+    }))
+    .then(function (chatMessage) {
+      return res.status(201).send(chatMessage);
+    })
+    .catch(function (err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing chatMessage in the DB.
+exports.update = function (req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return ChatMessage
+    .findById(req.params.id)
+    .then(function (chatMessage) {
+      if (!chatMessage) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(chatMessage, req.body);
+      return updated.save();
+    })
+    .then(function (chatMessage) {
+      return res.status(200).send(chatMessage);
+    })
+    .catch(function (err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a chatMessage from the DB.
+exports.destroy = function (req, res) {
+  ChatMessage
+    .findById(req.params.id)
+    .then(function (chatMessage) {
+      if (!chatMessage) {
+        return res.sendStatus(404);
+      }
+      chatMessage.destroy()
+        .then(function () {
+          return res.sendStatus(204);
+        })
+        .catch(function (err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function (err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chat_message/chat_message.socket.js b/server/api/chat_message/chat_message.socket.js
index a60816e..dccab23 100644
--- a/server/api/chat_message/chat_message.socket.js
+++ b/server/api/chat_message/chat_message.socket.js
@@ -1 +1,59 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var chat_message = require('../../models').ChatMessage;
+var _ = require('lodash');
+
+exports.register = function (socket) {
+  chat_message.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  chat_message.afterUpdate(function (doc) {
+    onSave(socket, doc);
+  });
+  chat_message.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  return doc
+    .getChatRoom()
+    .then(function (chatRoom) {
+      return [chatRoom
+        .getUsers({
+          attributes: ['id', 'name', 'fullname', 'email', 'role', 'internal'],
+          raw: true
+        }), chatRoom
+        .getChatVisitor({
+          attributes: ['id', 'fullname', 'email'],
+          raw: true
+        })
+      ];
+    })
+    .spread(function (users, visitor) {
+      var sender = _.find(users, {
+        id: doc.UserId
+      });
+
+      users.forEach(function (user) {
+        // SOCKET FILTER
+        if (socket.userId == user.id && (sender || visitor)) {
+          socket.emit('chat_message:save', _.merge(doc.dataValues, {
+            User: sender, // TODO modify to sender
+            ChatVisitor: visitor // TODO modify to visitor
+          }));
+        }
+      });
+    })
+    .catch(function (err) {
+      console.error(err);
+    });
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_message:remove', doc);
+}
diff --git a/server/api/chat_message/chat_message.spec.js b/server/api/chat_message/chat_message.spec.js
index 91f30f1..f646838 100644
--- a/server/api/chat_message/chat_message.spec.js
+++ b/server/api/chat_message/chat_message.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/chat/messages', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/chat/messages')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/chat_message/index.js b/server/api/chat_message/index.js
index 57067a8..efcb4e1 100644
--- a/server/api/chat_message/index.js
+++ b/server/api/chat_message/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./chat_message.controller');
+var auth = require('../../auth/auth.service');
+var license = require('../../config/license/license.service');
+var router = express.Router();
+
+router.get('/', license.isValidDate(), license.hasChat(), controller.index);
+router.get('/:id', license.isValidDate(), license.hasChat(), controller.show);
+router.post('/', license.isValidDate(), license.hasChat(), auth.isAuthenticated(), controller.create);
+router.put('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.delete('/:id', license.isValidDate(), license.hasChat(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_proactive_action/chat_proactive_action.controller.js b/server/api/chat_proactive_action/chat_proactive_action.controller.js
index e8c33e0..8e3b8ba 100644
--- a/server/api/chat_proactive_action/chat_proactive_action.controller.js
+++ b/server/api/chat_proactive_action/chat_proactive_action.controller.js
@@ -1 +1,112 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/chat/proactives              ->  index
+ * POST    /api/chat/proactives              ->  create
+ * GET     /api/chat/proactives/:id          ->  show
+ * PUT     /api/chat/proactives/:id          ->  update
+ * DELETE  /api/chat/proactives/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var ChatProactiveAction = require('../../models').ChatProactiveAction;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function responseWithoutResult(res, statusCode) {
+  statusCode = statusCode || 204;
+  return function() {
+    res.sendStatus(statusCode);
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) { 
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of ChatProactiveActions
+exports.index = function(req, res) {
+  return ChatProactiveAction.findAll(Util.getQuery(req.query))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single ChatProactiveAction from the DB
+exports.show = function(req, res) {
+  return ChatProactiveAction.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new ChatProactiveAction in the DB
+exports.create = function(req, res) {
+  return ChatProactiveAction.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing ChatProactiveAction in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ChatProactiveAction.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a ChatProactiveAction from the DB
+exports.destroy = function(req, res) {
+  return ChatProactiveAction.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
diff --git a/server/api/chat_proactive_action/chat_proactive_action.socket.js b/server/api/chat_proactive_action/chat_proactive_action.socket.js
index 2224c38..ca71aaf 100644
--- a/server/api/chat_proactive_action/chat_proactive_action.socket.js
+++ b/server/api/chat_proactive_action/chat_proactive_action.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ChatProactiveAction = require('../../models').ChatProactiveAction;
+
+exports.register = function(socket) {
+  ChatProactiveAction.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ChatProactiveAction.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('chat_proactive_action:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_proactive_action:remove', doc);
+}
diff --git a/server/api/chat_proactive_action/index.js b/server/api/chat_proactive_action/index.js
index 610ffd7..f38d95e 100644
--- a/server/api/chat_proactive_action/index.js
+++ b/server/api/chat_proactive_action/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./chat_proactive_action.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_proactive_action/index.spec.js b/server/api/chat_proactive_action/index.spec.js
index 379c2aa..2b9c66a 100644
--- a/server/api/chat_proactive_action/index.spec.js
+++ b/server/api/chat_proactive_action/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var chatProactiveActionCtrlStub = {
+  index: 'chatProactiveActionCtrl.index',
+  show: 'chatProactiveActionCtrl.show',
+  create: 'chatProactiveActionCtrl.create',
+  update: 'chatProactiveActionCtrl.update',
+  destroy: 'chatProactiveActionCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var chatProactiveActionIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './chat_proactive_action.controller': chatProactiveActionCtrlStub
+});
+
+describe('ChatProactiveAction API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(chatProactiveActionIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/chat/proactives', function() {
+
+    it('should route to chatProactiveAction.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'chatProactiveActionCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/chat/proactives/:id', function() {
+
+    it('should route to chatProactiveAction.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'chatProactiveActionCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/chat/proactives', function() {
+
+    it('should route to chatProactiveAction.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'chatProactiveActionCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/chat/proactives/:id', function() {
+
+    it('should route to chatProactiveAction.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'chatProactiveActionCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/chat/proactives/:id', function() {
+
+    it('should route to chatProactiveAction.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'chatProactiveActionCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/chat/proactives/:id', function() {
+
+    it('should route to chatProactiveAction.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'chatProactiveActionCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/chat_queue/chat_queue.controller.js b/server/api/chat_queue/chat_queue.controller.js
index 80d0bfb..e7c8ffd 100644
--- a/server/api/chat_queue/chat_queue.controller.js
+++ b/server/api/chat_queue/chat_queue.controller.js
@@ -1 +1,200 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var ChatQueue = require('../../models').ChatQueue;
+var User = require('../../models').User;
+var UserHasChatQueue = require('../../models').UserHasChatQueue;
+
+// Get list of mailQueues
+exports.index = function(req, res) {
+  return ChatQueue
+    .scope('default')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get list of my chat_rooms
+exports.me = function(req, res, next) {
+  UserHasChatQueue
+    .findAll({
+      where: {
+        UserId: req.user.id
+      }
+    })
+    .then(function(userHasChatQueues) {
+      return res.status(200).send(userHasChatQueues);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single chat_queue
+exports.show = function(req, res) {
+  ChatQueue
+    .findById(req.params.id)
+    .then(function(chat_queue) {
+      if (!chat_queue) {
+        return res.sendStatus(404);
+      }
+      return res.send(chat_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single queue
+exports.showAgents = function(req, res) {
+  return ChatQueue
+    .findById(req.params.id)
+    .then(function(queue) {
+      if (!queue) {
+        return res.sendStatus(404);
+      }
+      return queue
+        .getUsers({
+          where: {
+            role: 'agent'
+          },
+          attributes: ['id', 'name', 'fullname', 'email']
+        });
+    })
+    .then(function(agents) {
+      return res.status(200).send(agents);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new chat_queue in the DB.
+exports.create = function(req, res) {
+  return ChatQueue
+    .create(req.body)
+    .then(function(chat_queue) {
+      return res.status(201).send(chat_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing chat_queue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ChatQueue
+    .findById(req.params.id)
+    .then(function(chat_queue) {
+      if (!chat_queue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(chat_queue, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(chat_queue);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Add agents to chat queues
+exports.addAgents = function(req, res, next) {
+
+  return ChatQueue
+    .findById(req.params.id)
+    .then(function(chatQueue) {
+      if (!chatQueue) {
+        return res.sendStatus(404);
+      }
+      return chatQueue
+        .addUsers(req.body.agents, {
+          penalty: req.body.penalty,
+          queue: chatQueue.name
+        }, {
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.removeAgents = function(req, res, next) {
+  return ChatQueue
+    .findById(req.params.id)
+    .then(function(chatQueue) {
+      if (!chatQueue) {
+        return res.sendStatus(404);
+      }
+      return chatQueue
+        .removeUsers(req.query.agents, {
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a chat_queue from the DB.
+exports.destroy = function(req, res) {
+  ChatQueue
+    .findById(req.params.id)
+    .then(function(chat_queue) {
+      if (!chat_queue) {
+        return res.sendStatus(404);
+      }
+      chat_queue.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return ChatQueue
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chat_queue/chat_queue.socket.js b/server/api/chat_queue/chat_queue.socket.js
index 46d19b2..2e7f5d4 100644
--- a/server/api/chat_queue/chat_queue.socket.js
+++ b/server/api/chat_queue/chat_queue.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var chatQueue = require('../../models').ChatQueue;
+
+exports.register = function (socket) {
+  chatQueue.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  chatQueue.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('chat_queue:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_queue:remove', doc);
+}
diff --git a/server/api/chat_queue/index.js b/server/api/chat_queue/index.js
index 6c91e2b..d4cc855 100644
--- a/server/api/chat_queue/index.js
+++ b/server/api/chat_queue/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./chat_queue.controller');
+var auth = require('../../auth/auth.service');
+var license = require('../../config/license/license.service');
+var router = express.Router();
+
+router.get('/', license.isValidDate(), license.hasChat(), controller.index);
+router.get('/me', license.isValidDate(), license.hasChat(), auth.isAuthenticated(), controller.me);
+router.get('/:id/agents', auth.isAuthenticated(), license.hasChat(), controller.showAgents);
+router.get('/:id', license.isValidDate(), license.hasChat(), controller.show);
+router.post('/', license.isValidDate(), license.hasChat(), controller.create);
+router.post('/:id/agents', license.isValidDate(), license.hasChat(), controller.addAgents);
+router.put('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.delete('/:id/agents', license.isValidDate(), license.hasChat(), controller.removeAgents);
+router.patch('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.delete('/', license.isValidDate(), license.hasChat(), controller.bulkDestroy);
+router.delete('/:id', license.isValidDate(), license.hasChat(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_room/chat_room.controller.js b/server/api/chat_room/chat_room.controller.js
index 470d72b..efdde6b 100644
--- a/server/api/chat_room/chat_room.controller.js
+++ b/server/api/chat_room/chat_room.controller.js
@@ -1 +1,499 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var md5 = require('md5');
+var moment = require('moment');
+var csv = require('to-csv');
+
+var Util = require('../../config/util');
+
+var User = require('../../models').User;
+var ChatRoom = require('../../models').ChatRoom;
+var ChatMessage = require('../../models').ChatMessage;
+var ChatVisitor = require('../../models').ChatVisitor;
+var UserHasChatRoom = require('../../models').UserHasChatRoom;
+
+
+// Get list of chat_rooms
+exports.index = function(req, res, next) {
+  return ChatRoom
+    .findAndCountAll(_.merge(Util.getQuery(req.query), {
+      include: [{
+        model: User,
+        attributes: ['id', 'name', 'fullname', 'email', 'role', 'internal']
+      }, {
+        model: ChatMessage
+      }, {
+        model: ChatVisitor
+      }]
+    }))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Get list of my chat_rooms
+exports.me = function(req, res) {
+  return User
+    .findById(req.user.id)
+    .then(function(user) {
+      return user
+        .getChatRooms({
+          include: [{
+            model: ChatVisitor,
+            attributes: ['id', 'fullname', 'email']
+          }, {
+            model: User,
+            attributes: ['id', 'name', 'fullname', 'email', 'role', 'internal']
+          }]
+        });
+    })
+    .then(function(chatRooms) {
+      if (!chatRooms) {
+        return res.sendStatus(404);
+      }
+      return res.send(chatRooms);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single chatRoom
+exports.show = function(req, res) {
+  return ChatRoom
+    .findById(req.params.id, {
+      include: [{
+        model: ChatMessage,
+        include: [{
+          model: ChatVisitor,
+          attributes: ['id', 'email', 'fullname']
+        }, {
+          model: User,
+          attributes: ['id', 'email', 'name', 'fullname', 'userpic']
+        }]
+      }, {
+        model: User,
+        attributes: ['id', 'email', 'name', 'fullname']
+      }, {
+        model: ChatVisitor
+      }]
+    })
+    .then(function(chatRoom) {
+      if (!chatRoom) {
+        return res.sendStatus(404);
+      }
+      return res.send(chatRoom);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single chatRoom by users
+exports.getRoomByUsers = function(req, res, next) {
+
+  var _chatRoom;
+
+  return ChatRoom
+    .findOrCreate({
+      where: {
+        token: md5(JSON.stringify(req.query.users.sort())).toString('base64')
+      },
+      defaults: {
+        type: 'internal',
+        token: md5(JSON.stringify(req.query.users.sort())).toString('base64'),
+        status: 'OPEN'
+      },
+      include: [{
+        model: ChatMessage,
+        include: [{
+          model: ChatVisitor,
+          attributes: ['id', 'email', 'fullname']
+        }, {
+          model: User,
+          attributes: ['id', 'name', 'fullname', 'email', 'role', 'internal']
+        }]
+      }, {
+        model: User,
+        attributes: ['id', 'name', 'fullname', 'email', 'role', 'internal']
+      }]
+    })
+    .spread(function(chatRoom, created) {
+      _chatRoom = chatRoom;
+      // _chatRoom.dataValues.ChatMessages = [];
+      if (created) {
+        return _chatRoom
+          .setUsers(req.query.users);
+      } else {
+        return;
+      }
+    })
+    .then(function() {
+      return _chatRoom
+        .getUsers();
+    })
+    .then(function(users) {
+      return res.status(200).send(_.merge(_chatRoom.dataValues, {
+        Users: users
+      }));
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.export = function(req, res, next) {
+  console.log('export');
+  return ChatRoom
+    .findById(req.params.id, {
+      include: [{
+        model: ChatMessage,
+        include: [{
+          model: ChatVisitor,
+          attributes: ['id', 'fullname']
+        }, {
+          model: User,
+          attributes: ['id', 'fullname']
+        }]
+      }]
+    })
+    .then(function(chatRoom) {
+      var chatMessageForCSV = [];
+      chatRoom.ChatMessages.forEach(function(chatMessage) {
+        chatMessageForCSV.push({
+          name: chatMessage.User ? chatMessage.User.fullname : chatMessage.ChatVisitor.fullname,
+          text: chatMessage.body.replace(/(\r\n|\n|\r)/gm, " "),
+          date: chatMessage.createdAt
+        });
+      });
+      return res.type('text/csv').status(200).send(csv(chatMessageForCSV));
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single chatRoom
+exports.getRoomByType = function(req, res) {
+  User
+    .findById(req.user.id)
+    .then(function(user) {
+      user
+        .getChatRooms({
+          where: {
+            type: req.params.type
+          },
+          include: [{
+            model: ChatVisitor,
+            attributes: ['id', 'fullname', 'email']
+          }]
+        })
+        .then(function(chatRooms) {
+          if (!chatRooms) {
+            return res.sendStatus(404);
+          }
+          return res.send(chatRooms);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single chatRoom
+exports.getRoomGroups = function(req, res) {
+  User
+    .findById(req.user.id)
+    .then(function(user) {
+      user
+        .getChatRooms({
+          include: [{
+            model: ChatVisitor,
+            attributes: ['id', 'fullname', 'email']
+          }, {
+            model: User,
+            attributes: ['id', 'name', 'fullname', 'email', 'role', 'internal']
+          }]
+        })
+        .then(function(chatRooms) {
+          if (!chatRooms) {
+            return res.sendStatus(404);
+          }
+          return res.send(chatRooms);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new chatRoom in the DB.
+exports.create = function(req, res) {
+  // if (req.body.to) {
+  //
+  //   var token;
+  //   var participants = {
+  //     users: {
+  //       ids: []
+  //     },
+  //     visitor: {}
+  //   };
+  //
+  //   switch (req.body.type) {
+  //     case 'internal':
+  //
+  //       if (!req.body.to.isArray) {
+  //         req.body.to = [parseInt(req.body.to, 10)];
+  //       }
+  //
+  //       participants.users.ids = req.body.to;
+  //
+  //       if (!_.contains(req.body.to, req.user.id)) {
+  //         participants.users.ids.push(req.user.id);
+  //       }
+  //
+  //       participants.users.ids.sort();
+  //
+  //       token = md5(JSON.stringify(participants)).toString(
+  //         'base64');
+  //
+  //       participants.users.fullnames = [];
+  //
+  //       User
+  //         .findAll({
+  //           where: {
+  //             id: {
+  //               $in: req.body.to
+  //             }
+  //           },
+  //           attributes: ['id', 'fullname']
+  //         })
+  //         .then(function(users) {
+  //
+  //           _.sortBy(users, 'id').forEach(function(item, index) {
+  //             participants.users.fullnames.push(item.fullname);
+  //           });
+  //
+  //           ChatRoom
+  //             .findOrCreate({
+  //               where: {
+  //                 token: token
+  //               },
+  //               defaults: _.merge({
+  //                 token: token,
+  //                 participants: JSON.stringify(participants)
+  //               }, req.body),
+  //               include: [{
+  //                 model: ChatMessage
+  //               }]
+  //             })
+  //             .spread(function(chatRoom, created) {
+  //
+  //               if (created) {
+  //                 chatRoom
+  //                   .setUsers(participants.users.ids)
+  //                   .then(function() {
+  //                     return res.status(201).send(chatRoom);
+  //                   })
+  //                   .catch(function(err) {
+  //                     return handleError(res, err);
+  //                   });
+  //               } else {
+  //                 return res.status(201).send(chatRoom);
+  //               }
+  //
+  //             })
+  //             .catch(function(err) {
+  //               return handleError(res, err);
+  //             });
+  //         })
+  //         .catch(function(err) {
+  //           return handleError(res, err);
+  //         });
+  //
+  //       break;
+  //     case 'external':
+  //
+  //       participants.visitor.id = req.body.to;
+  //       participants.users.ids.push(req.user.id);
+  //       participants.users.ids.sort();
+  //
+  //       token = md5(JSON.stringify(participants)).toString('base64');
+  //
+  //       participants.users.fullnames = [req.user.fullname];
+  //
+  //       ChatVisitor
+  //         .findById(req.body.to, {
+  //           attributes: ['id', 'fullname', 'email', 'referer']
+  //         })
+  //         .then(function(chatVisitor) {
+  //           participants.visitor.fullname = chatVisitor.fullname;
+  //           participants.visitor.referer = chatVisitor.referer;
+  //           participants.visitor.email = chatVisitor.email;
+  //
+  //           ChatRoom
+  //             .findOrCreate({
+  //               where: {
+  //                 token: token
+  //               },
+  //               defaults: _.merge({
+  //                 token: token,
+  //                 participants: JSON.stringify(participants)
+  //               }, req.body),
+  //               include: [{
+  //                 model: ChatMessage
+  //               }, {
+  //                 model: ChatVisitor
+  //               }, {
+  //                 model: User,
+  //               }]
+  //             })
+  //             .spread(function(chatRoom, created) {
+  //               if (created) {
+  //
+  //                 chatRoom
+  //                   .setUsers(participants.users.ids, {
+  //                     individualHooks: true
+  //                   })
+  //                   .then(function() {
+  //                     chatRoom
+  //                       .setChatVisitor(participants.visitor.id)
+  //                       .then(function() {
+  //                         return res.status(201).send(chatRoom);
+  //                       })
+  //                       .catch(function(err) {
+  //                         return handleError(res, err);
+  //                       });
+  //                   })
+  //                   .catch(function(err) {
+  //                     return handleError(res, err);
+  //                   });
+  //               } else {
+  //                 return res.status(201).send(chatRoom);
+  //               }
+  //
+  //             })
+  //             .catch(function(err) {
+  //               return handleError(res, err);
+  //             });
+  //         })
+  //         .catch(function(err) {
+  //           return handleError(res, err);
+  //         });
+  //
+  //       break;
+  //     default:
+  //   }
+  // } else {
+  //   return handleError(res, new Error('field "to" omitted'));
+  // }
+};
+
+// Updates an existing chatRoom in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  ChatRoom
+    .findById(req.params.id)
+    .then(function(chatRoom) {
+      if (!chatRoom) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(chatRoom, req.body);
+      updated
+        .save()
+        .then(function() {
+          return res.status(200).send(chatRoom);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing chatRoom in the DB.
+exports.updateUsers = function(req, res) {
+  var _chatRoom;
+
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return ChatRoom
+    .findById(req.params.id)
+    .then(function(chatRoom) {
+      if (!chatRoom || !req.body.users) {
+        return res.sendStatus(404);
+      }
+      return chatRoom;
+    })
+    .then(function(chatRoom) {
+      return chatRoom
+        .update({
+          token: md5(JSON.stringify(req.body.users.sort())).toString('base64')
+        });
+    })
+    .then(function(chatRoom) {
+      _chatRoom = chatRoom;
+      return chatRoom
+        .setUsers(req.body.users.sort(), {
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return _chatRoom
+        .getUsers();
+    })
+    .then(function(users) {
+      return res.status(200).send(_.merge(_chatRoom.dataValues, {
+        Users: users,
+        ChatMessages: []
+      }));
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a chatRoom from the DB.
+exports.destroy = function(req, res) {
+  ChatRoom
+    .findById(req.params.id)
+    .then(function(chatRoom) {
+      if (!chatRoom) {
+        return res.sendStatus(404);
+      }
+
+      chatRoom
+        .destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chat_room/chat_room.socket.js b/server/api/chat_room/chat_room.socket.js
index 9bb273f..2f75a98 100644
--- a/server/api/chat_room/chat_room.socket.js
+++ b/server/api/chat_room/chat_room.socket.js
@@ -1 +1,30 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var User = require('../../models').User;
+var ChatRoom = require('../../models').ChatRoom;
+var ChatMessage = require('../../models').ChatMessage;
+var ChatVisitor = require('../../models').ChatVisitor;
+
+exports.register = function (socket) {
+  ChatRoom.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  ChatRoom.afterUpdate(function (doc) {
+    onSave(socket, doc);
+  });
+  ChatRoom.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('chat_room:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_room:remove', doc);
+}
diff --git a/server/api/chat_room/index.js b/server/api/chat_room/index.js
index 4bc6405..731381f 100644
--- a/server/api/chat_room/index.js
+++ b/server/api/chat_room/index.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./chat_room.controller');
+var auth = require('../../auth/auth.service');
+var license = require('../../config/license/license.service');
+var router = express.Router();
+
+router.get('/', license.isValidDate(), license.hasChat(), controller.index);
+router.get('/me', license.isValidDate(), license.hasChat(), auth.isAuthenticated(), controller.me);
+router.get('/users', license.isValidDate(), license.hasChat(), auth.isAuthenticated(), controller.getRoomByUsers);
+router.get('/groups', license.isValidDate(), license.hasChat(), auth.isAuthenticated(), controller.getRoomGroups);
+router.get('/type/:type', license.isValidDate(), license.hasChat(), auth.isAuthenticated(), controller.getRoomByType);
+router.get('/:id', license.isValidDate(), license.hasChat(), controller.show);
+router.get('/:id/export', license.isValidDate(), license.hasChat(), controller.export);
+router.post('/', license.isValidDate(), license.hasChat(), auth.isAuthenticated(), controller.create);
+router.put('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.put('/:id/users', license.isValidDate(), license.hasChat(), controller.updateUsers);
+router.patch('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.delete('/:id', license.isValidDate(), license.hasChat(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_visitor/chat_visitor.controller.js b/server/api/chat_visitor/chat_visitor.controller.js
index 2cb2ecd..38e5d14 100644
--- a/server/api/chat_visitor/chat_visitor.controller.js
+++ b/server/api/chat_visitor/chat_visitor.controller.js
@@ -1 +1,104 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var ChatVisitor = require('../../models').ChatVisitor;
+var ChatEnquiry = require('../../models').ChatEnquiry;
+var ChatWebsite = require('../../models').ChatWebsite;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return ChatVisitor
+    .findAndCountAll(_.merge(Util.getQuery(req.query), {
+      include: [{
+        model: ChatWebsite
+      }]
+    }))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      console.error(err);
+      return handleError(res, err);
+    });
+};
+
+// Get a single chatVisitor
+exports.show = function(req, res) {
+  return ChatVisitor
+    .findById(req.params.id, {
+      include: [{
+        model: ChatEnquiry
+      }, {
+        model: ChatWebsite
+      }]
+    })
+    .then(function(chatVisitor) {
+      if (!chatVisitor) {
+        return res.sendStatus(404);
+      }
+      return res.send(chatVisitor);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new chatVisitor in the DB.
+exports.create = function(req, res) {
+  return ChatVisitor
+    .create(req.body)
+    .then(function(chatVisitor) {
+      return res.status(201).send(chatVisitor);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing chatVisitor in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ChatVisitor
+    .findById(req.params.id)
+    .then(function(chatVisitor) {
+      if (!chatVisitor) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(chatVisitor, req.body);
+      return updated
+        .save();
+    })
+    .then(function(chatVisitor) {
+      return res.status(200).send(chatVisitor);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a chatVisitor from the DB.
+exports.destroy = function(req, res) {
+  return ChatVisitor
+    .findById(req.params.id)
+    .then(function(chatVisitor) {
+      if (!chatVisitor) {
+        return res.sendStatus(404);
+      }
+      return chatVisitor
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chat_visitor/chat_visitor.socket.js b/server/api/chat_visitor/chat_visitor.socket.js
index 7efe761..a283bfc 100644
--- a/server/api/chat_visitor/chat_visitor.socket.js
+++ b/server/api/chat_visitor/chat_visitor.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var chat_visitor = require('../../models').ChatVisitor;
+
+exports.register = function(socket) {
+  chat_visitor.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  chat_visitor.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  chat_visitor.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('chat_visitor:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_visitor:remove', doc);
+}
diff --git a/server/api/chat_visitor/index.js b/server/api/chat_visitor/index.js
index 1723ead..c58a917 100644
--- a/server/api/chat_visitor/index.js
+++ b/server/api/chat_visitor/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./chat_visitor.controller');
+var license = require('../../config/license/license.service');
+var router = express.Router();
+
+router.get('/', license.isValidDate(), license.hasChat(), controller.index);
+router.get('/:id', license.isValidDate(), license.hasChat(), controller.show);
+router.post('/', license.isValidDate(), license.hasChat(), controller.create);
+router.put('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.patch('/:id', license.isValidDate(), license.hasChat(), controller.update);
+router.delete('/:id', license.isValidDate(), license.hasChat(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_website/chat_website.controller.js b/server/api/chat_website/chat_website.controller.js
index 4222f6b..084d688 100644
--- a/server/api/chat_website/chat_website.controller.js
+++ b/server/api/chat_website/chat_website.controller.js
@@ -1 +1,383 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var async = require('async');
+var Mustache = require('mustache');
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var ChatWebsite = require('../../models').ChatWebsite;
+var ChatQueue = require('../../models').ChatQueue;
+var User = require('../../models').User;
+var ChatApplication = require('../../models').ChatApplication;
+var ChatWebsitesField = require('../../models').ChatWebsitesField;
+var ChatProactiveAction = require('../../models').ChatProactiveAction;
+var snippet = require('../../components/xchatty').snippet;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return ChatWebsite
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Get a single chat_website
+exports.show = function(req, res) {
+  return ChatWebsite
+    .findById(req.params.id)
+    .then(function(chat_website) {
+      if (!chat_website) {
+        return res.sendStatus(404);
+      }
+      return res.send(chat_website);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailAccount
+exports.showApplications = function(req, res, next) {
+  return ChatWebsite
+    // .scope('default')
+    .findById(req.params.id)
+    .then(function(chatWebsite) {
+      if (!chatWebsite) {
+        return res.sendStatus(404);
+      }
+      return chatWebsite
+        .getChatApplications(_.merge(Util.getQuery(req.query), {
+          include: [{
+            model: ChatQueue,
+            attributes: ['id', 'name']
+          }, {
+            model: User,
+            attributes: ['id', 'name']
+          }]
+        }));
+    })
+    .then(function(chatApplications) {
+      return res.send({
+        count: chatApplications.length,
+        rows: chatApplications
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.showProactiveActions = function(req, res, next) {
+  return ChatWebsite
+    .findById(req.params.id)
+    .then(function(chatWebsite) {
+      if (!chatWebsite) {
+        return res.sendStatus(404);
+      }
+      return chatWebsite
+        .getChatProactiveActions(Util.getQuery(req.query));
+    })
+    .then(function(chatProactiveActions) {
+      return res.send({
+        count: chatProactiveActions.length,
+        rows: chatProactiveActions
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a snippet for chat_website
+exports.getSnippetCode = function(req, res) {
+  return ChatWebsite
+    .findById(req.params.id)
+    .then(function(chat_website) {
+      var configSnippet = {
+        remote: chat_website.remote,
+        websiteId: req.params.id
+      };
+      var html = Mustache.render(snippet, configSnippet);
+      return res.status(200).send({
+        html: html
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new chat_website in the DB.
+exports.create = function(req, res) {
+  return ChatWebsite
+    .create(req.body)
+    .then(function(chat_website) {
+      return res.status(201).send(chat_website);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing chat_website in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return ChatWebsite
+    .findById(req.params.id)
+    .then(function(chat_website) {
+      if (!chat_website) {
+        return res.sendStatus(404);
+      }
+
+      return chat_website
+        .update(req.body)
+        .then(function(chat_website) {
+          return res.status(200).send(chat_website);
+        })
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing mailAccount in the DB.
+exports.updateApplications = function(req, res) {
+
+  var priority = 0;
+  var apps = req.body;
+
+  apps.forEach(function(app) {
+    delete app.id;
+    delete app.createdAt;
+    delete app.updatedAt;
+
+    app.priority = ++priority;
+  });
+
+  return sequelize.transaction(function(t) {
+    return ChatApplication
+      .destroy({
+        where: {
+          ChatWebsiteId: req.params.id
+        },
+        individualHooks: true,
+        transaction: t
+      }).then(function() {
+        return ChatApplication
+          .bulkCreate(apps, {
+            individualHooks: true,
+            transaction: t
+          });
+      });
+  }).then(function(chatApplications) {
+    return res.status(200).send({
+      count: chatApplications.length,
+      rows: chatApplications
+    });
+  }).catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+// Deletes a chat_website from the DB.
+exports.destroy = function(req, res) {
+  return ChatWebsite
+    .findById(req.params.id)
+    .then(function(chat_website) {
+      if (!chat_website) {
+        return res.sendStatus(404);
+      }
+      return chat_website
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return ChatWebsite
+    .destroy({
+      where: {
+        id: req.query.websites
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroyApplications = function(req, res) {
+  return ChatApplication
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailAccount from the DB.
+exports.destroyApplication = function(req, res, next) {
+  return ChatApplication
+    .findById(req.params.appId)
+    .then(function(mailApplication) {
+      if (!mailApplication) {
+        return res.sendStatus(404);
+      }
+
+      return mailApplication
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.getForms = function(req, res) {
+  return ChatWebsitesField
+    .scope({
+      method: [req.params.type, req.params.id]
+    })
+    .findAll()
+    .then(function(data) {
+      return res.status(200).json(data);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+}
+
+exports.updateForms = function(req, res, next) {
+  return ChatWebsitesField
+    .destroy({
+      where: req.params.type === 'online' ? {
+        OnlineId: req.params.id
+      } : {
+        OfflineId: req.params.id
+      }
+    })
+    .then(function() {
+      _.forEach(req.body, function(value) {
+        if (req.params.type === 'online') {
+          value.OnlineId = req.params.id;
+        } else if (req.params.type === 'offline') {
+          value.OfflineId = req.params.id;
+        }
+      });
+      return ChatWebsitesField.bulkCreate(req.body);
+    })
+    .then(function(fields) {
+      res.status(201).json(fields);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+}
+
+exports.bulkDestroyProactiveActions = function(req, res) {
+  return ChatProactiveAction
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailAccount from the DB.
+exports.destroyProactiveAction = function(req, res, next) {
+  return ChatProactiveAction
+    .findById(req.params.pactionId)
+    .then(function(chatProactiveAction) {
+      if (!chatProactiveAction) {
+        return res.sendStatus(404);
+      }
+      return chatProactiveAction.destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.createProactiveAction = function(req, res, next) {
+  return ChatWebsite
+    .findById(req.params.id)
+    .then(function(chatWebsite) {
+      if (!chatWebsite) {
+        return res.sendStatus(404);
+      }
+      req.body.ChatWebsiteId = chatWebsite.id;
+      return ChatProactiveAction.create(req.body);
+    })
+    .then(function(chatProactiveAction) {
+      return res.status(201).send(chatProactiveAction);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.updateProactiveAction = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return ChatProactiveAction
+    .findById(req.params.pactionId)
+    .then(function(chatProactiveAction) {
+      if (!chatProactiveAction) {
+        return res.sendStatus(404);
+      }
+
+      return chatProactiveAction
+        .update(req.body)
+        .then(function(chatProactiveAction) {
+          console.log(chatProactiveAction);
+          return res.status(200).send(chatProactiveAction);
+        })
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/chat_website/chat_website.socket.js b/server/api/chat_website/chat_website.socket.js
index 124f733..49f62d6 100644
--- a/server/api/chat_website/chat_website.socket.js
+++ b/server/api/chat_website/chat_website.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var chat_website = require('../../models').ChatWebsite;
+
+exports.register = function(socket) {
+  chat_website.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  chat_website.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('chat_website:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_website:remove', doc);
+}
diff --git a/server/api/chat_website/index.js b/server/api/chat_website/index.js
index ae5afe6..b7d89c0 100644
--- a/server/api/chat_website/index.js
+++ b/server/api/chat_website/index.js
@@ -1 +1,29 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./chat_website.controller');
+var auth = require('../../auth/auth.service');
+var license = require('../../config/license/license.service');
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/applications', auth.isAuthenticated(), controller.showApplications);
+router.get('/:id/proactive', auth.isAuthenticated(), controller.showProactiveActions);
+router.get('/:id/fields/:type', auth.isAuthenticated(), controller.getForms);
+router.get('/:id/snippet', auth.isAuthenticated(), controller.getSnippetCode);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/proactive', auth.isAuthenticated(), controller.createProactiveAction);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.put('/:id/applications', auth.isAuthenticated(), controller.updateApplications);
+router.put('/:id/proactive/:pactionId', auth.isAuthenticated(), controller.updateProactiveAction);
+router.put('/:id/fields/:type', auth.isAuthenticated(), controller.updateForms);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/:id/applications', auth.isAuthenticated(), controller.bulkDestroyApplications);
+router.delete('/:id/proactive', auth.isAuthenticated(), controller.bulkDestroyProactiveActions);
+router.delete('/:id/applications/:appId', auth.isAuthenticated(), controller.destroyApplication);
+router.delete('/:id/proactive/:pactionId', auth.isAuthenticated(), controller.destroyProactiveAction);
+
+module.exports = router;
diff --git a/server/api/chat_websites_field/chat_websites_field.controller.js b/server/api/chat_websites_field/chat_websites_field.controller.js
index 81205c2..78c9c47 100644
--- a/server/api/chat_websites_field/chat_websites_field.controller.js
+++ b/server/api/chat_websites_field/chat_websites_field.controller.js
@@ -1 +1,112 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/chat/websites/fields              ->  index
+ * POST    /api/chat/websites/fields              ->  create
+ * GET     /api/chat/websites/fields/:id          ->  show
+ * PUT     /api/chat/websites/fields/:id          ->  update
+ * DELETE  /api/chat/websites/fields/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var ChatWebsitesField = require('../../models').ChatWebsitesField;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function responseWithoutResult(res, statusCode) {
+  statusCode = statusCode || 204;
+  return function() {
+    res.sendStatus(statusCode);
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) { 
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of ChatWebsitesFields
+exports.index = function(req, res) {
+  return ChatWebsitesField.findAll(Util.getQuery(req.query))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single ChatWebsitesField from the DB
+exports.show = function(req, res) {
+  return ChatWebsitesField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new ChatWebsitesField in the DB
+exports.create = function(req, res) {
+  return ChatWebsitesField.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing ChatWebsitesField in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ChatWebsitesField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a ChatWebsitesField from the DB
+exports.destroy = function(req, res) {
+  return ChatWebsitesField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
diff --git a/server/api/chat_websites_field/chat_websites_field.socket.js b/server/api/chat_websites_field/chat_websites_field.socket.js
index 0d3df74..e6f0f21 100644
--- a/server/api/chat_websites_field/chat_websites_field.socket.js
+++ b/server/api/chat_websites_field/chat_websites_field.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ChatWebsitesField = require('../../models').ChatWebsitesField;
+
+exports.register = function(socket) {
+  ChatWebsitesField.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ChatWebsitesField.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('chat_websites_field:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('chat_websites_field:remove', doc);
+}
diff --git a/server/api/chat_websites_field/index.js b/server/api/chat_websites_field/index.js
index 82b24e4..dccf6d7 100644
--- a/server/api/chat_websites_field/index.js
+++ b/server/api/chat_websites_field/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./chat_websites_field.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/chat_websites_field/index.spec.js b/server/api/chat_websites_field/index.spec.js
index 46819ae..c50e6e0 100644
--- a/server/api/chat_websites_field/index.spec.js
+++ b/server/api/chat_websites_field/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var chatWebsitesFieldCtrlStub = {
+  index: 'chatWebsitesFieldCtrl.index',
+  show: 'chatWebsitesFieldCtrl.show',
+  create: 'chatWebsitesFieldCtrl.create',
+  update: 'chatWebsitesFieldCtrl.update',
+  destroy: 'chatWebsitesFieldCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var chatWebsitesFieldIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './chat_websites_field.controller': chatWebsitesFieldCtrlStub
+});
+
+describe('ChatWebsitesField API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(chatWebsitesFieldIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/chat/websites/fields', function() {
+
+    it('should route to chatWebsitesField.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'chatWebsitesFieldCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/chat/websites/fields/:id', function() {
+
+    it('should route to chatWebsitesField.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'chatWebsitesFieldCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/chat/websites/fields', function() {
+
+    it('should route to chatWebsitesField.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'chatWebsitesFieldCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/chat/websites/fields/:id', function() {
+
+    it('should route to chatWebsitesField.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'chatWebsitesFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/chat/websites/fields/:id', function() {
+
+    it('should route to chatWebsitesField.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'chatWebsitesFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/chat/websites/fields/:id', function() {
+
+    it('should route to chatWebsitesField.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'chatWebsitesFieldCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/company/company.controller.js b/server/api/company/company.controller.js
index 44c0164..3229f81 100644
--- a/server/api/company/company.controller.js
+++ b/server/api/company/company.controller.js
@@ -1 +1,156 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Company = require('../../models').Company;
+var util = require('util');
+var Util = require('../../config/util');
+
+// Get list of companies
+exports.index = function(req, res, next) {
+  return Company
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.companyValidation = function(req, res) {
+  console.log(req.body);
+  Company
+    .findAll({
+      where: {
+        name: req.body.name
+      }
+    })
+    .then(function(companies) {
+      if (!companies) {
+        return res.sendStatus(404);
+      }
+      return res.send(companies);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single company
+exports.show = function(req, res) {
+  Company
+    .findById(req.params.id)
+    .then(function(company) {
+      if (!company) {
+        return res.sendStatus(404);
+      }
+      return res.send(company);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new company in the DB.
+exports.create = function(req, res) {
+  Company
+    .create(req.body)
+    .then(function(company) {
+      return res.status(201).send(company);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing company in the DB.
+exports.update = function(req, res) {
+  Company
+    .findAll({
+      where: {
+        name: req.body.name,
+        id: {
+          $ne: req.body.id
+        }
+      }
+    })
+    .then(function(companies) {
+      if (!companies) {
+        return res.sendStatus(404);
+      }
+      if (companies.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_COMPANY'
+        })
+      }
+      if (req.body.id) {
+        delete req.body.id;
+      }
+      Company
+        .find({
+          where: {
+            id: req.params.id
+          }
+        })
+        .then(function(company) {
+          if (!company) {
+            return res.sendStatus(404);
+          }
+          var updated = _.merge(company, req.body);
+          updated.save()
+            .then(function() {
+              return res.status(200).send(company);
+            })
+            .catch(function(err) {
+              return handleError(res, err);
+            });
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a company from the DB.
+exports.destroy = function(req, res) {
+  Company
+    .findById(req.params.id)
+    .then(function(company) {
+      if (!company) {
+        return res.sendStatus(404);
+      }
+      company.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  Company
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/company/company.socket.js b/server/api/company/company.socket.js
index 7c85792..850be4a 100644
--- a/server/api/company/company.socket.js
+++ b/server/api/company/company.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Company = require('../../models').Company;
+
+exports.register = function(socket) {
+  Company.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Company.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('company:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('company:remove', doc);
+}
diff --git a/server/api/company/company.spec.js b/server/api/company/company.spec.js
index a5f77cc..37c8889 100644
--- a/server/api/company/company.spec.js
+++ b/server/api/company/company.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/contactmanager/companies', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/contactmanager/companies')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/company/index.js b/server/api/company/index.js
index 80f79a5..734f562 100644
--- a/server/api/company/index.js
+++ b/server/api/company/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./company.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.companyValidation);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/contact_manager/contact_manager.controller.js b/server/api/contact_manager/contact_manager.controller.js
index e0fc3c3..9457eb1 100644
--- a/server/api/contact_manager/contact_manager.controller.js
+++ b/server/api/contact_manager/contact_manager.controller.js
@@ -1 +1,429 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Contact = require('../../models').Contact;
+var ContactPhone = require('../../models').ContactPhone;
+var ContactEmail = require('../../models').ContactEmail;
+var CustomField = require('../../models').CustomField;
+var ReportCall = require('../../models').ReportCall;
+var ReportIntegration = require('../../models').ReportIntegration;
+var Service = require('../../models').Service;
+var ReportCallHistory = require('../../models').history.ReportCallHistory;
+var ReportMailSession = require('../../models').ReportMailSession;
+var ReportMailSessionHistory = require('../../models').history.ReportMailSessionHistory;
+var Tag = require('../../models').Tag;
+var stream = require('stream');
+var sequelize = require('../../models').sequelize;
+var util = require('util');
+var Util = require('../../config/util');
+
+// Get list of contacts
+exports.index = function(req, res, next) {
+  return Contact
+    .scope('service')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single managed_contact
+exports.show = function(req, res) {
+  Contact
+    .findById(req.params.id, {
+      include: [{
+        model: ContactPhone,
+        as: 'Phones',
+        include: [{
+          model: ReportCall,
+          as: 'Inbounds',
+          include: [{
+            all: true
+          }]
+        }, {
+          model: ReportCall,
+          as: 'Outbounds',
+          include: [{
+            all: true
+          }]
+        }, {
+          model: ReportCallHistory,
+          as: 'HistoryInbounds',
+          include: [{
+            all: true
+          }]
+        }, {
+          model: ReportCallHistory,
+          as: 'HistoryOutbounds',
+          include: [{
+            all: true
+          }]
+        }]
+      }, {
+        model: ContactEmail,
+        as: 'Emails',
+        include: [{
+          model: ReportMailSession,
+          as: 'InboundMessages',
+          include: [{
+            all: true
+          }]
+        }, {
+          model: ReportMailSession,
+          as: 'OutboundMessages',
+          include: [{
+            all: true
+          }]
+        }, {
+          model: ReportMailSessionHistory,
+          as: 'HistoryInboundMessages',
+          include: [{
+            all: true
+          }]
+        }, {
+          model: ReportMailSessionHistory,
+          as: 'HistoryOutboundMessages',
+          include: [{
+            all: true
+          }]
+        }]
+      }, {
+        model: CustomField,
+        as: 'CustomFields'
+      }, {
+        model: Service
+      }]
+    })
+    .then(function(managed_contact) {
+      if (!managed_contact) {
+        return res.sendStatus(404);
+      }
+      return res.status(200).send(managed_contact);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.getEventContacts = function(req, res) {
+  ReportIntegration
+    .findById(req.params.id)
+    .then(function(event) {
+      var modelInclude = {};
+      switch (event.eventChannel) {
+        case 'voice':
+          modelInclude = {
+            model: ContactPhone,
+            as: 'Phones',
+            where: {
+              phone: event.calleridnum
+            }
+          };
+          break;
+        case 'mail':
+          modelInclude = {
+            model: ContactEmail,
+            as: 'Emails',
+            where: {
+              email: event.from
+            }
+          };
+          break;
+        default:
+      }
+      return Contact.findAll({
+        include: [modelInclude, {
+          model: Service,
+          where: {
+            id: _.map(_.map(req.user.Services, 'dataValues'), 'id')
+          }
+        }]
+      });
+    })
+    .then(function(contacts) {
+      return res.status(200).send(contacts);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+
+};
+
+// validate contact uniqueness
+exports.contactValidation = function(req, res) {
+  Contact.findAll({
+      where: {
+        $or: {
+          '$Phones.phone$': req.body.phones,
+          '$Emails.email$': req.body.emails
+        }
+      },
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(contacts) {
+      console.log(contacts);
+      return res.status(200).send(contacts);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Creates a new managed_contact in the DB.
+exports.create = function(req, res, next) {
+  var newPhones = [],
+    newEmails = [],
+    newCustomFields = {};
+  Tag.findAll()
+    .then(function(tags) {
+      var newTags = [];
+      var tagList = _.map(_.map(tags, 'dataValues'), 'name');
+      if (req.body.tags) {
+        var contactTags = req.body.tags.split(',');
+        contactTags.forEach(function(elem) {
+          if (!_.includes(tags, elem)) {
+            newTags.push({
+              name: elem
+            });
+          }
+        });
+        if (newTags.length) {
+          Tag.bulkCreate(newTags, {
+            individualHooks: true
+          });
+        }
+      }
+      req.body.UserId = req.user.id;
+      var phones = _.uniq(_.map(req.body.Phones, 'phone'));
+      var contactPhones = [];
+      phones.forEach(function(elem) {
+        contactPhones.push(ContactPhone.findOrCreate({
+          where: {
+            phone: elem
+          },
+          defaults: {
+            phone: elem
+          }
+        }));
+      });
+      return contactPhones;
+    })
+    .all()
+    .then(function(res) {
+      newPhones = _.map(res, function(elem) {
+        return elem[0];
+      });
+
+      var contactEmails = [];
+      var emails = _.uniq(_.map(req.body.Emails, 'email'));
+      emails.forEach(function(elem) {
+        contactEmails.push(ContactEmail.findOrCreate({
+          where: {
+            email: elem
+          },
+          defaults: {
+            email: elem
+          }
+        }));
+      });
+      return contactEmails;
+    })
+    .all()
+    .then(function(res) {
+      newEmails = _.map(res, function(elem) {
+        return elem[0];
+      });
+      delete req.body.Phones;
+      delete req.body.Emails;
+      newCustomFields = _.clone(req.body.customFields);
+      delete req.body.customFields;
+
+      return Contact.create(req.body);
+    })
+    .then(function(contact) {
+      return [contact, contact.setPhones(newPhones)];
+    })
+    .spread(function(contact) {
+      return [contact, contact.setEmails(newEmails)];
+    })
+    .spread(function(contact) {
+      var customFieldsIds = _.keys(newCustomFields);
+      return [contact, CustomField.findAll({
+        where: {
+          id: customFieldsIds
+        }
+      })];
+    })
+    .spread(function(contact, customFields) {
+      customFields.forEach(function(elem) {
+        elem.ContactHasCustomField = {
+          value: newCustomFields[String(elem.id)]
+        }
+      });
+      return [contact, contact.setCustomFields(customFields)];
+    })
+    .spread(function(contact) {
+      return res.status(201).send(contact);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return next(err);
+    });
+};
+
+// Updates an existing managed_contact in the DB.
+exports.update = function(req, res, next) {
+  var newPhones = [],
+    newEmails = [],
+    newCustomFields = {};
+  Tag.findAll()
+    .then(function(tags) {
+      var newTags = [];
+      var tagList = _.map(_.map(tags, 'dataValues'), 'name');
+      if (req.body.tags) {
+        var contactTags = req.body.tags.split(',');
+        contactTags.forEach(function(elem) {
+          if (!_.includes(tags, elem)) {
+            newTags.push({
+              name: elem
+            });
+          }
+        });
+        if (newTags.length) {
+          Tag.bulkCreate(newTags, {
+            individualHooks: true
+          });
+        }
+      }
+      var contactPhones = [];
+      var phones = _.uniq(_.map(req.body.Phones, 'phone'));
+      phones.forEach(function(elem) {
+        contactPhones.push(ContactPhone.findOrCreate({
+          where: {
+            phone: elem
+          },
+          defaults: {
+            phone: elem
+          }
+        }));
+      });
+      return contactPhones;
+    })
+    .all()
+    .then(function(res) {
+      newPhones = _.map(res, function(elem) {
+        return elem[0];
+      });
+
+      var contactEmails = [];
+      var emails = _.uniq(_.map(req.body.Emails, 'email'));
+      emails.forEach(function(elem) {
+        contactEmails.push(ContactEmail.findOrCreate({
+          where: {
+            email: elem
+          },
+          defaults: {
+            email: elem
+          }
+        }));
+      });
+      return contactEmails;
+    })
+    .all()
+    .then(function(res) {
+      newEmails = _.map(res, function(elem) {
+        return elem[0];
+      });
+      delete req.body.Phones;
+      delete req.body.Emails;
+      newCustomFields = _.clone(req.body.customFields);
+      delete req.body.customFields;
+      delete req.body.CustomFields;
+      var updateId = req.body.id;
+      delete req.body.id;
+
+      return Contact.findById(updateId);
+    })
+    .then(function(contact) {
+      return [contact, contact.setPhones(newPhones)];
+    })
+    .spread(function(contact) {
+      return [contact, contact.setEmails(newEmails)];
+    })
+    .spread(function(contact) {
+      var customFieldsIds = _.keys(newCustomFields);
+      return [contact, CustomField.findAll({
+        where: {
+          id: customFieldsIds
+        }
+      })];
+    })
+    .spread(function(contact, customFields) {
+      customFields.forEach(function(elem) {
+        elem.ContactHasCustomField = {
+          value: newCustomFields[String(elem.id)]
+        }
+      });
+      return [contact, contact.setCustomFields(customFields)];
+    })
+    .spread(function(contact) {
+      return contact.updateAttributes(req.body)
+    })
+    .then(function(contact) {
+      return res.status(200).send(contact);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes a managed_contact from the DB.
+exports.destroy = function(req, res) {
+  Contact
+    .findById(req.params.id)
+    .then(function(managed_contact) {
+      if (!managed_contact) {
+        return res.sendStatus(404);
+      }
+      managed_contact.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a managed_contact from the DB.
+exports.bulkDestroy = function(req, res) {
+  Contact
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/contact_manager/contact_manager.socket.js b/server/api/contact_manager/contact_manager.socket.js
index 65fc24d..addfca0 100644
--- a/server/api/contact_manager/contact_manager.socket.js
+++ b/server/api/contact_manager/contact_manager.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Contact = require('../../models').Contact;
+
+exports.register = function(socket) {
+  Contact.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Contact.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('contact_manager:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('contact_manager:remove', doc);
+}
diff --git a/server/api/contact_manager/contact_manager.spec.js b/server/api/contact_manager/contact_manager.spec.js
index 0fa46e5..f212dfb 100644
--- a/server/api/contact_manager/contact_manager.spec.js
+++ b/server/api/contact_manager/contact_manager.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/contactmanager/contacts', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/contactmanager')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/contact_manager/index.js b/server/api/contact_manager/index.js
index 3e0bb55..18623b0 100644
--- a/server/api/contact_manager/index.js
+++ b/server/api/contact_manager/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./contact_manager.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id/checkevent', auth.isAuthenticated(), controller.getEventContacts);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.contactValidation);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/custom_report/custom_report.controller.js b/server/api/custom_report/custom_report.controller.js
index 64b9643..12e3404 100644
--- a/server/api/custom_report/custom_report.controller.js
+++ b/server/api/custom_report/custom_report.controller.js
@@ -1 +1,326 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var sequelize = require('../../models').sequelize;
+var CustomReport = require('../../models').CustomReport;
+var ReportField = require('../../models').ReportField;
+var Metric = require('../../models').Metric;
+var ExtractedReport = require('../../models').ExtractedReport;
+var extractUtils = require('../../utils/report_extraction');
+var reportBuildUtils = require('../../utils/report_build');
+var Util = require('../../config/util');
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return CustomReport
+    .findAndCountAll(Util.getQuery(_.merge(req.query, req.params)))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.preview = function(req, res) {
+  var _countAll;
+  return CustomReport
+    .scope('fields')
+    .findById(req.params.id)
+    .then(function(report) {
+      console.log('found report');
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      var query = reportBuildUtils.buildReport(report.dataValues, req.query);
+      // console.log(query);
+      return sequelize.query(query, {
+        type: sequelize.QueryTypes.SELECT
+      });
+    })
+    .then(function(results) {
+      _countAll = results.length;
+      if (req.query.limit) {
+        results = _(results).slice(req.query.offset || 0).take(req.query.limit).value();
+      }
+      res.status(200).send({
+        rows: results,
+        count: _countAll
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.extractPdf = function(req, res) {
+  // console.log('extractPdf api function');
+  docExtract(req, res, 'PDF')
+};
+
+exports.extractCsv = function(req, res) {
+  // console.log('extractCsv api function');
+  docExtract(req, res, 'CSV');
+};
+
+function docExtract(req, res, typeFunction) {
+  // console.log('common docExtract api function');
+  CustomReport
+    .scope('fields')
+    .findById(req.params.id)
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      // console.log('found the report, creating the new DB row for extracted reports..');
+      //CREATE A DB ROW IN A TABLE WITH THE REPORT INFO, QUERY DATES, AND THE STATUS 'ELABORATING'
+      return [report, ExtractedReport.create({
+        name: report.name,
+        startDate: req.query.startDate || null,
+        endDate: req.query.endDate || null,
+        output: typeFunction
+      })]
+    })
+    .spread(function(report, extractedReport) {
+      // console.log('created new extracted reports db entry, calling external extraction function: ' + typeFunction);
+      extractUtils['extract' + typeFunction](report, req.query, extractedReport.id);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+  return res.sendStatus(200);
+}
+
+// Get a single report
+exports.show = function(req, res) {
+  return CustomReport
+    .scope('fields')
+    .findById(req.params.id)
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      return res.send(report);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Copy a custom report
+exports.copy = function(req, res) {
+  var _fields;
+  return sequelize.transaction(function(t) {
+      return CustomReport
+        .scope('fields')
+        .findById(req.params.id)
+        .then(function(report) {
+          if (!report) {
+            return res.sendStatus(404);
+          }
+          var newReport = report.dataValues;
+          delete newReport.id;
+          delete newReport.createdAt;
+          delete newReport.updatedAt;
+          newReport.parent = req.body.parent;
+          _fields = _.map(newReport.Fields, 'dataValues');
+          return CustomReport
+            .create(newReport, {
+              transaction: t
+            });
+        })
+        .then(function(report) {
+          return [report, _fields];
+        })
+        .spread(createFields(t));
+    })
+    .then(function() {
+      return res.sendStatus(201);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Creates a new report in the DB.
+exports.create = function(req, res) {
+  var _report;
+  return sequelize.transaction(function(t) {
+      return CustomReport
+        .create(req.body, {
+          transaction: t
+        })
+        .then(function(report) {
+          _report = report;
+          return [_report, req.body.Fields];
+        })
+        .spread(createFields(t));
+    })
+    .then(function() {
+      return res.status(201).send(_report);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  var _report;
+  return sequelize.transaction(function(t) {
+      return CustomReport
+        .findById(req.params.id)
+        .then(function(report) {
+          if (!report) {
+            return res.sendStatus(404);
+          }
+          var updated = _.merge(report, req.body);
+          return report
+            .save({
+              transaction: t
+            });
+        })
+        .then(function(report) {
+          _report = report;
+          if (req.body.Fields) {
+            return ReportField.destroy({
+              where: {
+                CustomReportId: req.params.id
+              },
+              transaction: t
+            });
+          }
+        })
+        .then(function() {
+          return [_report, req.body.Fields];
+        })
+        .spread(createFields(t));
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+}
+
+function createFields(t) {
+  return function(report, fields) {
+    if (fields && Array.isArray(fields) && fields.length) {
+      var tableModel = getTable(report.table);
+      if (tableModel) {
+        var _tableFields;
+        return tableModel
+          .describe()
+          .then(function(tableFields) {
+            _tableFields = _.keys(tableFields);
+            return Metric
+              .findAll();
+          })
+          .then(function(metrics) {
+            var bulkFields = [];
+            metrics = _.map(metrics, 'dataValues');
+            fields.forEach(function(elem) {
+              elem.CustomReportId = report.id;
+              if (elem.MetricId) {
+                var metric = _.find(metrics, {
+                  id: elem.MetricId
+                });
+                if (metric) {
+                  elem.field = metric.metric;
+                  bulkFields.push(ReportField
+                    .create(elem, {
+                      transaction: t
+                    }));
+                } else {
+                  throw new Error("The metric with id " + elem.MetricId + " you specified does not exist");
+                }
+              } else {
+                if (_.includes(_tableFields, elem.field)) {
+                  bulkFields.push(ReportField
+                    .create(elem, {
+                      transaction: t
+                    }));
+                } else {
+                  throw new Error("The field " + elem.field + " you specified does not exist in the table");
+                }
+              }
+            });
+            return bulkFields;
+          })
+          .all();
+      } else {
+        throw new Error("You have not specified the table or the table does not exist");
+      }
+    }
+  }
+}
+
+function getTable(table) {
+  var tableModel;
+  switch (table) {
+    case 'report_agent_history':
+      tableModel = require('../../models').history.ReportAgentHistory;
+      break;
+    case 'report_mail_session_history':
+      tableModel = require('../../models').history.ReportMailSessionHistory;
+      break;
+    case 'report_queue_history':
+      tableModel = require('../../models').history.ReportQueueHistory;
+      break;
+    case 'report_chat_session_history':
+      tableModel = require('../../models').history.ReportChatSessionHistory;
+      break;
+    default:
+  }
+  return tableModel;
+}
+
+// Deletes a report from the DB.
+exports.destroy = function(req, res) {
+  CustomReport
+    .findById(req.params.id)
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      report.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  CustomReport
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/custom_report/custom_report.socket.js b/server/api/custom_report/custom_report.socket.js
index 6f92b11..07bf827 100644
--- a/server/api/custom_report/custom_report.socket.js
+++ b/server/api/custom_report/custom_report.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var CustomReport = require('../../models').CustomReport;
+
+exports.register = function(socket) {
+  CustomReport.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  CustomReport.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('custom_report:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('custom_report:remove', doc);
+}
diff --git a/server/api/custom_report/custom_report.spec.js b/server/api/custom_report/custom_report.spec.js
index 6ff1c15..905f802 100644
--- a/server/api/custom_report/custom_report.spec.js
+++ b/server/api/custom_report/custom_report.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/analytics/reports/custom', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/analytic/reports/custom')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/custom_report/index.js b/server/api/custom_report/index.js
index 9fd6f1e..132d3cc 100644
--- a/server/api/custom_report/index.js
+++ b/server/api/custom_report/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./custom_report.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id/preview', controller.preview);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.get('/:id/extract/pdf', controller.extractPdf);
+router.get('/:id/extract/csv', controller.extractCsv);
+router.post('/import', controller.create);
+router.post('/:id/copy', controller.copy);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/', controller.bulkDestroy);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/dashboard/dashboard.controller.js b/server/api/dashboard/dashboard.controller.js
index 84cc0b7..4eb8756 100644
--- a/server/api/dashboard/dashboard.controller.js
+++ b/server/api/dashboard/dashboard.controller.js
@@ -1 +1,147 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var Dashboard = require('../../models').Dashboard;
+var Util = require('../../config/util');
+
+// Get list of dashboards
+exports.index = function(req, res, next) {
+  return Dashboard
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single dashboard
+exports.all = function(req, res) {
+  Dashboard
+    .findAll()
+    .then(function(dashboards) {
+      if (!dashboards) {
+        return res.sendStatus(404);
+      }
+      return res.status(200).send(dashboards);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single dashboard
+exports.show = function(req, res) {
+  Dashboard
+    .findById(req.params.id)
+    .then(function(dashboard) {
+      if (!dashboard) {
+        return res.sendStatus(404);
+      }
+      return res.send(dashboard);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new dashboard in the DB.
+exports.create = function(req, res) {
+  Dashboard
+    .create(req.body)
+    .then(function(dashboard) {
+      return res.status(201).send(dashboard);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Clone a dashboard in the DB.
+exports.clone = function(req, res) {
+  Dashboard
+    .findById(req.params.id)
+    .then(function(dashboard) {
+      var newDashboard = dashboard.dataValues;
+      delete newDashboard.id;
+      newDashboard.editable = true;
+      newDashboard.defaultEntry = false;
+      newDashboard.name = req.body.name || newDashboard.name;
+      return Dashboard
+        .create(newDashboard);
+    })
+    .then(function(dashboard) {
+      return res.status(201).send(dashboard);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing dashboard in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  Dashboard
+    .findById(req.params.id)
+    .then(function(dashboard) {
+      if (!dashboard) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(dashboard, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(dashboard);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a dashboard from the DB.
+exports.destroy = function(req, res) {
+  Dashboard
+    .findById(req.params.id)
+    .then(function(dashboard) {
+      if (!dashboard) {
+        return res.sendStatus(404);
+      }
+      dashboard.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  Dashboard
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/dashboard/dashboard.socket.js b/server/api/dashboard/dashboard.socket.js
index fe1a684..9bf2215 100644
--- a/server/api/dashboard/dashboard.socket.js
+++ b/server/api/dashboard/dashboard.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Dashboard = require('../../models').Dashboard;
+
+exports.register = function(socket) {
+  Dashboard.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Dashboard.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  Dashboard.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('dashboard:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('dashboard:remove', doc);
+}
diff --git a/server/api/dashboard/dashboard.spec.js b/server/api/dashboard/dashboard.spec.js
index c1aea37..4784e74 100644
--- a/server/api/dashboard/dashboard.spec.js
+++ b/server/api/dashboard/dashboard.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/dashboards', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/dashboards')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/dashboard/index.js b/server/api/dashboard/index.js
index 67c11af..393fb11 100644
--- a/server/api/dashboard/index.js
+++ b/server/api/dashboard/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./dashboard.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/all', auth.isAuthenticated(), controller.all);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/clone', auth.isAuthenticated(), controller.clone);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/default_report/default_report.controller.js b/server/api/default_report/default_report.controller.js
index d09915d..4fc95af 100644
--- a/server/api/default_report/default_report.controller.js
+++ b/server/api/default_report/default_report.controller.js
@@ -1 +1,234 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var DefaultReport = require('../../models').DefaultReport;
+var sequelize = require('../../models').sequelize;
+var CustomReport = require('../../models').CustomReport;
+var ExtractedReport = require('../../models').ExtractedReport;
+var extractUtils = require('../../utils/report_extraction');
+var reportBuildUtils = require('../../utils/report_build');
+var Util = require('../../config/util');
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return DefaultReport
+    .findAndCountAll(Util.getQuery(_.merge(req.query, req.params)))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+
+exports.preview = function(req, res) {
+  var _countAll;
+  DefaultReport
+    .findById(req.params.id, {
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(report) {
+      console.log('found report');
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      var query = reportBuildUtils.buildReport(report.dataValues, req.query);
+      // console.log(query);
+      return sequelize.query(query, {
+        type: sequelize.QueryTypes.SELECT
+      });
+    })
+    .then(function(results) {
+      _countAll = results.length;
+      if (req.query.limit) {
+        var results = _(results).slice(req.query.offset || 0).take(req.query.limit).value();
+      }
+      res.status(200).send({
+        rows: results,
+        count: _countAll
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.extractPdf = function(req, res) {
+  // console.log('extractPdf api function');
+  docExtract(req, res, 'PDF')
+};
+
+exports.extractCsv = function(req, res) {
+  // console.log('extractCsv api function');
+  docExtract(req, res, 'CSV');
+};
+
+function docExtract(req, res, typeFunction) {
+  // console.log('common docExtract api function');
+  DefaultReport
+    .findById(req.params.id, {
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      // console.log('found the report, creating the new DB row for extracted reports..');
+      //CREATE A DB ROW IN A TABLE WITH THE REPORT INFO, QUERY DATES, AND THE STATUS 'ELABORATING'
+      return [report, ExtractedReport.create({
+        name: report.name,
+        startDate: req.query.startDate || null,
+        endDate: req.query.endDate || null,
+        output: typeFunction
+      })]
+    })
+    .spread(function(report, extractedReport) {
+      // console.log('created new extracted reports db entry, calling external extraction function: ' + typeFunction);
+      extractUtils['extract' + typeFunction](report, req.query, extractedReport.id);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+  return res.sendStatus(200);
+}
+
+function getConditions(group) {
+  if (!group) return "";
+  for (var str = "(", i = 0; i < group.rules.length; i++) {
+    i > 0 && (str += " " + group.operator + " ");
+    str += group.rules[i].group ?
+      getConditions(group.rules[i].group) :
+      group.rules[i].field + " " + group.rules[i].condition + " '" + (group.rules[i].condition === 'LIKE' ? "%" + group.rules[i].value + "%" : group.rules[i].value) + "'";
+  }
+
+  return str + ')';
+}
+
+// Get a single report
+exports.show = function(req, res) {
+  DefaultReport
+    .findById(req.params.id, {
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      return res.send(report);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Copy a default report
+exports.copy = function(req, res) {
+  DefaultReport
+    .findById(req.params.id, {
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      var newReport = report.dataValues;
+      delete newReport.id;
+      delete newReport.createdAt;
+      delete newReport.updatedAt;
+      newReport.parent = req.body.parent;
+      newReport.Fields = _.map(newReport.Fields, 'dataValues');
+      _.forEach(newReport.Fields, function(elem) {
+        delete elem.id;
+        delete elem.DefaultReportId;
+        delete elem.createdAt;
+        delete elem.updatedAt;
+      });
+
+      return CustomReport.create(newReport, {
+        include: [{
+          all: true
+        }]
+      })
+    })
+    .then(function() {
+      return res.sendStatus(201);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Creates a new report in the DB.
+exports.create = function(req, res) {
+  DefaultReport
+    .create(req.body)
+    .then(function(report) {
+      return res.status(201).send(report);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing report in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  DefaultReport
+    .findById(req.params.id)
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(report, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(report);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a report from the DB.
+exports.destroy = function(req, res) {
+  DefaultReport
+    .findById(req.params.id)
+    .then(function(report) {
+      if (!report) {
+        return res.sendStatus(404);
+      }
+      report.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/default_report/default_report.socket.js b/server/api/default_report/default_report.socket.js
index aba6d5e..2acd563 100644
--- a/server/api/default_report/default_report.socket.js
+++ b/server/api/default_report/default_report.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var DefaultReport = require('../../models').DefaultReport;
+
+exports.register = function(socket) {
+  DefaultReport.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  DefaultReport.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('default_report:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('default_report:remove', doc);
+}
diff --git a/server/api/default_report/default_report.spec.js b/server/api/default_report/default_report.spec.js
index be499e8..2a6c81d 100644
--- a/server/api/default_report/default_report.spec.js
+++ b/server/api/default_report/default_report.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/analytics/reports/default', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/analytic/reports/default')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/default_report/index.js b/server/api/default_report/index.js
index 633728a..550ecd6 100644
--- a/server/api/default_report/index.js
+++ b/server/api/default_report/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./default_report.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id/preview', controller.preview);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.get('/:id/extract/pdf', controller.extractPdf);
+router.get('/:id/extract/csv', controller.extractCsv);
+router.post('/:id/copy', controller.copy);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/desk_account/desk_account.controller.js b/server/api/desk_account/desk_account.controller.js
index 680efa0..78e1902 100644
--- a/server/api/desk_account/desk_account.controller.js
+++ b/server/api/desk_account/desk_account.controller.js
@@ -1 +1,256 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var desk = require('desk.js');
+
+var DeskAccount = require('../../models').DeskAccount;
+
+// Get list of desk_accounts
+exports.index = function(req, res) {
+  var attributes = ['description', 'name', 'username', 'remoteUri'];
+  var per_page = req.query.per_page ? parseInt(req.query.per_page, 10) : 100;
+  var page = req.query.page ? parseInt(req.query.page, 10) : 0;
+
+  var query = {
+    where: {},
+    limit: per_page,
+    offset: page * per_page
+  };
+
+  _.forIn(req.query, function(value, key) {
+    switch (key) {
+      case 'per_page':
+      case 'page':
+        break;
+      case 'sort_by':
+        query.order = util.format('%s %s', req.query.sort_by, req.query.sort_order || 'ASC') || null;
+        break;
+      case 'sort_order':
+        break;
+      case '$':
+        query.where.$or = [];
+        attributes.forEach(function(attribute) {
+          var tmp = {};
+          tmp[attribute] = {
+            $like: '%' + value + '%'
+          };
+
+          query.where.$or.push(tmp);
+        });
+        break;
+      default:
+        query.where[key] = {
+          $like: {}
+        };
+        query.where[key].$like = '%' + value + '%';
+    }
+  });
+
+  DeskAccount
+    .findAndCountAll(query)
+    .then(function(result) {
+
+      var total_pages = Math.ceil(result.count / per_page);
+      var next_page = total_pages > (query.offset + 1) ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page + 1) : null;
+      var previous_page = page > 0 ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page - 1) : null;
+
+      return res.status(200).send({
+        count: result.count,
+        rows: result.rows,
+        next_page: next_page,
+        previous_page: previous_page,
+        total_pages: total_pages
+      });
+
+    })
+    .catch(function(err) {
+      return res.status(500).send({
+        error: 'Something blew up!'
+      });
+    });
+};
+
+// Get a single desk_account
+exports.show = function(req, res) {
+  DeskAccount
+    .findById(req.params.id)
+    .then(function(desk_account) {
+      if (!desk_account) {
+        return res.sendStatus(404);
+      }
+      return res.send(desk_account);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new desk_account in the DB.
+exports.create = function(req, res, next) {
+  DeskAccount
+    .create(req.body)
+    .then(function(desk_account) {
+      return res.status(201).send(desk_account);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing desk_account in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  DeskAccount
+    .findById(req.params.id)
+    .then(function(desk_account) {
+      if (!desk_account) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(desk_account, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(desk_account);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a desk_account from the DB.
+exports.destroy = function(req, res) {
+  DeskAccount
+    .findById(req.params.id)
+    .then(function(desk_account) {
+      if (!desk_account) {
+        return res.sendStatus(404);
+      }
+      desk_account.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.checkAccount = function(req, res) {
+  DeskAccount
+    .findById(req.params.id)
+    .then(function(account) {
+      var desk_account = {
+        endpoint: stripTrailingSlash(account.remoteUri),
+        retry: false
+      };
+      if (account.authType === 'basic') {
+        desk_account.username = account.username;
+        desk_account.password = account.password;
+      } else if (account.authType === 'oauth') {
+        desk_account.consumerKey = account.consumerKey;
+        desk_account.consumerSecret = account.consumerSecret;
+        desk_account.token = account.token;
+        desk_account.tokenSecret = account.tokenSecret;
+      }
+      var client = desk.createClient(desk_account);
+      client.get('/api/v2/users/me', function(err, body, response) {
+        if (err) {
+          return handleError(res, err);
+        }
+        return res.sendStatus(200);
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.deskValidation = function(req, res) {
+  var where = {};
+  where[req.params.field] = req.body.value;
+  DeskAccount
+    .findAndCountAll({
+      where: where
+    })
+    .then(function(result) {
+      if (result.count) {
+        return res.status(200).send({
+          isValid: false,
+          value: req.body.value
+        });
+      }
+      return res.status(200).send({
+        isValid: true,
+        value: req.body.value
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.getFields = function(req, res) {
+  DeskAccount
+    .findById(req.params.id)
+    .then(function(account) {
+      var desk_account = {
+        endpoint: stripTrailingSlash(account.remoteUri),
+        retry: false
+      };
+      if (account.authType === 'basic') {
+        desk_account.username = account.username;
+        desk_account.password = account.password;
+      } else if (account.authType === 'oauth') {
+        desk_account.consumerKey = account.consumerKey;
+        desk_account.consumerSecret = account.consumerSecret;
+        desk_account.token = account.token;
+        desk_account.tokenSecret = account.tokenSecret;
+      }
+      var client = desk.createClient(desk_account);
+      client.get('/api/v2/custom_fields', function(err, body, response) {
+        if (err) {
+          return handleError(res, err);
+        }
+        return res.status(200).json(body._embedded.entries);
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+}
+
+exports.bulkDestroy = function(req, res) {
+  DeskAccount
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function stripTrailingSlash(str) {
+  if (str.substr(-1) === '/') {
+    return str.substr(0, str.length - 1);
+  }
+  return str;
+}
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/desk_account/desk_account.socket.js b/server/api/desk_account/desk_account.socket.js
index c723273..3f7badd 100644
--- a/server/api/desk_account/desk_account.socket.js
+++ b/server/api/desk_account/desk_account.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var DeskAccount = require('../../models').DeskAccount;
+
+exports.register = function(socket) {
+  DeskAccount.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  DeskAccount.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('desk_account:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('desk_account:remove', doc);
+}
diff --git a/server/api/desk_account/desk_account.spec.js b/server/api/desk_account/desk_account.spec.js
index acba779..a56fc11 100644
--- a/server/api/desk_account/desk_account.spec.js
+++ b/server/api/desk_account/desk_account.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/desk/accounts', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/desk/accounts')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/desk_account/index.js b/server/api/desk_account/index.js
index 0a93455..4a593d0 100644
--- a/server/api/desk_account/index.js
+++ b/server/api/desk_account/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./desk_account.controller');
+var auth = require('../../auth/auth.service');
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/check', auth.isAuthenticated(), controller.checkAccount);
+router.get('/:id/fields', auth.isAuthenticated(), controller.getFields);
+router.post('/validate/:field', auth.isAuthenticated(), controller.deskValidation);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/desk_configuration/desk_configuration.controller.js b/server/api/desk_configuration/desk_configuration.controller.js
index 5367d3d..3824ca1 100644
--- a/server/api/desk_configuration/desk_configuration.controller.js
+++ b/server/api/desk_configuration/desk_configuration.controller.js
@@ -1 +1,145 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/desk/configurations              ->  index
+ * POST    /api/desk/configurations              ->  create
+ * GET     /api/desk/configurations/:id          ->  show
+ * PUT     /api/desk/configurations/:id          ->  update
+ * DELETE  /api/desk/configurations/:id          ->  destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+
+var DeskConfiguration = require('../../models').DeskConfiguration;
+var DeskField = require('../../models').DeskField;
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of DeskConfigurations
+exports.index = function(req, res) {
+  DeskConfiguration.findAll({
+      where: req.query
+    })
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single DeskConfiguration from the DB
+exports.show = function(req, res) {
+  DeskConfiguration.find({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        model: DeskField,
+        as: 'Subject'
+      }, {
+        model: DeskField,
+        as: 'Description'
+      }, {
+        model: DeskField,
+        as: 'Field'
+      }]
+    })
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new DeskConfiguration in the DB
+exports.create = function(req, res) {
+  DeskConfiguration.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing DeskConfiguration in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  DeskConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a DeskConfiguration from the DB
+exports.destroy = function(req, res) {
+  DeskConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+exports.addConfigurationField = function(req, res, next) {
+  // console.log(req.params);
+  var deskConfiguration;
+  DeskConfiguration
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(desk_configuration) {
+      deskConfiguration = desk_configuration;
+      return DeskField.create(req.body)
+    })
+    .then(function(deskField) {
+      switch (req.params.type) {
+        case 'subject':
+          return [deskConfiguration.addSubject(deskField), deskField];
+        case 'description':
+          return [deskConfiguration.addDescription(deskField), deskField];
+        case 'field':
+          return [deskConfiguration.addField(deskField), deskField];
+      }
+    })
+    .spread(function(data, deskField) {
+      return res.status(201).json(deskField);
+    })
+    .catch(handleError(res));
+};
diff --git a/server/api/desk_configuration/desk_configuration.socket.js b/server/api/desk_configuration/desk_configuration.socket.js
index 3a34d03..d26e4f6 100644
--- a/server/api/desk_configuration/desk_configuration.socket.js
+++ b/server/api/desk_configuration/desk_configuration.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var DeskConfiguration = require('../../models').DeskConfiguration;
+
+exports.register = function(socket) {
+  DeskConfiguration.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  DeskConfiguration.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('desk_configuration:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('desk_configuration:remove', doc);
+}
diff --git a/server/api/desk_configuration/index.js b/server/api/desk_configuration/index.js
index 719acd1..91adaf0 100644
--- a/server/api/desk_configuration/index.js
+++ b/server/api/desk_configuration/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./desk_configuration.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.post('/:id/:type', auth.isAuthenticated(), controller.addConfigurationField);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/desk_configuration/index.spec.js b/server/api/desk_configuration/index.spec.js
index a99767d..fd6b650 100644
--- a/server/api/desk_configuration/index.spec.js
+++ b/server/api/desk_configuration/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var deskConfigurationCtrlStub = {
+  index: 'deskConfigurationCtrl.index',
+  show: 'deskConfigurationCtrl.show',
+  create: 'deskConfigurationCtrl.create',
+  update: 'deskConfigurationCtrl.update',
+  destroy: 'deskConfigurationCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var deskConfigurationIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './desk_configuration.controller': deskConfigurationCtrlStub
+});
+
+describe('DeskConfiguration API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(deskConfigurationIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/desk/configurations', function() {
+
+    it('should route to deskConfiguration.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'deskConfigurationCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/desk/configurations/:id', function() {
+
+    it('should route to deskConfiguration.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'deskConfigurationCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/desk/configurations', function() {
+
+    it('should route to deskConfiguration.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'deskConfigurationCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/desk/configurations/:id', function() {
+
+    it('should route to deskConfiguration.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'deskConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/desk/configurations/:id', function() {
+
+    it('should route to deskConfiguration.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'deskConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/desk/configurations/:id', function() {
+
+    it('should route to deskConfiguration.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'deskConfigurationCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/desk_field/desk_field.controller.js b/server/api/desk_field/desk_field.controller.js
index 57808e7..6b39f83 100644
--- a/server/api/desk_field/desk_field.controller.js
+++ b/server/api/desk_field/desk_field.controller.js
@@ -1 +1,104 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/desk/fields              ->  index
+ * POST    /api/desk/fields              ->  create
+ * GET     /api/desk/fields/:id          ->  show
+ * PUT     /api/desk/fields/:id          ->  update
+ * DELETE  /api/desk/fields/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var DeskField = require('../../models').DeskField;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of DeskFields
+exports.index = function(req, res) {
+  DeskField.findAll()
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single DeskField from the DB
+exports.show = function(req, res) {
+  DeskField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new DeskField in the DB
+exports.create = function(req, res) {
+  DeskField.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing DeskField in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  DeskField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a DeskField from the DB
+exports.destroy = function(req, res) {
+  DeskField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
diff --git a/server/api/desk_field/desk_field.socket.js b/server/api/desk_field/desk_field.socket.js
index bd62d16..b01d8f1 100644
--- a/server/api/desk_field/desk_field.socket.js
+++ b/server/api/desk_field/desk_field.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var DeskField = require('../../models').DeskField;
+
+exports.register = function(socket) {
+  DeskField.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  DeskField.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('desk_field:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('desk_field:remove', doc);
+}
diff --git a/server/api/desk_field/index.js b/server/api/desk_field/index.js
index fe6988e..266676c 100644
--- a/server/api/desk_field/index.js
+++ b/server/api/desk_field/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./desk_field.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/desk_field/index.spec.js b/server/api/desk_field/index.spec.js
index c6c5c44..fef888c 100644
--- a/server/api/desk_field/index.spec.js
+++ b/server/api/desk_field/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var deskFieldCtrlStub = {
+  index: 'deskFieldCtrl.index',
+  show: 'deskFieldCtrl.show',
+  create: 'deskFieldCtrl.create',
+  update: 'deskFieldCtrl.update',
+  destroy: 'deskFieldCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var deskFieldIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './desk_field.controller': deskFieldCtrlStub
+});
+
+describe('DeskField API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(deskFieldIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/desk/fields', function() {
+
+    it('should route to deskField.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'deskFieldCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/desk/fields/:id', function() {
+
+    it('should route to deskField.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'deskFieldCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/desk/fields', function() {
+
+    it('should route to deskField.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'deskFieldCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/desk/fields/:id', function() {
+
+    it('should route to deskField.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'deskFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/desk/fields/:id', function() {
+
+    it('should route to deskField.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'deskFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/desk/fields/:id', function() {
+
+    it('should route to deskField.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'deskFieldCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/extracted_report/extracted_report.controller.js b/server/api/extracted_report/extracted_report.controller.js
index 165a970..3d57d37 100644
--- a/server/api/extracted_report/extracted_report.controller.js
+++ b/server/api/extracted_report/extracted_report.controller.js
@@ -1 +1,197 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ExtractedReport = require('../../models').ExtractedReport;
+var util = require('util');
+var path = require('path');
+var moment = require('moment');
+var config = require('../../config/environment');
+var base64 = require('file-base64');
+var fs = require('fs');
+var Util = require('../../config/util');
+
+// Get list of extracted_reports
+exports.index = function(req, res, next) {
+  return ExtractedReport
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single extracted_reports
+exports.show = function(req, res) {
+  ExtractedReport
+    .findById(req.params.id)
+    .then(function(extracted) {
+      if (!extracted) {
+        return res.sendStatus(404);
+      }
+      return res.send(extracted);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.download = function(req, res) {
+  ExtractedReport
+    .findById(req.params.id)
+    .then(function(extract) {
+      if (!extract) {
+        return res.status(404).send(extract);
+      }
+      var filepath = path.join(config.root, 'server', 'files', 'reports', extract.savename);
+      base64.encode(filepath, function(err, base64String) {
+        if (err) {
+          console.log(err);
+          return handleError(res, err);
+        } else {
+          return res.status(200).send({
+            downloadString: base64String
+          });
+        }
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Creates a new extracted_report in the DB.
+exports.create = function(req, res) {
+  ExtractedReport
+    .create(req.body)
+    .then(function(extracted) {
+      return res.status(201).send(extracted);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing extracted_report in the DB.
+exports.update = function(req, res) {
+  ExtractedReport
+    .findAll({
+      where: {
+        name: req.body.name,
+        id: {
+          $ne: req.body.id
+        }
+      }
+    })
+    .then(function(extracted_reports) {
+      if (!extracted_reports) {
+        return res.sendStatus(404);
+      }
+      if (req.body.id) {
+        delete req.body.id;
+      }
+      ExtractedReport
+        .findById(req.params.id)
+        .then(function(extracted) {
+          if (!extracted) {
+            return res.sendStatus(404);
+          }
+          var updated = _.merge(extracted, req.body);
+          updated.save()
+            .then(function() {
+              return res.status(200).send(extracted);
+            })
+            .catch(function(err) {
+              return handleError(res, err);
+            });
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes an extracted_report from the DB.
+exports.destroy = function(req, res) {
+  var _extracted;
+  ExtractedReport
+    .findById(req.params.id)
+    .then(function(extracted) {
+      _extracted = extracted;
+      if (!extracted) {
+        return res.sendStatus(404);
+      }
+      return extracted.destroy();
+    })
+    .then(function() {
+      if (_extracted.savename) {
+        var filepath = path.join(config.root, 'server', 'files', 'reports', _extracted.savename);
+        fs.access(filepath, fs.R_OK | fs.W_OK, function(err) {
+          if (err) {
+            console.error('File does not exist or operation not permitted');
+          } else {
+            fs.unlink(filepath, function(err) {
+              if (err) {
+                console.error('error deleting file :', err);
+              } else {
+                console.log('file deleted');
+              }
+            })
+          }
+        });
+      }
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  ExtractedReport
+    .findAll({
+      where: {
+        id: req.query.ids
+      }
+    })
+    .then(function(extracts) {
+      extracts.forEach(function(extracted) {
+        extracted.destroy()
+          .then(function() {
+            if (extracted.savename) {
+              var filepath = path.join(config.root, 'server', 'files', 'reports', extracted.savename);
+              fs.access(filepath, fs.R_OK | fs.W_OK, function(err) {
+                if (err) {
+                  console.error('File does not exist or operation not permitted');
+                } else {
+                  fs.unlink(filepath, function(err) {
+                    if (err) {
+                      console.error('error deleting file :', err);
+                    } else {
+                      console.log('file deleted');
+                    }
+                  })
+                }
+              });
+            }
+          })
+          .catch(function(err) {
+            console.log(err);
+          });
+      });
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/extracted_report/extracted_report.socket.js b/server/api/extracted_report/extracted_report.socket.js
index 36143b3..faf3482 100644
--- a/server/api/extracted_report/extracted_report.socket.js
+++ b/server/api/extracted_report/extracted_report.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ExtractedReport = require('../../models').ExtractedReport;
+
+exports.register = function(socket) {
+  ExtractedReport.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ExtractedReport.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('extracted_report:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('extracted_report:remove', doc);
+}
diff --git a/server/api/extracted_report/extracted_report.spec.js b/server/api/extracted_report/extracted_report.spec.js
index d82b009..0830865 100644
--- a/server/api/extracted_report/extracted_report.spec.js
+++ b/server/api/extracted_report/extracted_report.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/analytics/extracted', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/analytics/extracted')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/extracted_report/index.js b/server/api/extracted_report/index.js
index 2418f60..21b8e1c 100644
--- a/server/api/extracted_report/index.js
+++ b/server/api/extracted_report/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./extracted_report.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id/download', auth.isAuthenticated(), controller.download);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/fax_account/fax_account.controller.js b/server/api/fax_account/fax_account.controller.js
index 7b00e78..d550303 100644
--- a/server/api/fax_account/fax_account.controller.js
+++ b/server/api/fax_account/fax_account.controller.js
@@ -1 +1,374 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var async = require('async');
+
+var Trunk = require('../../models').Trunk;
+var FaxAccount = require('../../models').FaxAccount;
+var FaxApplication = require('../../models').FaxApplication;
+var VoiceExtension = require('../../models').VoiceExtension;
+var sequelize = require('../../models').sequelize;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+
+  var attributes = ['description', 'name', 'phone'];
+  var per_page = req.query.per_page ? parseInt(req.query.per_page, 10) : 100;
+  var page = req.query.page ? parseInt(req.query.page, 10) : 0;
+
+  var query = {
+    where: {},
+    limit: per_page,
+    offset: page * per_page
+  };
+
+  _.forIn(req.query, function(value, key) {
+    switch (key) {
+      case 'per_page':
+      case 'page':
+        break;
+      case 'sort_by':
+        query.order = util.format('%s %s', req.query.sort_by, req.query.sort_order || 'ASC') || null;
+        break;
+      case 'sort_order':
+        break;
+      case '$':
+        query.where.$or = [];
+        attributes.forEach(function(attribute) {
+          var tmp = {};
+          tmp[attribute] = {
+            $like: '%' + value + '%'
+          };
+
+          query.where.$or.push(tmp);
+        });
+        break;
+      default:
+        query.where[key] = {
+          $like: {}
+        };
+        query.where[key].$like = '%' + value + '%';
+    }
+  });
+
+  FaxAccount
+    .findAndCountAll(query)
+    .then(function(result) {
+
+      var total_pages = Math.ceil(result.count / per_page);
+      var next_page = total_pages > (query.offset + 1) ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page + 1) : null;
+      var previous_page = page > 0 ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page - 1) : null;
+
+      res.status(200).send({
+        count: result.count,
+        rows: result.rows,
+        next_page: next_page,
+        previous_page: previous_page,
+        total_pages: total_pages
+      });
+
+    })
+    .catch(function(err) {
+      res.status(500).send({
+        error: 'Something blew up!'
+      });
+    });
+};
+
+// Get a single faxAccount
+exports.show = function(req, res, next) {
+  FaxAccount
+    .findById(req.params.id)
+    .then(function(faxAccount) {
+      if (!faxAccount) {
+        return res.sendStatus(404);
+      }
+      return res.send(faxAccount);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Creates a new faxAccount in the DB.
+exports.create = function(req, res, next) {
+  // console.log(req.body);
+  return sequelize
+    .transaction()
+    .then(function(t) {
+      async.waterfall([
+        function(callback) {
+          return FaxAccount
+            .create(req.body, {
+              transaction: t
+            })
+            .then(function(faxAccount) {
+              callback(null, faxAccount);
+            })
+            .catch(function(err) {
+              callback(err);
+            });
+        },
+        function(faxAccount, callback) {
+          return Trunk
+            .findById(req.body.TrunkId, {
+              transaction: t
+            })
+            .then(function(faxTrunk) {
+              callback(null, faxAccount, faxTrunk);
+            })
+            .catch(function(err) {
+              callback(err);
+            });
+        },
+        function(faxAccount, faxTrunk, callback) {
+          return VoiceExtension
+            .create({
+              context: faxTrunk.context,
+              exten: req.body.phone,
+              priority: 1,
+              app: 'Goto',
+              appdata: 'inbound-fax,s,1',
+              type: 'inbound-fax',
+              TrunkId: faxTrunk.id
+            }, {
+              transaction: t
+            })
+            .then(function(voiceExtension) {
+              callback(null, faxAccount);
+            })
+            .catch(function(err) {
+              callback(err);
+            });
+        }
+      ], function(err, faxAccount) {
+        if (err) {
+          console.log(err);
+          t.rollback();
+          return res.status(404).send(err);
+        } else {
+          console.log('ok');
+          t.commit();
+          return res.status(201).send(faxAccount);
+        }
+      })
+    });
+};
+
+// Updates an existing faxAccount in the DB.
+exports.update = function(req, res, next) {
+  // console.log(req.body);
+  // console.log(req.params);
+
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return sequelize
+    .transaction()
+    .then(function(t) {
+      async.waterfall([
+        function(callback) {
+          return FaxAccount
+            .findOne({
+              where: {
+                id: req.params.id
+              },
+              include: [{
+                model: Trunk
+              }]
+            }, {
+              transaction: t
+            })
+            .then(function(faxAccount) {
+              callback(null, faxAccount);
+            })
+            .catch(function(err) {
+              callback(err);
+            });
+        },
+        function(faxAccount, callback) {
+          return Trunk
+            .findById(req.body.TrunkId, {
+              transaction: t
+            })
+            .then(function(trunk) {
+              return faxAccount
+                .updateAttributes({
+                  TrunkId: trunk.id
+                }, {
+                  transaction: t
+                })
+                .then(function(data) {
+                  callback(null, faxAccount, trunk);
+                })
+                .catch(function(err) {
+                  callback(err);
+                });
+            })
+            .catch(function(err) {
+              callback(err);
+            });
+        },
+        function(faxAccount, trunk, callback) {
+          return VoiceExtension
+            .findOne({
+              where: {
+                priority: 1,
+                exten: faxAccount.phone,
+                context: faxAccount.Trunk.context
+              }
+            }, {
+              transaction: t
+            })
+            .then(function(voiceExtension) {
+              if (!voiceExtension) {
+                callback(null, faxAccount);
+              }
+              return voiceExtension
+                .updateAttributes({
+                  exten: req.body.phone,
+                  context: trunk.context,
+                  TrunkId: req.body.UserId
+                }, {
+                  transaction: t
+                })
+                .then(function() {
+                  console.log('voiceExtension edited');
+                  callback(null, faxAccount);
+                })
+                .catch(function(err) {
+                  callback(err);
+                });
+            })
+            .catch(function(err) {
+              callback(err);
+            });
+        },
+        function(faxAccount, callback) {
+          var updated = _.merge(faxAccount, req.body);
+          return updated.save({
+              transaction: t
+            })
+            .then(function() {
+              console.log('faxAccount saved');
+              callback(null, faxAccount);
+            })
+            .catch(function(err) {
+              callback(err);
+            });
+        }
+      ], function(err, faxAccount) {
+        if (err) {
+          console.log(err);
+          t.rollback();
+          return res.status(404).send(err);
+        } else {
+          console.log('ok');
+          t.commit();
+          return res.status(201).send(faxAccount);
+        }
+      })
+    });
+};
+
+// Deletes a faxAccount from the DB.
+exports.destroy = function(req, res, next) {
+  FaxAccount
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(faxAccount) {
+      if (!faxAccount) {
+        return res.sendStatus(404);
+      }
+      faxAccount.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          next(err);
+        });
+    })
+    .catch(function(err) {
+      next(err);
+    });
+};
+
+exports.addApplications = function(req, res, next) {
+
+  FaxApplication
+    .findAll({
+      where: {
+        id: req.body.applications
+      }
+    })
+    .then(function(faxApplications) {
+      var tmpFaxApplications = faxApplications;
+
+      return sequelize.transaction(function(t) {
+        return FaxApplication.destroy({
+          where: {
+            id: req.body.applications
+          }
+        }, {
+          transaction: t
+        }).then(function() {
+
+          var sortedApplications = [];
+
+          for (var i = 0; i < req.body.applications.length; i++) {
+
+            var tmpFaxApplication = _.find(tmpFaxApplications, {
+              'id': req.body.applications[i]
+            });
+
+            if (tmpFaxApplication) {
+              tmpFaxApplication.priority = i + 1;
+              sortedApplications.push(tmpFaxApplication.dataValues);
+            }
+          }
+
+          return FaxApplication.bulkCreate(sortedApplications, {
+            transaction: t
+          }).then(function(data) {
+            return data;
+          });
+        });
+      }).then(function(result) {
+        return res.status(200).send(result);
+        // Transaction has been committed
+        // result is whatever the result of the promise chain returned to the transaction callback
+      }).catch(function(err) {
+        return handleError(res, err);
+        // Transaction has been rolled back
+        // err is whatever rejected the promise chain returned to the transaction callback
+      });
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  FaxAccount
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/fax_account/fax_account.socket.js b/server/api/fax_account/fax_account.socket.js
index 335899e..e28dde9 100644
--- a/server/api/fax_account/fax_account.socket.js
+++ b/server/api/fax_account/fax_account.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var fax_account = require('../../models').FaxAccount;
+
+exports.register = function(socket) {
+  fax_account.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  fax_account.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('fax_account:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('fax_account:remove', doc);
+}
diff --git a/server/api/fax_account/fax_account.spec.js b/server/api/fax_account/fax_account.spec.js
index 0acef82..0399e33 100644
--- a/server/api/fax_account/fax_account.spec.js
+++ b/server/api/fax_account/fax_account.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/fax/accounts', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/fax/accounts')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/fax_account/index.js b/server/api/fax_account/index.js
index c85a803..15886c9 100644
--- a/server/api/fax_account/index.js
+++ b/server/api/fax_account/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./fax_account.controller');
+var auth = require('../../auth/auth.service');
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.post('/:id/applications', controller.addApplications);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/', controller.bulkDestroy);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/fax_application/fax_application.controller.js b/server/api/fax_application/fax_application.controller.js
index 53c814f..59b09cb 100644
--- a/server/api/fax_application/fax_application.controller.js
+++ b/server/api/fax_application/fax_application.controller.js
@@ -1 +1,94 @@
\ No newline at end of file
+'use strict';
+var _ = require('lodash');
+var FaxApplication = require('../../models').FaxApplication;
+// Get list of fax_applications
+exports.index = function(req, res) {
+  FaxApplication.findAll({
+    where: req.query
+  }).then(function(fax_applications) {
+    return res.status(200).send(fax_applications);
+  }).catch(function(err) {
+    return handleError(res, err);
+  });
+};
+// Get a single fax_application
+exports.show = function(req, res) {
+  FaxApplication.findById(req.params.id).then(function(fax_application) {
+    if (!fax_application) {
+      return res.sendStatus(404);
+    }
+    return res.send(fax_application);
+  }).catch(function(err) {
+    return handleError(res, err);
+  });
+};
+// Creates a new fax_application in the DB.
+exports.create = function(req, res, next) {
+  console.log(req.body);
+  FaxApplication.max('priority', {
+    where: {
+      FaxAccountId: req.body.FaxAccountId
+    }
+  }).then(function(max) {
+    // SET PRIORITY
+    req.body.priority = max ? ++max : 1;
+    FaxApplication.create(req.body).then(function(faxApplication) {
+      return res.status(201).send(faxApplication);
+    }).catch(function(err) {
+      return next(err);
+    });
+    console.log(max);
+  }).catch(function(err) {
+    next(err);
+  });
+  // FaxApplication
+  // .create(req.body)
+  // .then(function(fax_application) {
+  //   return res.status(201).send(fax_application);
+  // })
+  // .catch(function(err) {
+  //   return handleError(res, err);
+  // });
+};
+// Updates an existing fax_application in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  FaxApplication.findById(req.params.id).then(function(fax_application) {
+    if (!fax_application) {
+      return res.sendStatus(404);
+    }
+    var updated = _.merge(fax_application, req.body);
+    updated.save().then(function() {
+      return res.status(200).send(fax_application);
+    }).catch(function(err) {
+      return handleError(res, err);
+    });
+  }).catch(function(err) {
+    return handleError(res, err);
+  });
+};
+// Deletes a fax_application from the DB.
+exports.destroy = function(req, res) {
+  FaxApplication.find({
+    where: {
+      id: req.params.id
+    }
+  }).then(function(fax_application) {
+    if (!fax_application) {
+      return res.sendStatus(404);
+    }
+    fax_application.destroy().then(function() {
+      return res.sendStatus(204);
+    }).catch(function(err) {
+      return handleError(res, err);
+    });
+  }).catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/fax_application/fax_application.socket.js b/server/api/fax_application/fax_application.socket.js
index 09d3c45..2a677d7 100644
--- a/server/api/fax_application/fax_application.socket.js
+++ b/server/api/fax_application/fax_application.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var fax_application = require('../../models').FaxApplication;
+
+exports.register = function(socket) {
+  fax_application.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  fax_application.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('fax_application:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('fax_application:remove', doc);
+}
diff --git a/server/api/fax_application/fax_application.spec.js b/server/api/fax_application/fax_application.spec.js
index c8c5e62..9eeaee8 100644
--- a/server/api/fax_application/fax_application.spec.js
+++ b/server/api/fax_application/fax_application.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/fax/applications', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/fax/applications')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/fax_application/index.js b/server/api/fax_application/index.js
index acc2519..b27e419 100644
--- a/server/api/fax_application/index.js
+++ b/server/api/fax_application/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./fax_application.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/fax_message/fax_message.ami.js b/server/api/fax_message/fax_message.ami.js
index ff5ff99..2d69e42 100644
--- a/server/api/fax_message/fax_message.ami.js
+++ b/server/api/fax_message/fax_message.ami.js
@@ -1 +1,64 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+
+var FaxMessage = require('../../models').FaxMessage;
+var Trunk = require('../../models').User;
+
+exports.register = function(ami) {
+
+  FaxMessage.afterCreate(function(doc) {
+    console.log('FaxMessage afterCreate');
+    onSave(ami, doc);
+  });
+};
+
+function onSave(ami, doc, cb) {
+  if (doc.status === 'NOT SENT' && !doc.actionid) {
+    // I have to send the fax with the Originate
+    var faxBody = JSON.parse(doc.body);
+    console.log('faxBody', faxBody);
+
+    Trunk
+      .findById(faxBody.trunk.id)
+      .then(function(trunk) {
+        var action = {
+          Action: 'originate',
+          CallerID: faxBody.from,
+          Channel: util.format('%s/%s@%s', faxBody.technology, faxBody.to, trunk.name),
+          Context: 'outbound-fax',
+          Exten: 's',
+          Priority: 1,
+          Async: 'true',
+          Variable: {
+            MAXRATE: faxBody.maxrate,
+            MINRATE: faxBody.minrate,
+            ECM: faxBody.ecm ? 'yes' : 'no',
+            LOCALID: faxBody.fax_localid,
+            FAXFILE: faxBody.fax_file,
+            // 'FAXFILE': 'test.tiff', // Scommentare se si fanno prove in locale!
+            FAXHEADER: faxBody.fax_header,
+            FAXUUID: faxBody.uuid
+          }
+        };
+
+        ami.action(action, function(err, res) {
+          if (err) {
+            doc.updateAttributes({
+              status: 'FAILED'
+            });
+          } else {
+            if (res.response && res.actionid) {
+              doc.updateAttributes({
+                actionid: res.actionid
+              });
+            }
+          }
+        });
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  }
+}
diff --git a/server/api/fax_message/fax_message.controller.js b/server/api/fax_message/fax_message.controller.js
index a45dd86..e354d5f 100644
--- a/server/api/fax_message/fax_message.controller.js
+++ b/server/api/fax_message/fax_message.controller.js
@@ -1 +1,422 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var fs = require('fs');
+var path = require('path');
+var uploadFile = require('upload-file');
+var uuidLib = require('node-uuid');
+var pdf = require('html-pdf');
+var spindrift = require('spindrift');
+var formidable = require('formidable');
+var exec = require('child_process').exec;
+var config = require('../../config/environment');
+
+var FaxMessage = require('../../models').FaxMessage;
+var FaxRoom = require('../../models').FaxRoom;
+var FaxAccount = require('../../models').FaxAccount;
+var Contact = require('../../models').Contact;
+
+// Get list of fax_messages
+exports.index = function(req, res) {
+  FaxMessage
+    .findAll({
+      where: req.query,
+      order: 'createdAt DESC',
+      include: [{
+        model: Contact,
+        as: 'From'
+      }, {
+        model: Contact,
+        as: 'To'
+      }, {
+        model: FaxRoom,
+        include: [{
+          model: FaxAccount
+        }]
+      }]
+    })
+    .then(function(fax_messages) {
+      return res.status(200).send(fax_messages);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single fax_message
+exports.show = function(req, res) {
+  FaxMessage
+    .findOne({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        model: Contact,
+        as: 'From'
+      }, {
+        model: Contact,
+        as: 'To'
+      }]
+    })
+    .then(function(fax_message) {
+      if (!fax_message) {
+        return res.sendStatus(404);
+      }
+      return res.send(fax_message);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new fax_message in the DB.
+exports.create = function(req, res, next) {
+  // FaxMessage
+  //   .create(req.body)
+  //   .then(function(fax_message) {
+  //     return res.status(201).send(fax_message);
+  //   })
+  //   .catch(function(err) {
+  //     return handleError(res, err);
+  //   });
+
+  var form = new formidable.IncomingForm();
+  // form.encoding = 'utf-8';
+  form.uploadDir = path.join(config.root, 'server/files/fax/outbound/original');
+  form.keepExtensions = true;
+  form.multiples = false;
+
+  if (req.body.onlyText) {
+    //There is not file, only text
+    // console.log(req.body);
+    if (req.body.html) {
+      //There is the html message -> convert
+      var uuid = uuidLib.v4()
+      var dest = path.join(config.root, 'server/files/fax/outbound/original', uuid + '.pdf');
+      pdf.create(req.body.html, {
+        "timeout": 30000
+      }).toFile(dest, function(err, result) {
+        console.log('Fax: HTML Converted');
+        convertPdfAndSendFax(req, res, uuid, req.body);
+      });
+    }
+  } else {
+    form.parse(req, function(err, fields, files) {
+      if (err) {
+        return handleError(res, err);
+      }
+
+      if (fields.html) {
+        //There is HTML with PDF
+        var uuid = uuidLib.v4();
+        var dest = path.join(config.root, 'server/files/fax/outbound/original', uuid + '.pdf');
+        pdf.create(fields.html, {
+          "timeout": 30000
+        }).toFile(dest, function(err, result) {
+          console.log('Fax: HTML Converted');
+          var pdfHTML = spindrift(result.filename);
+          var pdfUploaded = spindrift(files.file.path);
+          var uuid = uuidLib.v4();
+          spindrift.join(pdfHTML, pdfUploaded).pdfStream().pipe(fs.createWriteStream(path.join(config.root, 'server/files/fax/outbound/original', uuid + '.pdf')));
+          console.log('Fax: HTML joined with PDF uploaded');
+          convertPdfAndSendFax(req, res, uuid, fields);
+        });
+      } else {
+        //There is only PDF
+        var uuid = uuidLib.v4();
+        var dest = path.join(config.root, 'server/files/fax/outbound/original', uuid + '.pdf');
+        fs.rename(files.file.path, dest, function(err) {
+          if (err) {
+            console.error(err);
+            return handleError(res, req);
+            // return next(err);
+          }
+          console.log('Fax: PDF Rename');
+          convertPdfAndSendFax(req, res, uuid, fields);
+        });
+      }
+
+    });
+  }
+};
+
+function convertPdfAndSendFax(req, res, filenameInput, fields) {
+  var inputPath = path.join(config.root, 'server', 'files', 'fax', 'outbound', 'original', filenameInput + '.pdf');
+  var resultPath = path.join(config.root, 'server', 'files', 'fax', 'outbound', 'converted', filenameInput + '.tif');
+  var command = 'gs' + ' -q -dNOPAUSE -dBATCH -sDEVICE=tiffg4 ' + '-sPAPERSIZE=letter -sOutputFile=' + resultPath + ' ' + inputPath;
+  // console.log(inputPath, resultPath, command);
+
+  var uuidFax = uuidLib.v4();
+  FaxAccount
+    .findById(fields.accountId)
+    .then(function(faxAccount) {
+      // console.log('FaxAccount', faxAccount);
+
+      if (faxAccount) {
+        var faxBody = {
+          technology: 'SIP',
+          to: fields.toNum,
+          from: faxAccount.phone,
+          fax_file: resultPath,
+          fax_header: faxAccount.faxheader,
+          fax_localid: faxAccount.localid,
+          maxrate: faxAccount.maxrate,
+          minrate: faxAccount.minrate,
+          ecm: faxAccount.ecm,
+          uuid: uuidFax,
+          trunk: {
+            id: faxAccount.TrunkId
+          }
+        };
+
+        fs.exists(inputPath, function(exists) {
+          if (!exists) {
+            // console.log('File not exist');
+            handleError(res, new Error('File not exists'));
+          } else {
+            exec(command, function(err) {
+              if (err) {
+                console.error(err);
+                handleError(res, err);
+              } else {
+                console.log('Fax: PDF converted to TIFF');
+                fs.chmod(resultPath, 511, function(err) {
+                  if (fields.roomId) {
+                    // Room already exists -> create new message and associate
+                    FaxRoom
+                      .findOrCreate({
+                        where: {
+                          id: fields.roomId
+                        },
+                        defaults: {}
+                      })
+                      .spread(function(faxRoom, created) {
+                        // TODO Verify if we have to manage also the created
+                        FaxMessage
+                          .create({
+                            status: 'NOT SENT',
+                            read: true,
+                            filenamePDF: filenameInput + '.pdf',
+                            filename: filenameInput + '.tif',
+                            body: JSON.stringify(faxBody),
+                            uuid: uuidFax,
+                            FaxRoomId: faxRoom.id
+                          })
+                          .then(function(faxMessage) {
+                            // Fax message created, now association
+                            Contact
+                              .findOrCreate({
+                                where: {
+                                  phone: faxBody.from
+                                },
+                                defaults: {
+                                  phone: faxBody.from,
+                                  fullname: faxBody.from
+                                }
+                              })
+                              .spread(function(contact, created) {
+                                // console.log('Set relationship with from', created);
+                                faxMessage.setFrom(contact);
+                              });
+
+                            Contact
+                              .findOrCreate({
+                                where: {
+                                  phone: faxBody.to
+                                },
+                                defaults: {
+                                  phone: faxBody.to,
+                                  fullname: faxBody.to
+                                }
+                              })
+                              .spread(function(contact, created) {
+                                // console.log('Set relationship with to', created);
+                                faxMessage.setTo(contact);
+                              });
+
+                            return res.status(200).send(faxMessage);
+                          })
+                          .catch(function(err) {
+                            handleError(res, err);
+                            // console.error(err);
+                            // next(err);
+                          });
+                      });
+                  } else {
+                    // Room not exists -> create room, message and associate
+                    FaxRoom
+                      .create({
+                        status: 'OPEN',
+                        from: fields.toNum,
+                        FaxAccountId: fields.accountId,
+                        UserId: fields.userId
+                      })
+                      .then(function(faxRoom) {
+                        // Room created, now message
+                        FaxMessage
+                          .create({
+                            status: 'NOT SENT',
+                            read: true,
+                            filenamePDF: filenameInput + '.pdf',
+                            filename: filenameInput + '.tif',
+                            body: JSON.stringify(faxBody),
+                            uuid: uuidFax,
+                            FaxRoomId: faxRoom.id
+                          })
+                          .then(function(faxMessage) {
+                            // Fax message created, now association
+
+                            Contact
+                              .findOrCreate({
+                                where: {
+                                  phone: faxBody.from
+                                },
+                                defaults: {
+                                  phone: faxBody.from,
+                                  fullname: faxBody.from
+                                }
+                              })
+                              .spread(function(contact, created) {
+                                faxMessage.setFrom(contact);
+                              });
+
+                            Contact
+                              .findOrCreate({
+                                where: {
+                                  phone: faxBody.to
+                                },
+                                defaults: {
+                                  phone: faxBody.to,
+                                  fullname: faxBody.to
+                                }
+                              })
+                              .spread(function(contact, created) {
+                                faxMessage.setTo(contact);
+                              });
+
+                            return res.status(200).send(
+                              faxMessage);
+                          })
+                          .catch(function(err) {
+                            handleError(res, err);
+                          });
+                      })
+                      .catch(function(err) {
+                        handleError(res, err);
+                      });
+                  }
+                });
+              }
+            });
+          }
+        });
+      }
+    });
+}
+
+// Updates an existing fax_message in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  FaxMessage
+    .findOne({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        model: Contact,
+        as: 'From'
+      }, {
+        model: Contact,
+        as: 'To'
+      }]
+    })
+    .then(function(fax_message) {
+      if (!fax_message) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(fax_message, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(fax_message);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a fax_message from the DB.
+exports.destroy = function(req, res) {
+  FaxMessage
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(fax_message) {
+      if (!fax_message) {
+        return res.sendStatus(404);
+      }
+      fax_message.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.getContentFromMessage = function(req, res) {
+
+  FaxMessage
+    .findById(req.params.id)
+    .then(function(fax_message) {
+
+      // console.log(fax_message);
+
+      if (!fax_message) {
+        return res.sendStatus(404);
+      }
+
+      if (fax_message.filenamePDF) {
+
+        if (fax_message.status === 'RECEIVED') {
+          var pdfPath = path.join(config.root, 'server', 'files', 'fax', 'inbound', fax_message.filenamePDF);
+          fs.exists(pdfPath, function(exists) {
+            if (exists)
+              return res.sendFile(pdfPath);
+            else
+              return res.sendStatus(404);
+          });
+        } else if (fax_message.status === 'NOT SENT' || fax_message.directory ===
+          'SENT' || fax_message.status === 'FAILED') {
+          var pdfPath = path.join(config.root, 'server', 'files', 'fax', 'outbound', 'original', fax_message.filenamePDF);
+          fs.exists(pdfPath, function(exists) {
+            if (exists)
+              return res.sendFile(pdfPath);
+            else
+              return res.sendStatus(404);
+          });
+        }
+      } else {
+        res.sendStatus(404);
+      }
+
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/fax_message/fax_message.socket.js b/server/api/fax_message/fax_message.socket.js
index 50a41de..79a31ad 100644
--- a/server/api/fax_message/fax_message.socket.js
+++ b/server/api/fax_message/fax_message.socket.js
@@ -1 +1,85 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var FaxMessage = require('../../models').FaxMessage;
+var Contact = require('../../models').Contact;
+var FaxRoom = require('../../models').FaxRoom;
+var FaxAccount = require('../../models').FaxAccount;
+
+exports.register = function(socket) {
+  FaxMessage.afterCreate(function(doc) {
+    FaxMessage
+      .findOne({
+        where: {
+          id: doc.id
+        },
+        include: [{
+          model: Contact,
+          as: 'From'
+        }, {
+          model: Contact,
+          as: 'To'
+        }, {
+          model: FaxRoom,
+          include: [{
+            model: FaxAccount
+          }]
+        }]
+      })
+      .then(function(faxMessage) {
+        onSave(socket, faxMessage);
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  });
+
+  FaxMessage.afterUpdate(function(doc) {
+    FaxMessage
+      .findOne({
+        where: {
+          id: doc.id
+        },
+        include: [{
+          model: Contact,
+          as: 'From'
+        }, {
+          model: Contact,
+          as: 'To'
+        }, {
+          model: FaxRoom,
+          include: [{
+            model: FaxAccount
+          }]
+        }]
+      })
+      .then(function(faxMessage) {
+        onSave(socket, faxMessage);
+        onUpdate(socket, faxMessage);
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  });
+
+  FaxMessage.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('fax_message:save', doc);
+  // socket.emit('fax_message:' + doc.directory + ':save', doc);
+}
+
+function onUpdate(socket, doc, cb) {
+  socket.emit('fax_message:update', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('fax_message:remove', doc);
+  // socket.emit('fax_message:' + doc.directory + ':remove', doc);
+}
diff --git a/server/api/fax_message/fax_message.spec.js b/server/api/fax_message/fax_message.spec.js
index ceae706..d61f66e 100644
--- a/server/api/fax_message/fax_message.spec.js
+++ b/server/api/fax_message/fax_message.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/fax/messages', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/fax/messages')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/fax_message/index.js b/server/api/fax_message/index.js
index 802fd3e..b237a79 100644
--- a/server/api/fax_message/index.js
+++ b/server/api/fax_message/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./fax_message.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id/content', controller.getContentFromMessage);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/fax_queue/fax_queue.controller.js b/server/api/fax_queue/fax_queue.controller.js
index 01fa25b..65cffaf 100644
--- a/server/api/fax_queue/fax_queue.controller.js
+++ b/server/api/fax_queue/fax_queue.controller.js
@@ -1 +1,185 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var FaxQueue = require('../../models').FaxQueue;
+
+// Get list of fax_queues
+exports.index = function(req, res) {
+  return FaxQueue
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single fax_queue
+exports.show = function(req, res) {
+  FaxQueue
+    .findById(req.params.id)
+    .then(function(fax_queue) {
+      if (!fax_queue) {
+        return res.sendStatus(404);
+      }
+      return res.send(fax_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new fax_queue in the DB.
+exports.create = function(req, res) {
+  FaxQueue
+    .create(req.body)
+    .then(function(fax_queue) {
+      return res.status(201).send(fax_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing fax_queue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  FaxQueue
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(fax_queue) {
+      if (!fax_queue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(fax_queue, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(fax_queue);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a fax_queue from the DB.
+exports.destroy = function(req, res) {
+  FaxQueue
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(fax_queue) {
+      if (!fax_queue) {
+        return res.sendStatus(404);
+      }
+      fax_queue.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.addAgents = function(req, res, next) {
+
+  return FaxQueue
+    .findById(req.params.id)
+    .then(function(faxQueue) {
+      if (!faxQueue) {
+        return res.sendStatus(404);
+      }
+      return faxQueue
+        .addUsers(req.body.agents, {
+          penalty: req.body.penalty,
+          queue: faxQueue.name
+        }, {
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.showAgents = function(req, res) {
+  return FaxQueue
+    .findById(req.params.id)
+    .then(function(queue) {
+      if (!queue) {
+        return res.sendStatus(404);
+      }
+      return queue
+        .getUsers({
+          where: {
+            role: 'agent'
+          },
+          attributes: ['id', 'name', 'fullname', 'email']
+        });
+    })
+    .then(function(agents) {
+      return res.status(200).send(agents);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.removeAgents = function(req, res, next) {
+  return FaxQueue
+    .findById(req.params.id)
+    .then(function(queue) {
+      if (!queue) {
+        return res.sendStatus(404);
+      }
+      return queue
+        .removeUser(req.query.agents);
+    })
+    .then(function() {
+      return res.sendStatus(202);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  FaxQueue
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/fax_queue/fax_queue.socket.js b/server/api/fax_queue/fax_queue.socket.js
index fac783d..243ed07 100644
--- a/server/api/fax_queue/fax_queue.socket.js
+++ b/server/api/fax_queue/fax_queue.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var fax_queue = require('../../models').FaxQueue;
+
+exports.register = function(socket) {
+  fax_queue.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  fax_queue.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('fax_queue:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('fax_queue:remove', doc);
+}
diff --git a/server/api/fax_queue/fax_queue.spec.js b/server/api/fax_queue/fax_queue.spec.js
index 971d412..9148967 100644
--- a/server/api/fax_queue/fax_queue.spec.js
+++ b/server/api/fax_queue/fax_queue.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/fax/queues', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/fax/queues')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/fax_queue/index.js b/server/api/fax_queue/index.js
index 2e2bc22..9f1fac3 100644
--- a/server/api/fax_queue/index.js
+++ b/server/api/fax_queue/index.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./fax_queue.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/agents', auth.isAuthenticated(), controller.showAgents);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/agents', auth.isAuthenticated(), controller.addAgents);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/:id/agents', auth.isAuthenticated(), controller.removeAgents);
+
+module.exports = router;
diff --git a/server/api/fax_room/fax_room.controller.js b/server/api/fax_room/fax_room.controller.js
index 325a9de..a139fd5 100644
--- a/server/api/fax_room/fax_room.controller.js
+++ b/server/api/fax_room/fax_room.controller.js
@@ -1 +1,151 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var FaxRoom = require('../../models').FaxRoom;
+var FaxAccount = require('../../models').FaxAccount;
+var FaxMessage = require('../../models').FaxMessage;
+var Contact = require('../../models').Contact;
+var User = require('../../models').User;
+
+// Get list of fax_rooms
+exports.index = function(req, res) {
+  FaxRoom
+    .findAll({
+      include: [{
+        model: FaxMessage,
+        attributes: ['id']
+      }, {
+        model: FaxAccount
+      }, {
+        model: User
+      }]
+    })
+    .then(function(fax_rooms) {
+      return res.status(200).send(fax_rooms);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get list of agent mailRooms
+exports.agentIndex = function(req, res, next) {
+  FaxRoom
+    .findAll({
+      where: {
+        UserId: req.params.id
+      },
+      include: [{
+        model: FaxAccount
+      }, {
+        model: User
+      }]
+    })
+    .then(function(faxRooms) {
+      return res.status(200).send(faxRooms);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Get a single fax_room
+exports.show = function(req, res) {
+  FaxRoom
+    .findOne({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        model: FaxMessage,
+        include: [{
+          model: FaxRoom,
+          include: [{
+            model: FaxAccount
+          }]
+        }, {
+          model: Contact,
+          as: 'From'
+        }, {
+          model: Contact,
+          as: 'To'
+        }]
+      }, {
+        model: FaxAccount
+      }],
+      order: [
+        [FaxMessage, 'createdAt', 'DESC']
+      ]
+    })
+    .then(function(fax_room) {
+      if (!fax_room) {
+        return res.sendStatus(404);
+      }
+      return res.send(fax_room);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new fax_room in the DB.
+exports.create = function(req, res) {
+  FaxRoom
+    .create(req.body)
+    .then(function(fax_room) {
+      return res.status(201).send(fax_room);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing fax_room in the DB.
+exports.update = function(req, res) {
+  // if (req.body.id) {
+  //   delete req.body.id;
+  // }
+  FaxRoom
+    .findById(req.params.id)
+    .then(function(fax_room) {
+      if (!fax_room) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(fax_room, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(fax_room);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a fax_room from the DB.
+exports.destroy = function(req, res) {
+  FaxRoom
+    .findById(req.params.id)
+    .then(function(fax_room) {
+      if (!fax_room) {
+        return res.sendStatus(404);
+      }
+      fax_room.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/fax_room/fax_room.kue.js b/server/api/fax_room/fax_room.kue.js
index 28a931a..ba9c227 100644
--- a/server/api/fax_room/fax_room.kue.js
+++ b/server/api/fax_room/fax_room.kue.js
@@ -1 +1,64 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var User = require('../../models').User;
+var FaxRoom = require('../../models').FaxRoom;
+var FaxQueue = require('../../models').FaxQueue;
+var FaxAccount = require('../../models').FaxAccount;
+var FaxApplication = require('../../models').FaxApplication;
+
+exports.register = function(queue) {
+  FaxRoom.afterCreate(function(doc) {
+    onSave(queue, doc);
+  });
+}
+
+function onSave(queue, doc, cb) {
+  if (doc.status === 'NEW') {
+    FaxAccount
+      .findById(doc.FaxAccountId)
+      .then(function(faxAccount) {
+        faxAccount
+          .getFaxApplications({
+            include: [{
+              model: User,
+              attributes: ['id']
+            }, {
+              model: FaxQueue,
+              attributes: ['id', 'strategy', 'timeout'],
+              include: {
+                model: User,
+                attributes: ['id']
+              }
+            }],
+            order: [
+              ['priority']
+            ]
+          })
+          .then(function(faxApplications) {
+
+            var data = {
+              roomId: doc.id,
+              accountId: doc.FaxAccountId,
+              channel: 'FAX',
+              msg: {
+                from: doc.from
+              },
+              applications: faxApplications
+            };
+
+            // Create queue process
+            queue.create('fax', data).save();
+          })
+          .catch(function(err) {
+            console.error(err);
+          });
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  }
+}
diff --git a/server/api/fax_room/fax_room.socket.js b/server/api/fax_room/fax_room.socket.js
index 36f650a..92f71b0 100644
--- a/server/api/fax_room/fax_room.socket.js
+++ b/server/api/fax_room/fax_room.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var fax_room = require('../../models').FaxRoom;
+
+exports.register = function(socket) {
+  fax_room.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  fax_room.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('fax_room:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('fax_room:remove', doc);
+}
diff --git a/server/api/fax_room/fax_room.spec.js b/server/api/fax_room/fax_room.spec.js
index 2315f6e..1a5ae47 100644
--- a/server/api/fax_room/fax_room.spec.js
+++ b/server/api/fax_room/fax_room.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/fax/rooms', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/fax/rooms')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/fax_room/index.js b/server/api/fax_room/index.js
index 0cfaec0..73e938a 100644
--- a/server/api/fax_room/index.js
+++ b/server/api/fax_room/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./fax_room.controller');
+var license = require('../../config/license/license.service');
+
+var router = express.Router();
+
+router.get('/', license.isValidDate(), license.hasFax(), controller.index);
+router.get('/agent/:id', controller.agentIndex);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/freshdesk_account/freshdesk_account.controller.js b/server/api/freshdesk_account/freshdesk_account.controller.js
index ddfe36a..ae9ab8a 100644
--- a/server/api/freshdesk_account/freshdesk_account.controller.js
+++ b/server/api/freshdesk_account/freshdesk_account.controller.js
@@ -1 +1,239 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/freshdesk/accounts              ->  index
+ * POST    /api/freshdesk/accounts              ->  create
+ * GET     /api/freshdesk/accounts/:id          ->  show
+ * PUT     /api/freshdesk/accounts/:id          ->  update
+ * DELETE  /api/freshdesk/accounts/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var util = require('util');
+var _fd = require('freshdesk');
+
+var FreshdeskAccount = require('../../models').FreshdeskAccount;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of FreshdeskAccounts
+exports.index = function(req, res) {
+  var attributes = ['description', 'name', 'username', 'remoteUri'];
+  var per_page = req.query.per_page ? parseInt(req.query.per_page, 10) : 100;
+  var page = req.query.page ? parseInt(req.query.page, 10) : 0;
+
+  var query = {
+    where: {},
+    limit: per_page,
+    offset: page * per_page
+  };
+
+  _.forIn(req.query, function(value, key) {
+    switch (key) {
+      case 'per_page':
+      case 'page':
+        break;
+      case 'sort_by':
+        query.order = util.format('%s %s', req.query.sort_by, req.query.sort_order || 'ASC') || null;
+        break;
+      case 'sort_order':
+        break;
+      case '$':
+        query.where.$or = [];
+        attributes.forEach(function(attribute) {
+          var tmp = {};
+          tmp[attribute] = {
+            $like: '%' + value + '%'
+          };
+
+          query.where.$or.push(tmp);
+        });
+        break;
+      default:
+        query.where[key] = {
+          $like: {}
+        };
+        query.where[key].$like = '%' + value + '%';
+    }
+  });
+
+  FreshdeskAccount
+    .findAndCountAll(query)
+    .then(function(result) {
+
+      var total_pages = Math.ceil(result.count / per_page);
+      var next_page = total_pages > (query.offset + 1) ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page + 1) : null;
+      var previous_page = page > 0 ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page - 1) : null;
+
+      return res.status(200).send({
+        count: result.count,
+        rows: result.rows,
+        next_page: next_page,
+        previous_page: previous_page,
+        total_pages: total_pages
+      });
+
+    })
+    .catch(function(err) {
+      return res.status(500).send({
+        error: 'Something blew up!'
+      });
+    });
+}
+
+// Gets a single FreshdeskAccount from the DB
+exports.show = function(req, res) {
+  FreshdeskAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new FreshdeskAccount in the DB
+exports.create = function(req, res) {
+  FreshdeskAccount.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing FreshdeskAccount in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  FreshdeskAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a FreshdeskAccount from the DB
+exports.destroy = function(req, res) {
+  FreshdeskAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+exports.bulkDestroy = function(req, res) {
+  FreshdeskAccount
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.checkAccount = function(req, res) {
+  FreshdeskAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(account) {
+      var freshdesk = new _fd(account.remoteUri, account.apiKey);
+      freshdesk.listTickets(function(err, response, body) {
+        if (err) {
+          return res.sendStatus(400);
+        }
+        body = JSON.parse(body);
+        if (body.require_login) {
+          return res.sendStatus(400);
+        }
+        return res.sendStatus(200);
+      })
+    })
+    .catch(handleError(res));
+}
+
+exports.accountValidation = function(req, res) {
+  var where = {};
+  where[req.params.field] = req.body.value;
+  FreshdeskAccount
+    .findAndCountAll({
+      where: where
+    })
+    .then(function(result) {
+      if (result.count) {
+        return res.status(200).send({
+          isValid: false,
+          value: req.body.value
+        });
+      }
+      return res.status(200).send({
+        isValid: true,
+        value: req.body.value
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.getFields = function(req, res, next) {
+  FreshdeskAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(account) {
+      var Freshdesk = new _fd(account.remoteUri, account.apiKey);
+      Freshdesk.get('/ticket_fields.json', function(err, response, body) {
+        if (err) {
+          return next(err);
+        }
+        body = JSON.parse(body);
+        return res.status(200).send(body);
+      });
+    })
+    .catch(handleError(res));
+};
diff --git a/server/api/freshdesk_account/freshdesk_account.socket.js b/server/api/freshdesk_account/freshdesk_account.socket.js
index ba48045..2aabaae 100644
--- a/server/api/freshdesk_account/freshdesk_account.socket.js
+++ b/server/api/freshdesk_account/freshdesk_account.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var FreshdeskAccount = require('../../models').FreshdeskAccount;
+
+exports.register = function(socket) {
+  FreshdeskAccount.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  FreshdeskAccount.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('freshdesk_account:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('freshdesk_account:remove', doc);
+}
diff --git a/server/api/freshdesk_account/index.js b/server/api/freshdesk_account/index.js
index eed04fd..a7385b7 100644
--- a/server/api/freshdesk_account/index.js
+++ b/server/api/freshdesk_account/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./freshdesk_account.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/fields', auth.isAuthenticated(), controller.getFields);
+router.get('/:id/check', auth.isAuthenticated(), controller.checkAccount);
+router.post('/validate/:field', auth.isAuthenticated(), controller.accountValidation);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/freshdesk_account/index.spec.js b/server/api/freshdesk_account/index.spec.js
index 4a3c10e..6d7fc75 100644
--- a/server/api/freshdesk_account/index.spec.js
+++ b/server/api/freshdesk_account/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var freshdeskAccountCtrlStub = {
+  index: 'freshdeskAccountCtrl.index',
+  show: 'freshdeskAccountCtrl.show',
+  create: 'freshdeskAccountCtrl.create',
+  update: 'freshdeskAccountCtrl.update',
+  destroy: 'freshdeskAccountCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var freshdeskAccountIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './freshdesk_account.controller': freshdeskAccountCtrlStub
+});
+
+describe('FreshdeskAccount API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(freshdeskAccountIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/freshdesk/accounts', function() {
+
+    it('should route to freshdeskAccount.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'freshdeskAccountCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/freshdesk/accounts/:id', function() {
+
+    it('should route to freshdeskAccount.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'freshdeskAccountCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/freshdesk/accounts', function() {
+
+    it('should route to freshdeskAccount.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'freshdeskAccountCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/freshdesk/accounts/:id', function() {
+
+    it('should route to freshdeskAccount.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'freshdeskAccountCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/freshdesk/accounts/:id', function() {
+
+    it('should route to freshdeskAccount.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'freshdeskAccountCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/freshdesk/accounts/:id', function() {
+
+    it('should route to freshdeskAccount.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'freshdeskAccountCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/freshdesk_configuration/freshdesk_configuration.controller.js b/server/api/freshdesk_configuration/freshdesk_configuration.controller.js
index 6d4aa96..4c3d44d 100644
--- a/server/api/freshdesk_configuration/freshdesk_configuration.controller.js
+++ b/server/api/freshdesk_configuration/freshdesk_configuration.controller.js
@@ -1 +1,146 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/freshdesk/configurations              ->  index
+ * POST    /api/freshdesk/configurations              ->  create
+ * GET     /api/freshdesk/configurations/:id          ->  show
+ * PUT     /api/freshdesk/configurations/:id          ->  update
+ * DELETE  /api/freshdesk/configurations/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var FreshdeskConfiguration = require('../../models').FreshdeskConfiguration;
+var FreshdeskField = require('../../models').FreshdeskField;
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of FreshdeskConfigurations
+exports.index = function(req, res) {
+  FreshdeskConfiguration.findAll({
+      where: req.query
+    })
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single FreshdeskConfiguration from the DB
+exports.show = function(req, res) {
+  FreshdeskConfiguration.find({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        model: FreshdeskField,
+        as: 'Subject'
+      }, {
+        model: FreshdeskField,
+        as: 'Description'
+      }, {
+        model: FreshdeskField,
+        as: 'Field'
+      }]
+    })
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new FreshdeskConfiguration in the DB
+exports.create = function(req, res) {
+  FreshdeskConfiguration.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing FreshdeskConfiguration in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  FreshdeskConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a FreshdeskConfiguration from the DB
+exports.destroy = function(req, res) {
+  FreshdeskConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+exports.addConfigurationField = function(req, res, next) {
+  // console.log(req.params);
+  var freshdeskConfiguration;
+  FreshdeskConfiguration
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(salesforce_configuration) {
+      freshdeskConfiguration = salesforce_configuration;
+      return FreshdeskField.create(req.body)
+    })
+    .then(function(freshdeskField) {
+      switch (req.params.type) {
+        case 'subject':
+          return [freshdeskConfiguration.addSubject(freshdeskField), freshdeskField];
+        case 'description':
+          return [freshdeskConfiguration.addDescription(freshdeskField), freshdeskField];
+        case 'field':
+          return [freshdeskConfiguration.addField(freshdeskField), freshdeskField];
+      }
+    })
+    .spread(function(data, freshdeskField) {
+      return res.status(201).json(freshdeskField);
+    })
+    .catch(handleError(res));
+};
diff --git a/server/api/freshdesk_configuration/freshdesk_configuration.socket.js b/server/api/freshdesk_configuration/freshdesk_configuration.socket.js
index cabe8c1..4ce7f30 100644
--- a/server/api/freshdesk_configuration/freshdesk_configuration.socket.js
+++ b/server/api/freshdesk_configuration/freshdesk_configuration.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var FreshdeskConfiguration = require('../../models').FreshdeskConfiguration;
+
+exports.register = function(socket) {
+  FreshdeskConfiguration.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  FreshdeskConfiguration.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('freshdesk_configuration:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('freshdesk_configuration:remove', doc);
+}
diff --git a/server/api/freshdesk_configuration/index.js b/server/api/freshdesk_configuration/index.js
index ecd1fdc..e08945f 100644
--- a/server/api/freshdesk_configuration/index.js
+++ b/server/api/freshdesk_configuration/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./freshdesk_configuration.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.post('/:id/:type', auth.isAuthenticated(), controller.addConfigurationField);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/freshdesk_configuration/index.spec.js b/server/api/freshdesk_configuration/index.spec.js
index ebf6801..b297c23 100644
--- a/server/api/freshdesk_configuration/index.spec.js
+++ b/server/api/freshdesk_configuration/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var freshdeskConfigurationCtrlStub = {
+  index: 'freshdeskConfigurationCtrl.index',
+  show: 'freshdeskConfigurationCtrl.show',
+  create: 'freshdeskConfigurationCtrl.create',
+  update: 'freshdeskConfigurationCtrl.update',
+  destroy: 'freshdeskConfigurationCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var freshdeskConfigurationIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './freshdesk_configuration.controller': freshdeskConfigurationCtrlStub
+});
+
+describe('FreshdeskConfiguration API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(freshdeskConfigurationIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/freshdesk/configurations', function() {
+
+    it('should route to freshdeskConfiguration.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'freshdeskConfigurationCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/freshdesk/configurations/:id', function() {
+
+    it('should route to freshdeskConfiguration.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'freshdeskConfigurationCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/freshdesk/configurations', function() {
+
+    it('should route to freshdeskConfiguration.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'freshdeskConfigurationCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/freshdesk/configurations/:id', function() {
+
+    it('should route to freshdeskConfiguration.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'freshdeskConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/freshdesk/configurations/:id', function() {
+
+    it('should route to freshdeskConfiguration.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'freshdeskConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/freshdesk/configurations/:id', function() {
+
+    it('should route to freshdeskConfiguration.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'freshdeskConfigurationCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/freshdesk_field/freshdesk_field.controller.js b/server/api/freshdesk_field/freshdesk_field.controller.js
index f44e166..4a35db8 100644
--- a/server/api/freshdesk_field/freshdesk_field.controller.js
+++ b/server/api/freshdesk_field/freshdesk_field.controller.js
@@ -1 +1,104 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/freshdesk/fields              ->  index
+ * POST    /api/freshdesk/fields              ->  create
+ * GET     /api/freshdesk/fields/:id          ->  show
+ * PUT     /api/freshdesk/fields/:id          ->  update
+ * DELETE  /api/freshdesk/fields/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var FreshdeskField = require('../../models').FreshdeskField;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of FreshdeskFields
+exports.index = function(req, res) {
+  FreshdeskField.findAll()
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single FreshdeskField from the DB
+exports.show = function(req, res) {
+  FreshdeskField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new FreshdeskField in the DB
+exports.create = function(req, res) {
+  FreshdeskField.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing FreshdeskField in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  FreshdeskField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a FreshdeskField from the DB
+exports.destroy = function(req, res) {
+  FreshdeskField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
diff --git a/server/api/freshdesk_field/freshdesk_field.socket.js b/server/api/freshdesk_field/freshdesk_field.socket.js
index 2ae970b..b351442 100644
--- a/server/api/freshdesk_field/freshdesk_field.socket.js
+++ b/server/api/freshdesk_field/freshdesk_field.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var FreshdeskField = require('../../models').FreshdeskField;
+
+exports.register = function(socket) {
+  FreshdeskField.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  FreshdeskField.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('freshdesk_field:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('freshdesk_field:remove', doc);
+}
diff --git a/server/api/freshdesk_field/index.js b/server/api/freshdesk_field/index.js
index 0e063ce..d17268f 100644
--- a/server/api/freshdesk_field/index.js
+++ b/server/api/freshdesk_field/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./freshdesk_field.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/freshdesk_field/index.spec.js b/server/api/freshdesk_field/index.spec.js
index c6eaf2b..b5c0fb8 100644
--- a/server/api/freshdesk_field/index.spec.js
+++ b/server/api/freshdesk_field/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var freshdeskFieldCtrlStub = {
+  index: 'freshdeskFieldCtrl.index',
+  show: 'freshdeskFieldCtrl.show',
+  create: 'freshdeskFieldCtrl.create',
+  update: 'freshdeskFieldCtrl.update',
+  destroy: 'freshdeskFieldCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var freshdeskFieldIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './freshdesk_field.controller': freshdeskFieldCtrlStub
+});
+
+describe('FreshdeskField API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(freshdeskFieldIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/freshdesk/fields', function() {
+
+    it('should route to freshdeskField.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'freshdeskFieldCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/freshdesk/fields/:id', function() {
+
+    it('should route to freshdeskField.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'freshdeskFieldCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/freshdesk/fields', function() {
+
+    it('should route to freshdeskField.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'freshdeskFieldCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/freshdesk/fields/:id', function() {
+
+    it('should route to freshdeskField.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'freshdeskFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/freshdesk/fields/:id', function() {
+
+    it('should route to freshdeskField.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'freshdeskFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/freshdesk/fields/:id', function() {
+
+    it('should route to freshdeskField.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'freshdeskFieldCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/integration/index.js b/server/api/integration/index.js
index 11500f3..5789126 100644
--- a/server/api/integration/index.js
+++ b/server/api/integration/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./integration.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/integration/integration.controller.js b/server/api/integration/integration.controller.js
index 708aef6..cc5e706 100644
--- a/server/api/integration/integration.controller.js
+++ b/server/api/integration/integration.controller.js
@@ -1 +1,178 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var uploadFile = require('upload-file');
+var config = require('../../config/environment');
+var path = require('path');
+var sr = require('simple-random');
+var admZip = require('adm-zip');
+var str2json = require('string-to-json');
+var fs = require('fs');
+var Util = require('../../config/util');
+
+var Integration = require('../../models').Integration;
+
+// Get list of Integration
+exports.index = function(req, res, next) {
+  return Integration
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single integration
+exports.show = function(req, res) {
+  Integration
+    .findById(req.params.id)
+    .then(function(integration) {
+      if (!integration) {
+        return res.sendStatus(404);
+      }
+      return res.send(integration);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new integration in the DB.
+exports.create = function(req, res) {
+  var upload = new uploadFile({
+    dest: path.join(config.root, 'server/files/integrations'),
+    maxFileSize: 5 * 1000000,
+    maxNumberOfFiles: 1,
+    minNumberOfFiles: 1,
+    acceptFileTypes: /(\.|\/)(zip)$/i,
+    messages: {
+      maxNumberOfFiles: "Max number of files upload exceeded",
+      acceptFileTypes: "Only zip format accepted",
+      maxFileSize: "The maximum file size is 5 Mb",
+      invalidRequest: "Invalid request"
+    }
+  });
+
+  upload.on('end', function(fields, files) {
+    var zip = new admZip(files.file.path);
+
+    var manifest = false;
+    var zipEntries = zip.getEntries();
+    zipEntries.forEach(function(zipEntry) {
+      if (zipEntry.name === 'manifest.json') {
+        var str = zipEntry.getData().toString('utf8');
+        console.log('uploadZip', str);
+        try {
+          manifest = JSON.parse(str);
+          console.log('uploadZip', manifest);
+          Integration
+            .create(_.merge(files.file, manifest))
+            .then(function(integration) {
+
+              // Extract All Zip and overwrite old directory
+              zip.extractAllTo(path.join(config.root,
+                'server/files/integrations'), true);
+
+              return res.status(201).send(integration);
+            })
+            .catch(function(err) {
+              return handleError(res, err);
+            });
+        } catch (e) {
+          console.log(e);
+          var err = {
+            errors: [{
+              type: 'json',
+              message: e
+            }]
+          };
+          return handleError(res, err);
+        }
+      }
+    });
+
+    fs.unlinkSync(files.file.path);
+
+    if (!manifest) {
+      var err = {
+        errors: [{
+          type: 'manifest',
+          message: 'manifest.json not found'
+        }]
+      };
+      return handleError(res, err);
+    }
+  });
+
+  upload.on('error', function(e) {
+    var err = {
+      errors: [{
+        type: 'upload',
+        message: e
+      }]
+    };
+    return handleError(res, err);
+  });
+
+  upload.parse(req);
+};
+
+// Updates an existing integration in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  Integration
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(integration) {
+      if (!integration) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(integration, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(integration);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a integration from the DB.
+exports.destroy = function(req, res) {
+  Integration
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(integration) {
+      if (!integration) {
+        return res.sendStatus(404);
+      }
+      integration.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/integration/integration.socket.js b/server/api/integration/integration.socket.js
index f0945f2..a79af43 100644
--- a/server/api/integration/integration.socket.js
+++ b/server/api/integration/integration.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Integration = require('../../models').Integration;
+
+exports.register = function(socket) {
+  Integration.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Integration.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  Integration.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('integration:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('integration:remove', doc);
+}
diff --git a/server/api/integration/integration.spec.js b/server/api/integration/integration.spec.js
index 9fca24c..0d4146d 100644
--- a/server/api/integration/integration.spec.js
+++ b/server/api/integration/integration.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/integrations', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/integrations')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/interval/index.js b/server/api/interval/index.js
index f72b376..b9f5ab2 100644
--- a/server/api/interval/index.js
+++ b/server/api/interval/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./interval.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/interval/interval.controller.js b/server/api/interval/interval.controller.js
index 3d9c837..584391e 100644
--- a/server/api/interval/interval.controller.js
+++ b/server/api/interval/interval.controller.js
@@ -1 +1,167 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Interval = require('../../models').Interval;
+var VoiceExtension = require('../../models').VoiceExtension;
+var sequelize = require('../../models').sequelize;
+
+var Util = require('../../config/util');
+
+// Get list of interval
+exports.index = function(req, res, next) {
+  return Interval
+    .scope('intervals', 'subintervals')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single interval
+exports.show = function(req, res) {
+  return Interval
+    .scope('subintervals')
+    .findById(req.params.id)
+    .then(function(interval) {
+      if (!interval) {
+        return res.sendStatus(404);
+      }
+      return res.send(interval);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new interval in the DB.
+exports.create = function(req, res) {
+  return Interval
+    .scope('subintervals')
+    .create(req.body)
+    .then(function(interval) {
+      return res.status(201).send(interval);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing interval in the DB.
+exports.update = function(req, res) {
+  console.log(req.body);
+  return sequelize.transaction(function(t) {
+      if (req.body.id) {
+        delete req.body.id;
+      }
+      return Interval
+        .update(req.body, {
+          where: {
+            id: req.params.id
+          },
+          individualHooks: true,
+          transaction: t
+        })
+        .then(function() {
+          if (req.body.SubIntervals) {
+            return Interval
+              .destroy({
+                where: {
+                  IntervalId: req.params.id
+                },
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          console.log('are there intervals');
+          if (req.body.SubIntervals && req.body.SubIntervals.length) {
+            return req.params.id;
+          }
+        })
+        .then(bulkCreateSubIntervals(req.body.SubIntervals, t))
+        .all();
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+function bulkCreateSubIntervals(subIntervals, t) {
+  return function(mainInterval) {
+    if (mainInterval) {
+      console.log('yes there are');
+      var bulkSubInts = [];
+      subIntervals.forEach(function(elem) {
+        elem.IntervalId = mainInterval;
+        bulkSubInts.push(Interval.create(elem, {
+          transaction: t
+        }));
+      });
+      return bulkSubInts;
+    } else {
+      return [];
+    }
+  }
+}
+
+// Deletes a interval from the DB.
+exports.destroy = function(req, res) {
+  return sequelize.transaction(function(t) {
+      return Interval
+        .destroy({
+          where: {
+            id: req.params.id
+          },
+          individualHooks: true,
+          transaction: t
+        });
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(sequelize.ForeignKeyConstraintError, function(err) {
+      err.message = 'The Interval is associated with one or more routes, delete them first';
+      err.translatedMessage = 'MESSAGE_INTERVAL_ROUTE_ASSOCIATED'
+      return handleError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes multiple intervals from the DB.
+exports.bulkDestroy = function(req, res) {
+  return sequelize.transaction(function(t) {
+      return Interval
+        .destroy({
+          where: {
+            id: req.query.ids
+          },
+          individualHooks: true,
+          transaction: t
+        });
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(sequelize.ForeignKeyConstraintError, function(err) {
+      err.message = 'The Interval is associated with one or more routes, delete them first';
+      err.translatedMessage = 'MESSAGE_INTERVAL_ROUTE_ASSOCIATED'
+      return handleError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/interval/interval.socket.js b/server/api/interval/interval.socket.js
index 0967e4e..d8aee27 100644
--- a/server/api/interval/interval.socket.js
+++ b/server/api/interval/interval.socket.js
@@ -1 +1,31 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Interval = require('../../models').Interval;
+
+exports.register = function(socket) {
+  Interval.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Interval.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+  Interval.afterUpdate(function(doc) {
+    onUpdate(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('interval:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('interval:remove', doc);
+}
+
+function onUpdate(socket, doc, cb) {
+  socket.emit('interval:update', doc);
+}
diff --git a/server/api/interval/interval.spec.js b/server/api/interval/interval.spec.js
index 6b69557..280cb64 100644
--- a/server/api/interval/interval.spec.js
+++ b/server/api/interval/interval.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/intervals', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/intervals')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/jscripty_project/index.js b/server/api/jscripty_project/index.js
index e16d9ab..1ecf642 100644
--- a/server/api/jscripty_project/index.js
+++ b/server/api/jscripty_project/index.js
@@ -1 +1,33 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./jscripty_project.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/download', controller.download);
+router.get('/:id/sessions', controller.showSessions);
+router.get('/:id/sessions/:sessionId', controller.showSession);
+
+router.get('/:id/questions', controller.showQuestions);
+
+router.get('/:id/questions/summary', controller.showSummary);
+router.get('/:id/questions/detail', controller.showDetail);
+
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/sessions', auth.isAuthenticated(), controller.createSession);
+router.post('/:id/sessions/:sessionId/questions', auth.isAuthenticated(), controller.createQuestions);
+
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.put('/:id/sessions/:sessionId', auth.isAuthenticated(), controller.updateSession);
+
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+
+module.exports = router;
diff --git a/server/api/jscripty_project/index.spec.js b/server/api/jscripty_project/index.spec.js
index 12dd761..3860e91 100644
--- a/server/api/jscripty_project/index.spec.js
+++ b/server/api/jscripty_project/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var jscriptyProjectCtrlStub = {
+  index: 'jscriptyProjectCtrl.index',
+  show: 'jscriptyProjectCtrl.show',
+  create: 'jscriptyProjectCtrl.create',
+  update: 'jscriptyProjectCtrl.update',
+  destroy: 'jscriptyProjectCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var jscriptyProjectIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './jscripty_project.controller': jscriptyProjectCtrlStub
+});
+
+describe('JscriptyProject API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(jscriptyProjectIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/jscripty/projects', function() {
+
+    it('should route to jscriptyProject.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'jscriptyProjectCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/jscripty/projects/:id', function() {
+
+    it('should route to jscriptyProject.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'jscriptyProjectCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/jscripty/projects', function() {
+
+    it('should route to jscriptyProject.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'jscriptyProjectCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/jscripty/projects/:id', function() {
+
+    it('should route to jscriptyProject.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'jscriptyProjectCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/jscripty/projects/:id', function() {
+
+    it('should route to jscriptyProject.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'jscriptyProjectCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/jscripty/projects/:id', function() {
+
+    it('should route to jscriptyProject.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'jscriptyProjectCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/jscripty_project/jscripty_project.controller.js b/server/api/jscripty_project/jscripty_project.controller.js
index d501ee1..7550756 100644
--- a/server/api/jscripty_project/jscripty_project.controller.js
+++ b/server/api/jscripty_project/jscripty_project.controller.js
@@ -1 +1,389 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/jscripty/projects              ->  index
+ * POST    /api/jscripty/projects              ->  create
+ * GET     /api/jscripty/projects/:id          ->  show
+ * PUT     /api/jscripty/projects/:id          ->  update
+ * DELETE  /api/jscripty/projects/:id          ->  destroy
+ */
+
+'use strict';
+var xml = require('xml2js');
+
+var _ = require('lodash');
+
+var JscriptyProject = require('../../models').JscriptyProject;
+var Util = require('../../config/util');
+var sequelize = require('../../models').sequelize;
+var ReportJscriptySession = require('../../models').ReportJscriptySession;
+var ReportJscriptyQuestion = require('../../models').ReportJscriptyQuestion;
+var ReportJscriptyInput = require('../../models').ReportJscriptyInput;
+var Promise = require('bluebird');
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
+
+
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of JscriptyProjects
+
+exports.index = function(req, res, next) {
+  return JscriptyProject
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.show = function(req, res) {
+  return JscriptyProject
+    .findById(req.params.id)
+    .then(function(jscripty_project) {
+      if (!jscripty_project) {
+        return res.sendStatus(404);
+      }
+      return res.send(jscripty_project);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Gets a all ReportJscriptySession from the DB
+exports.showSessions = function(req, res) {
+    return ReportJscriptySession
+      .scope({
+        method: ['questionsAndInput', req.params.id]
+      })
+      .findAndCountAll(Util.getQuery(req.query))
+      .then(handleEntityNotFound(res))
+      .then(responseWithResult(res))
+      .catch(function(err) {
+        console.log(err);
+        return handleError(res, err);
+      });
+  }
+  // Gets a one ReportJscriptySession from the DB
+exports.showSession = function(req, res) {
+  return ReportJscriptySession
+    .findAll({
+      where: {
+        projectId: req.params.id,
+        session_id: req.params.sessionId
+      },
+      include: [{
+        model: ReportJscriptyQuestion,
+        include: [{
+          model: ReportJscriptyInput
+        }]
+      }]
+
+    })
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+}
+
+// Gets a all ReportJscriptyQuestion from the DB
+exports.showQuestions = function(req, res) {
+  return ReportJscriptyQuestion
+    .findAll({
+      where: {
+        projectId: req.params.id
+      },
+      include: [{
+        model: ReportJscriptyInput
+      }],
+
+    })
+    .then(function(jscripty_questions) {
+      if (!jscripty_questions) {
+        return res.sendStatus(404);
+      }
+      return res.status(200).send(jscripty_questions);
+    })
+    .then(handleEntityNotFound(res))
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+}
+
+
+// Gets summary of ReportJscriptyQuestion from the DB
+
+exports.showSummary = function(req, res) {
+    return ReportJscriptyQuestion
+      .scope({
+        method: ['questionSummary', req.params.id]
+      })
+      .findAndCountAll(Util.getQuery(req.query))
+      .then(handleEntityNotFound(res))
+      .then(function(jscripty_summary) {
+        return res.status(200).send(jscripty_summary);
+      })
+      .catch(function(err) {
+        console.log(err);
+        return handleError(res, err);
+      });
+
+  }
+  // Gets detail of ReportJscriptyQuestion from the DB
+exports.showDetail = function(req, res) {
+
+    return ReportJscriptyQuestion
+      .findAll({
+
+        where: {
+          projectId: req.params.id,
+          question_type: 'question'
+        },
+        attributes: ['question_id', 'question_label', 'question_text', 'question_answer', [sequelize.fn('count', sequelize.col('question_answer')),
+          'count'
+        ]],
+
+        group: ['question_id', 'question_label', 'question_text', 'question_answer']
+
+
+      })
+      .then(function(jscripty_questions) {
+
+        return res.status(201).send(jscripty_questions);
+
+      })
+
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+  }
+  // Creates a new JscriptyProject in the DB
+exports.create = function(req, res) {
+  return JscriptyProject
+    .create(req.body)
+    .then(function(jscripty_project) {
+      return res.status(201).send(jscripty_project);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      if (err.fields.name) {
+        err.message = 'Project name already exists';
+        err.translatedMessage = 'MESSAGE_EXIST_PROJECT'
+      }
+      return handleError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new ReportJscriptySession in the DB
+exports.createSession = function(req, res) {
+    ReportJscriptySession
+      .create(req.body)
+      .then(function(jscripty_session) {
+        return res.status(201).send(jscripty_session);
+      })
+      .catch(function(err) {
+        return handleError(res, err);
+      });
+
+  }
+  // Creates a new ReportJscriptyQuestions in the DB
+exports.createQuestions = function(req, res) {
+    ReportJscriptyQuestion
+      .destroy({
+        where: {
+          sessionId: req.params.sessionId
+        }
+      })
+      .then(function() {
+        console.log('req.body', req.body);
+        var _promises = [];
+
+        req.body.forEach(function(question) {
+          _promises.push(ReportJscriptyQuestion.create(question, {
+            include: [{
+              model: ReportJscriptyInput
+            }]
+          }));
+        });
+
+        return Promise
+          .all(_promises);
+      })
+      .then(function(jscripty_question) {
+        return res.status(201).send(jscripty_question);
+      })
+      .catch(function(err) {
+        return handleError(res, err);
+      });
+  }
+  // Updates an existing JscriptyProject in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  JscriptyProject
+    .findById(req.params.id)
+    .then(function(jscripty_project) {
+      if (!jscripty_project) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(jscripty_project, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(jscripty_project);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Updates an existing ReportJscriptySession in the DB
+exports.updateSession = function(req, res) {
+
+  ReportJscriptySession
+    .update(req.body, {
+      where: {
+        session_id: req.params.sessionId
+      }
+
+    })
+    .then(function(jscripty_session) {
+      return res.status(200).send(jscripty_session);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+}
+
+// Deletes a JscriptyProject from the DB
+exports.destroy = function(req, res) {
+  JscriptyProject
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(jscripty_project) {
+      if (!jscripty_project) {
+        return res.sendStatus(404);
+      }
+      jscripty_project.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a jscripty_project from the DB.
+exports.bulkDestroy = function(req, res) {
+  JscriptyProject
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+exports.download = function(req, res, next) {
+  JscriptyProject
+    .findById(req.params.id)
+    .then(function(jscripty_project) {
+      if (!jscripty_project) {
+        return res.sendStatus(404);
+      }
+      console.log(req.query.filename);
+      res.set({
+        "Content-Disposition": "attachment; filename=\"" + req.query.filename +
+          ".xml\"",
+        "Content-Type": "txt/xml"
+      });
+      return res.send(jscripty_project.production);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// exports.projectJson = function(req, res, next) {
+//   JscriptyProject
+//     .findById(req.params.id)
+//     .then(function(jscripty_project) {
+//       if (!jscripty_project) {
+//         return res.sendStatus(404);
+//       }
+//
+//       xml.parseString(jscripty_project.production, {
+//         normalizeTags: true,
+//         explicitArray: false
+//       }, function(err, result) {
+//         console.log(result);
+//         return res.status(200).send(result);
+//
+//       });
+//     })
+//     .catch(function(err) {
+//       return handleError(res, err);
+//     });
+// };
diff --git a/server/api/jscripty_project/jscripty_project.socket.js b/server/api/jscripty_project/jscripty_project.socket.js
index 6217c86..4f5b49e 100644
--- a/server/api/jscripty_project/jscripty_project.socket.js
+++ b/server/api/jscripty_project/jscripty_project.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var JscriptyProject = require('../../models').JscriptyProject;
+
+exports.register = function(socket) {
+  JscriptyProject.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  JscriptyProject.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('jscripty_project:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('jscripty_project:remove', doc);
+}
diff --git a/server/api/license/index.js b/server/api/license/index.js
index 04e25d2..3eb5928 100644
--- a/server/api/license/index.js
+++ b/server/api/license/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./license.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+// router.get('/:id', auth.isAuthenticated(), controller.show);
+// router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/request', auth.isAuthenticated(), controller.request);
+// router.put('/:id', auth.isAuthenticated(), controller.update);
+// router.patch('/:id', auth.isAuthenticated(), controller.update);
+// router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/license/license.controller.js b/server/api/license/license.controller.js
index 323cc9b..3d38afa 100644
--- a/server/api/license/license.controller.js
+++ b/server/api/license/license.controller.js
@@ -1 +1,161 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/licenses              ->  index
+ * POST    /api/licenses              ->  create
+ * GET     /api/licenses/:id          ->  show
+ * PUT     /api/licenses/:id          ->  update
+ * DELETE  /api/licenses/:id          ->  destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var rp = require('request-promise');
+
+var config = require('../../config/environment');
+var license = require('../../config/license/license.service');
+var Settings = require('../../models').Settings;
+
+var demoLicense = {
+  users: 1,
+  agents: 1,
+  telephones: 0,
+  chat: false,
+  mail: false,
+  fax: false,
+  description: 'DEMO LICENSE'
+};
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      if (entity.license) {
+        entity.dataValues.license = license.decrypt(entity.dataValues.license);
+        entity.dataValues.license = JSON.parse(entity.dataValues.license);
+      } else {
+        entity.dataValues.license = demoLicense;
+      }
+
+      entity.dataValues.license.token = license.getUuid();
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      entity = {
+        dataValues: {
+          license: demoLicense
+        }
+      };
+
+      entity.dataValues.license.token = license.getUuid();
+      res.status(201).json(entity);
+      return null;
+    }
+
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity
+      .updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity
+        .destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Licenses
+exports.index = function(req, res) {
+  // License.findAll()
+  //   .then(responseWithResult(res))
+  //   .catch(handleError(res));
+
+  Settings
+    .findById(1)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single License from the DB
+exports.show = function(req, res) {
+  // License.findById(req.params.id)
+  //   .then(handleEntityNotFound(res))
+  //   .then(responseWithResult(res))
+  //   .catch(handleError(res));
+}
+
+// Creates a new License in the DB
+exports.create = function(req, res) {
+  // License.create(req.body)
+  //   .then(responseWithResult(res, 201))
+  //   .catch(handleError(res));
+}
+
+// Creates a new License in the DB
+exports.request = function(req, res) {
+  // License.create(req.body)
+  //   .then(responseWithResult(res, 201))
+  //   .catch(handleError(res));
+  return rp({
+      method: 'POST',
+      uri: config.provisioning + '/servers/' + license.getUuid() + '/request',
+      body: req.body,
+      json: true // Automatically stringifies the body to JSON
+    })
+    .then(function(data) {
+      console.log(data);
+      res.status(201).send(data);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return res.status(err.statusCode || 500).send(err);
+    });
+  // .catch(handleError(res));
+}
+
+// Updates an existing License in the DB
+exports.update = function(req, res) {
+  // if (req.body.id) {
+  //   delete req.body.id;
+  // }
+  // License.findById(req.params.id)
+  //   .then(handleEntityNotFound(res))
+  //   .then(saveUpdates(req.body))
+  //   .then(responseWithResult(res))
+  //   .catch(handleError(res));
+}
+
+// Deletes a License from the DB
+exports.destroy = function(req, res) {
+  // License.findById(req.params.id)
+  //   .then(handleEntityNotFound(res))
+  //   .then(removeEntity(res))
+  //   .catch(handleError(res));
+}
diff --git a/server/api/license/license.socket.js b/server/api/license/license.socket.js
index 9d46a54..fb7f746 100644
--- a/server/api/license/license.socket.js
+++ b/server/api/license/license.socket.js
@@ -1 +1,24 @@
-var _0xdf26=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x72\x65\x67\x69\x73\x74\x65\x72","\x6C\x69\x63\x65\x6E\x73\x65\x3A\x73\x61\x76\x65","\x65\x6D\x69\x74","\x6C\x69\x63\x65\x6E\x73\x65\x3A\x72\x65\x6D\x6F\x76\x65"];_0xdf26[0];exports[_0xdf26[1]]=function(_0x89fex1){};function onSave(_0x89fex1,_0x89fex3,_0x89fex4){_0x89fex1[_0xdf26[3]](_0xdf26[2],_0x89fex3)}function onRemove(_0x89fex1,_0x89fex3,_0x89fex4){_0x89fex1[_0xdf26[3]](_0xdf26[4],_0x89fex3)}
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+// var License = require('../../models').License;
+
+exports.register = function (socket) {
+  // License.afterCreate(function(doc) {
+  //   onSave(socket, doc);
+  // });
+  // License.afterDestroy(function(doc) {
+  //   onRemove(socket, doc);
+  // });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('license:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('license:remove', doc);
+}
diff --git a/server/api/mail_account/index.js b/server/api/mail_account/index.js
index c9ab84b..92b8162 100644
--- a/server/api/mail_account/index.js
+++ b/server/api/mail_account/index.js
@@ -1 +1,23 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_account.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/applications', auth.isAuthenticated(), controller.showApplications);
+router.post('/', auth.isAuthenticated(), controller.create);
+// router.post('/:id/applications', auth.isAuthenticated(), controller.createApplications);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.put('/:id/applications', auth.isAuthenticated(), controller.updateApplications);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/:id/applications', auth.isAuthenticated(), controller.bulkDestroyApplications);
+router.delete('/:id/applications/:appId', auth.isAuthenticated(), controller.destroyApplication);
+
+
+module.exports = router;
diff --git a/server/api/mail_account/mail_account.controller.js b/server/api/mail_account/mail_account.controller.js
index b600b8d..87e8593 100644
--- a/server/api/mail_account/mail_account.controller.js
+++ b/server/api/mail_account/mail_account.controller.js
@@ -1 +1,242 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var async = require('async');
+
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var User = require('../../models').User;
+var MailQueue = require('../../models').MailQueue;
+var MailAccount = require('../../models').MailAccount;
+var MailServerIn = require('../../models').MailServerIn;
+var MailServerOut = require('../../models').MailServerOut;
+var MailApplication = require('../../models').MailApplication;
+
+// Get list of mailAccounts
+exports.index = function(req, res) {
+  return MailAccount
+    .scope('default')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailAccount
+exports.show = function(req, res, next) {
+  return MailAccount
+    .scope('default')
+    .findById(req.params.id)
+    .then(function(mailAccount) {
+      if (!mailAccount) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailAccount);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailAccount
+exports.showApplications = function(req, res, next) {
+  return MailAccount
+    .scope('default')
+    .findById(req.params.id)
+    .then(function(mailAccount) {
+      if (!mailAccount) {
+        return res.sendStatus(404);
+      }
+      return mailAccount
+        .getMailApplications(_.merge(Util.getQuery(req.query), {
+          include: [{
+            model: MailQueue,
+            attributes: ['id', 'name']
+          }, {
+            model: User,
+            attributes: ['id', 'name', 'online']
+          }]
+        }));
+    })
+    .then(function(mailApplications) {
+      return res.send({
+        count: mailApplications.length,
+        rows: mailApplications
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new mailAccount in the DB.
+exports.create = function(req, res, next) {
+  return sequelize.transaction(function(t) {
+      return MailAccount
+        .create(req.body, {
+          include: [{
+            model: MailServerIn
+          }, {
+            model: MailServerOut
+          }],
+          transaction: t
+        });
+    }).then(function(mailAccount) {
+      return res.status(201).send(mailAccount);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing mailAccount in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return MailAccount
+    .findById(req.params.id)
+    .then(function(mailAccount) {
+      if (!mailAccount) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailAccount, req.body);
+      return updated
+        .save();
+    })
+    .then(function(mailAccount) {
+      return res.status(200).send(mailAccount);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing mailAccount in the DB.
+exports.updateApplications = function(req, res) {
+
+  var priority = 0;
+  var apps = req.body;
+
+  apps.forEach(function(app) {
+    delete app.id;
+    delete app.createdAt;
+    delete app.updatedAt;
+
+    app.priority = ++priority;
+  });
+
+  return sequelize.transaction(function(t) {
+    return MailApplication
+      .destroy({
+        where: {
+          MailAccountId: req.params.id
+        },
+        individualHooks: true,
+        transaction: t
+      }).then(function(mailApplication) {
+        return MailApplication
+          .bulkCreate(apps, {
+            individualHooks: true,
+            transaction: t
+          });
+      });
+  }).then(function(mailAccounts) {
+    return res.status(200).send({
+      count: mailAccounts.length,
+      rows: mailAccounts
+    });
+  }).catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+// Deletes a mailAccount from the DB.
+exports.destroy = function(req, res, next) {
+  return MailAccount
+    .findById(req.params.id)
+    .then(function(mailAccount) {
+      if (!mailAccount) {
+        return res.sendStatus(404);
+      }
+
+      return mailAccount
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailAccount from the DB.
+exports.destroyApplication = function(req, res, next) {
+  return MailApplication
+    .findById(req.params.appId)
+    .then(function(mailApplication) {
+      if (!mailApplication) {
+        return res.sendStatus(404);
+      }
+
+      return mailApplication
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return MailAccount
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroyApplications = function(req, res) {
+  return MailApplication
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_account/mail_account.socket.js b/server/api/mail_account/mail_account.socket.js
index dfe746f..5b51c79 100644
--- a/server/api/mail_account/mail_account.socket.js
+++ b/server/api/mail_account/mail_account.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var MailAccount = require('../../models').MailAccount;
+
+exports.register = function(socket) {
+  MailAccount.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  MailAccount.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  MailAccount.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_account:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_account:remove', doc);
+}
diff --git a/server/api/mail_application/index.js b/server/api/mail_application/index.js
index 32fed73..3c132bb 100644
--- a/server/api/mail_application/index.js
+++ b/server/api/mail_application/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_application.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/mail_application/mail_application.controller.js b/server/api/mail_application/mail_application.controller.js
index a9a2255..260557e 100644
--- a/server/api/mail_application/mail_application.controller.js
+++ b/server/api/mail_application/mail_application.controller.js
@@ -1 +1,111 @@
\ No newline at end of file
+'use strict';
+var _ = require('lodash');
+var MailApplication = require('../../models').MailApplication;
+// Get list of mailApplications
+exports.index = function(req, res, next) {
+  MailApplication
+    .findAll({
+      where: req.query,
+      order: 'priority',
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(mailApplications) {
+      return res.status(200).send(mailApplications);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Get a single mailApplication
+exports.show = function(req, res, next) {
+  MailApplication
+    .findById(req.params.id, {
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(mailApplication) {
+      if (!mailApplication) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailApplication);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Creates a new mailApplication in the DB.
+exports.create = function(req, res, next) {
+  MailApplication
+    .max('priority', {
+      where: {
+        MailAccountId: req.body.MailAccountId
+      }
+    }).then(function(max) {
+      // SET PRIORITY
+      req.body.priority = max ? ++max : 1;
+      MailApplication
+        .create(req.body)
+        .then(function(mailApplication) {
+          return res.status(201).send(mailApplication);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Updates an existing mailApplication in the DB.
+exports.update = function(req, res, next) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  MailApplication
+    .findById(req.params.id)
+    .then(function(mailApplication) {
+      if (!mailApplication) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailApplication, req.body);
+      updated
+        .save()
+        .then(function() {
+          return res.status(200).send(mailApplication);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Deletes a mailApplication from the DB.
+exports.destroy = function(req, res, next) {
+  MailApplication
+    .findById(req.params.id)
+    .then(function(mailApplication) {
+      if (!mailApplication) {
+        return res.sendStatus(404);
+      }
+      mailApplication
+        .destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_application/mail_application.socket.js b/server/api/mail_application/mail_application.socket.js
index c26bcdf..ead0c90 100644
--- a/server/api/mail_application/mail_application.socket.js
+++ b/server/api/mail_application/mail_application.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var mailApplication = require('../../models').MailApplication;
+
+exports.register = function(socket) {
+  mailApplication.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  mailApplication.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_application:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_application:remove', doc);
+}
diff --git a/server/api/mail_application/mail_application.spec.js b/server/api/mail_application/mail_application.spec.js
index 364f9ff..023b99b 100644
--- a/server/api/mail_application/mail_application.spec.js
+++ b/server/api/mail_application/mail_application.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/mail/applications', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/mail/applications')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/mail_attachment/index.js b/server/api/mail_attachment/index.js
index d438b27..f154de4 100644
--- a/server/api/mail_attachment/index.js
+++ b/server/api/mail_attachment/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_attachment.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/mail_attachment/mail_attachment.controller.js b/server/api/mail_attachment/mail_attachment.controller.js
index ff92eaf..72d2b7b 100644
--- a/server/api/mail_attachment/mail_attachment.controller.js
+++ b/server/api/mail_attachment/mail_attachment.controller.js
@@ -1 +1,100 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var path = require('path');
+var fs = require('fs');
+var formidable = require('formidable');
+var Promise = require('bluebird');
+
+var config = require('../../config/environment');
+var MailAttachment = require('../../models').MailAttachment;
+
+// Get list of mailAttachments
+exports.index = function(req, res, next) {
+  return MailAttachment
+    .findAll()
+    .then(function(mailAttachments) {
+      return res.status(200).send(mailAttachments);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Get a single mailAttachment
+exports.show = function(req, res, next) {
+  return MailAttachment
+    .findById(req.params.id)
+    .then(function(mailAttachment) {
+      return res.download(mailAttachment.path, mailAttachment.name);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Creates a new mailAttachment in the DB.
+exports.create = function(req, res, next) {
+
+  var form = new formidable.IncomingForm();
+  form.uploadDir = path.join(config.root, 'server', 'files', 'attachments');
+  form.keepExtensions = true;
+  form.multiples = true;
+  form.hash = true;
+
+  form.parse(req, function(err, form, wrap) {
+    if (err) {
+      return res.status(500).send(err);
+    }
+    return res.status(201).send({
+      size: wrap.file.size,
+      path: wrap.file.path,
+      name: wrap.file.name,
+      type: wrap.file.type,
+      basename: path.basename(wrap.file.path)
+    });
+  });
+};
+
+// Updates an existing mailAttachment in the DB.
+exports.update = function(req, res, next) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  MailAttachment
+    .findById(req.params.id)
+    .then(function(mailAttachment) {
+      if (!mailAttachment) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailAttachment, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(mailAttachment);
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Deletes a mailAttachment from the DB.
+exports.destroy = function(req, res, next) {
+  var unlink = Promise.promisify(require("fs").unlink);
+  var _path = path.join(config.root, 'server', 'files', 'attachments', req.params.id);
+
+  return unlink(_path)
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_attachment/mail_attachment.socket.js b/server/api/mail_attachment/mail_attachment.socket.js
index a4f6e63..65c9085 100644
--- a/server/api/mail_attachment/mail_attachment.socket.js
+++ b/server/api/mail_attachment/mail_attachment.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var mail_attachment = require('../../models').MailAttachment;
+
+exports.register = function(socket) {
+  mail_attachment.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  mail_attachment.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_attachment:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_attachment:remove', doc);
+}
diff --git a/server/api/mail_message/index.js b/server/api/mail_message/index.js
index 89bde1c..58b5988 100644
--- a/server/api/mail_message/index.js
+++ b/server/api/mail_message/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_message.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/', auth.isAuthenticated(), controller.update);
+router.put('/delete/bulk', auth.isAuthenticated(), controller.bulkDelete);
+router.put('/delete/:id', auth.isAuthenticated(), controller.update);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/mail_message/mail_message.controller.js b/server/api/mail_message/mail_message.controller.js
index 7f9205d..0105703 100644
--- a/server/api/mail_message/mail_message.controller.js
+++ b/server/api/mail_message/mail_message.controller.js
@@ -1 +1,179 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var async = require('async');
+
+var htmlToText = require('html-to-text');
+var formidable = require('formidable');
+var path = require('path');
+
+var config = require('../../config/environment');
+var sequelize = require('../../models').sequelize;
+var MailMessage = require('../../models').MailMessage;
+var MailAttachment = require('../../models').MailAttachment;
+var MailRoom = require('../../models').MailRoom;
+var MailAccount = require('../../models').MailAccount;
+var ContactEmail = require('../../models').ContactEmail;
+var Contact = require('../../models').Contact;
+var User = require('../../models').User;
+
+// Get list of mail_messages
+exports.index = function(req, res, next) {
+  var params = {
+    order: 'createdAt DESC',
+    include: [{
+      all: true,
+      include: [{
+        all: true
+      }]
+    }]
+  };
+
+  switch (req.user.role) {
+    case 'admin':
+      params.where = req.query;
+      break;
+    case 'agent':
+      break;
+    default:
+  }
+
+  return MailMessage
+    .findAll(params)
+    .then(function(mail_messages) {
+      return res.status(200).send(mail_messages);
+    })
+    .catch(function(err) {
+      return handleError(res, err, next);
+    });
+};
+
+// Get a single mailMessage
+exports.show = function(req, res, next) {
+  return MailMessage
+    .findById(req.params.id, {
+      include: [{
+        model: MailAttachment
+      }, {
+        model: User,
+        attributes: ['id', 'name', 'fullname', 'email']
+      }]
+    })
+    .then(function(mailMessage) {
+      if (!mailMessage) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailMessage);
+    })
+    .catch(function(err) {
+      return handleError(res, err, next);
+    });
+};
+
+// Creates a new mailMessage in the DB.
+exports.create = function(req, res, next) {
+  req.body.UserId = req.user.id;
+  req.body.text = req.body.text || htmlToText.fromString(req.body.html, {
+    wordwrap: false
+  });
+
+  return sequelize.transaction().then(function(t) {
+    return MailMessage
+      .create(req.body, {
+        include: [{
+          model: MailAttachment
+        }],
+        transaction: t,
+        // user: req.user
+      }).then(function(mailMessage) {
+        t.commit();
+        return res.status(201).send(mailMessage);
+      }).catch(function(err) {
+        console.error(err);
+        t.rollback();
+        return handleError(res, err);
+      });
+  });
+};
+
+// Updates an existing mailMessage in the DB.
+exports.update = function(req, res, next) {
+
+  MailMessage
+    .findById(req.params.id, {
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(mailMessage) {
+      if (!mailMessage) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailMessage, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(updated);
+        })
+        .catch(function(err) {
+          return handleError(res, err, next);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err, next);
+    });
+};
+
+// Deletes a mailMessage from the DB.
+exports.destroy = function(req, res, next) {
+  MailMessage
+    .findById(req.params.id)
+    .then(function(mailMessage) {
+      if (!mailMessage) {
+        return res.sendStatus(404);
+      }
+      mailMessage.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err, next);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err, next);
+    });
+};
+
+// Deletes a mailMessage from the DB.
+exports.bulkDelete = function(req, res, next) {
+  MailMessage
+    .bulkUpdate({
+      where: {
+        id: {
+          $in: req.body.id
+        }
+      },
+      attributes: {
+        directory: 'TRASH'
+      }
+    })
+    .then(function(mailMessage) {
+      if (!mailMessage) {
+        return res.sendStatus(404);
+      }
+      mailMessage.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err, next);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err, next);
+    });
+};
+
+function handleError(res, err, next) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_message/mail_message.socket.js b/server/api/mail_message/mail_message.socket.js
index b068d73..64a5b96 100644
--- a/server/api/mail_message/mail_message.socket.js
+++ b/server/api/mail_message/mail_message.socket.js
@@ -1 +1,31 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var MailMessage = require('../../models').MailMessage;
+
+exports.register = function(socket) {
+  MailMessage.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+
+  MailMessage.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+
+  MailMessage.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  if (socket.userId === doc.UserId || socket.role === 'admin') {
+    socket.emit('mail_message:save', doc);
+  }
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_message:remove', doc);
+}
diff --git a/server/api/mail_queue/index.js b/server/api/mail_queue/index.js
index 1cebf5a..1252097 100644
--- a/server/api/mail_queue/index.js
+++ b/server/api/mail_queue/index.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_queue.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/agents', auth.isAuthenticated(), controller.showAgents);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/agents', auth.isAuthenticated(), controller.addAgents);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/:id/agents', auth.isAuthenticated(), controller.removeAgents);
+
+module.exports = router;
diff --git a/server/api/mail_queue/mail_queue.controller.js b/server/api/mail_queue/mail_queue.controller.js
index caf0185..70cd358 100644
--- a/server/api/mail_queue/mail_queue.controller.js
+++ b/server/api/mail_queue/mail_queue.controller.js
@@ -1 +1,212 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var MailQueue = require('../../models').MailQueue;
+
+// Get list of mailQueues
+exports.index = function(req, res) {
+  return MailQueue
+    .scope('default')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailQueue
+exports.show = function(req, res) {
+  return MailQueue
+    .scope('default')
+    .findById(req.params.id)
+    .then(function(mailQueue) {
+      if (!mailQueue) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailQueue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new mailQueue in the DB.
+exports.create = function(req, res) {
+  return MailQueue
+    .create(req.body)
+    .then(function(mailQueue) {
+      return res.status(201).send(mailQueue);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing mailQueue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return MailQueue
+    .findById(req.params.id)
+    .then(function(mailQueue) {
+      if (!mailQueue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailQueue, req.body);
+      return updated
+        .save();
+    })
+    .then(function(mailQueue) {
+      return res.status(200).send(mailQueue);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing user_has_mailQueue in the DB.
+exports.addAgents = function(req, res, next) {
+  return MailQueue
+    .findById(req.params.id)
+    .then(function(mailQueue) {
+      if (mailQueue) {
+        return mailQueue
+          .addUsers(req.body.agents, {
+            penalty: req.body.penalty,
+            queue: mailQueue.name
+          }, {
+            individualHooks: true
+          });
+      } else {
+        throw new Error('MailQueue not found');
+      }
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.removeAgents = function(req, res) {
+  return MailQueue
+    .findById(req.params.id)
+    .then(function(mailQueue) {
+      if (mailQueue) {
+        return mailQueue
+          .removeUsers(req.body.agents, {
+            individualHooks: true
+          });
+      } else {
+        throw new Error('MailQueue not found');
+      }
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailQueue from the DB.
+exports.destroy = function(req, res) {
+  var _mailQueue;
+
+  return MailQueue
+    .findById(req.params.id)
+    .then(function(mailQueue) {
+      _mailQueue = mailQueue;
+      if (!mailQueue) {
+        return res.sendStatus(404);
+      }
+      return mailQueue
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(sequelize.ForeignKeyConstraintError, function(err) {
+      return Util.errorManager.handleForeignKeyConstraintError(res, 'queue', _mailQueue.name, err);
+    })
+    .catch(function(err) {
+      console.error('err', err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return MailQueue
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.showAgents = function(req, res) {
+  return MailQueue
+    .findById(req.params.id)
+    .then(function(queue) {
+      if (!queue) {
+        return res.sendStatus(404);
+      }
+      return queue
+        .getUsers({
+          where: {
+            role: 'agent'
+          },
+          attributes: ['id', 'name', 'fullname', 'email']
+        });
+    })
+    .then(function(agents) {
+      return res.status(200).send(agents);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.removeAgents = function(req, res, next) {
+  return MailQueue
+    .findById(req.params.id)
+    .then(function(queue) {
+      if (!queue) {
+        return res.sendStatus(404);
+      }
+      return queue
+        .removeUser(req.query.agents);
+    })
+    .then(function() {
+      return res.sendStatus(202);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_queue/mail_queue.socket.js b/server/api/mail_queue/mail_queue.socket.js
index 1ea58ce..3518b62 100644
--- a/server/api/mail_queue/mail_queue.socket.js
+++ b/server/api/mail_queue/mail_queue.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var MailQueue = require('../../models').MailQueue;
+
+exports.register = function(socket) {
+  MailQueue.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  MailQueue.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_queue:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_queue:remove', doc);
+}
diff --git a/server/api/mail_room/index.js b/server/api/mail_room/index.js
index f458043..91ef299 100644
--- a/server/api/mail_room/index.js
+++ b/server/api/mail_room/index.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_room.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+// TODO endpoint to filter just NEW/OPEN
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/agent/:id', auth.isAuthenticated(), controller.agentIndex);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/agents', controller.showAgents);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/messages', auth.isAuthenticated(), controller.createMessage);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.put('/:id/agents', auth.isAuthenticated(), controller.updateAgents);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/mail_room/mail_room.controller.js b/server/api/mail_room/mail_room.controller.js
index f89d3b5..0d5f5bb 100644
--- a/server/api/mail_room/mail_room.controller.js
+++ b/server/api/mail_room/mail_room.controller.js
@@ -1 +1,231 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var htmlToText = require('html-to-text');
+
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var User = require('../../models').User;
+var Agent = require('../../models').User;
+var Contact = require('../../models').Contact;
+var MailRoom = require('../../models').MailRoom;
+var MailMessage = require('../../models').MailMessage;
+var MailAccount = require('../../models').MailAccount;
+var MailAttachment = require('../../models').MailAttachment;
+
+// Get list of mailRooms
+exports.index = function(req, res, next) {
+  switch (req.user.role) {
+    case 'agent':
+
+      return MailRoom
+        .scope('default', {
+          method: ['agent', req.user.id]
+        })
+        .findAndCountAll(Util.getQuery(req.query))
+        .then(function(result) {
+          return res.status(200).send(result);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+
+    default:
+
+      return MailRoom
+        .scope('default')
+        .findAndCountAll(Util.getQuery(req.query))
+        .then(function(result) {
+          return res.status(200).send(result);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+  }
+};
+
+// Get list of agent mailRooms
+exports.agentIndex = function(req, res, next) {
+  return MailRoom
+    .findAll({
+      where: {
+        UserId: req.params.id
+      },
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(mailRooms) {
+      return res.status(200).send(mailRooms);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailRoom
+exports.show = function(req, res, next) {
+  return MailRoom
+    .scope('default')
+    .findById(req.params.id)
+    .then(function(mailRoom) {
+      if (!mailRoom) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailRoom);
+    })
+    .catch(function(err) {
+      console.error(err);
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailRoom
+exports.showAgents = function(req, res, next) {
+  return MailRoom
+    .findById(req.params.id)
+    .then(function(mailRoom) {
+      if (!mailRoom) {
+        return res.sendStatus(404);
+      }
+
+      return mailRoom
+        .getUsers({
+          attributes: ['id', 'name', 'fullname', 'email', 'role', 'internal']
+        });
+    })
+    .then(function(users) {
+      return res.send(users);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new mailRoom in the DB.
+exports.create = function(req, res, next) {
+  return MailRoom
+    .create(req.body)
+    .then(function(mailRoom) {
+      return res.status(201).send(mailRoom);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new mailMessage in the DB.
+exports.createMessage = function(req, res, next) {
+  req.body.UserId = req.user.id;
+  req.body.MailRoomId = req.params.id;
+  req.body.attachment = (req.body.MailAttachments && req.body.MailAttachments.length) ? true : false;
+  req.body.text = req.body.text || htmlToText.fromString(req.body.html, {
+    wordwrap: false
+  });
+
+  return sequelize.transaction().then(function(t) {
+    return MailMessage
+      .create(req.body, {
+        include: [{
+          model: MailAttachment
+        }],
+        transaction: t,
+        user: req.user
+      }).then(function(mailMessage) {
+        t.commit();
+        return res.status(201).send(mailMessage);
+      }).catch(function(err) {
+        console.error(err);
+        t.rollback();
+        return handleError(res, err);
+      });
+  });
+};
+
+// Updates an existing mailRoom in the DB.
+exports.update = function(req, res, next) {
+  return MailRoom
+    .findById(req.params.id)
+    .then(function(mailRoom) {
+      if (!mailRoom) {
+        return res.sendStatus(404);
+      }
+      // CHECK IF MAIL IS ALREADY ASSIGNED
+      if (mailRoom.jobId) {
+        throw new Error('Mail already assigned');
+      }
+      var updated = _.merge(mailRoom, req.body);
+
+      return updated
+        .save();
+    })
+    .then(function(mailRoom) {
+      return res.status(200).send(mailRoom);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailRoom
+exports.updateAgents = function(req, res, next) {
+  return MailRoom
+    .findById(req.params.id)
+    .then(function(mailRoom) {
+      if (!mailRoom) {
+        return res.sendStatus(404);
+      }
+
+      return mailRoom
+        .setUsers(req.body.agents, {
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailRoom from the DB.
+exports.destroy = function(req, res, next) {
+  return MailRoom
+    .findById(req.params.id)
+    .then(function(mailRoom) {
+      if (!mailRoom) {
+        return res.sendStatus(404);
+      }
+      return mailRoom.destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return MailRoom
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_room/mail_room.socket.js b/server/api/mail_room/mail_room.socket.js
index 8f95a3b..3f12a1c 100644
--- a/server/api/mail_room/mail_room.socket.js
+++ b/server/api/mail_room/mail_room.socket.js
@@ -1 +1,43 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+
+var MailMessage = require('../../models').MailMessage;
+var MailRoom = require('../../models').MailRoom;
+var User = require('../../models').User;
+
+exports.register = function(socket) {
+  MailRoom.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+
+  MailRoom.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+
+  MailRoom.afterUpdate(function(doc) {
+    MailRoom
+      .findById(doc.id, {
+        include: [{
+          all: true
+        }]
+      })
+      .then(function(mailRoom) {
+        onSave(socket, mailRoom);
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_room:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_room:remove', doc);
+}
diff --git a/server/api/mail_server_in/index.js b/server/api/mail_server_in/index.js
index fe3fc77..adb146b 100644
--- a/server/api/mail_server_in/index.js
+++ b/server/api/mail_server_in/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_server_in.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/mail_server_in/mail_server_in.controller.js b/server/api/mail_server_in/mail_server_in.controller.js
index edac6cc..27fda21 100644
--- a/server/api/mail_server_in/mail_server_in.controller.js
+++ b/server/api/mail_server_in/mail_server_in.controller.js
@@ -1 +1,96 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var MailServerIn = require('../../models').MailServerIn;
+
+// Get list of mail_server_ins
+exports.index = function(req, res) {
+  return MailServerIn
+    .findAll()
+    .then(function(mail_server_ins) {
+      return res.status(200).send(mail_server_ins);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailServerIn
+exports.show = function(req, res) {
+  return MailServerIn
+    .findById(req.params.id)
+    .then(function(mailServerIn) {
+      if (!mailServerIn) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailServerIn);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new mailServerIn in the DB.
+exports.create = function(req, res) {
+  return MailServerIn
+    .create(req.body)
+    .then(function(mailServerIn) {
+      return res.status(201).send(mailServerIn);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing mailServerIn in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return MailServerIn
+    .findById(req.params.id)
+    .then(function(mailServerIn) {
+      if (!mailServerIn) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailServerIn, req.body);
+      return updated
+        .save();
+    })
+    .then(function(mailServerIn) {
+      return res.status(200).send(mailServerIn);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailServerIn from the DB.
+exports.destroy = function(req, res) {
+  return MailServerIn
+    .findById(req.params.id)
+    .then(function(mailServerIn) {
+      if (!mailServerIn) {
+        return res.sendStatus(404);
+      }
+      return mailServerIn
+        .destroy();
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_server_in/mail_server_in.socket.js b/server/api/mail_server_in/mail_server_in.socket.js
index e934afd..811977e 100644
--- a/server/api/mail_server_in/mail_server_in.socket.js
+++ b/server/api/mail_server_in/mail_server_in.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var MailServerIn = require('../../models').MailServerIn;
+
+exports.register = function(socket) {
+  MailServerIn.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  MailServerIn.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  MailServerIn.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_server_in:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_server_in:remove', doc);
+}
diff --git a/server/api/mail_server_out/index.js b/server/api/mail_server_out/index.js
index 718bccf..526e713 100644
--- a/server/api/mail_server_out/index.js
+++ b/server/api/mail_server_out/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_server_out.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/service', auth.isAuthenticated(), controller.indexService);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/test', auth.isAuthenticated(), controller.test);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/test', auth.isAuthenticated(), controller.test);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/mail_server_out/mail_server_out.controller.js b/server/api/mail_server_out/mail_server_out.controller.js
index f8a8eec..f2c852d 100644
--- a/server/api/mail_server_out/mail_server_out.controller.js
+++ b/server/api/mail_server_out/mail_server_out.controller.js
@@ -1 +1,162 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var MailServerOut = require('../../models').MailServerOut;
+var MailMessage = require('../../models').MailMessage;
+var sequelize = require('../../models').sequelize;
+
+// Get list of mailServerOuts
+exports.index = function(req, res) {
+  return MailServerOut
+    .findAll()
+    .then(function(mailServerOuts) {
+      return res.status(200).send(mailServerOuts);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.indexService = function(req, res) {
+  return MailServerOut
+    .findOne({
+      where: {
+        service: true
+      }
+    })
+    .then(function(mailServerOut) {
+      return res.status(200).send(mailServerOut);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailServerOut
+exports.show = function(req, res) {
+  return MailServerOut
+    .findById(req.params.id)
+    .then(function(mailServerOut) {
+      if (!mailServerOut) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailServerOut);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new mailServerOut in the DB.
+exports.create = function(req, res) {
+  return MailServerOut
+    .create(req.body)
+    .then(function(mailServerOut) {
+      return res.status(201).send(mailServerOut);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing mailServerOut in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return MailServerOut
+    .findById(req.params.id)
+    .then(function(mailServerOut) {
+      if (!mailServerOut) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailServerOut, req.body);
+      return updated
+        .save();
+    })
+    .then(function(mailServerOut) {
+      return res.status(200).send(mailServerOut);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailServerOut from the DB.
+exports.destroy = function(req, res) {
+  return MailServerOut
+    .findById(req.params.id)
+    .then(function(mailServerOut) {
+      if (!mailServerOut) {
+        return res.sendStatus(404);
+      }
+
+      return mailServerOut
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.test = function(req, res) {
+  return sequelize
+    .transaction()
+    .then(function(t) {
+      var _mailServerOut;
+      var _mailMessage;
+
+      return MailServerOut
+        .findById(req.params.id)
+        .then(function(mailServerOut) {
+          if (!mailServerOut) {
+            t.rollback();
+            return res.sendStatus(404);
+          } else {
+            return mailServerOut;
+          }
+        })
+        .then(function(mailServerOut) {
+          _mailServerOut = mailServerOut;
+          return MailMessage
+            .create({
+              subject: 'MailAccount ' + mailServerOut.username + ' test..',
+              text: 'MailAccount ' + mailServerOut.username + ' test..',
+              from: [mailServerOut.username],
+              to: [mailServerOut.username]
+            }, {
+              transaction: t,
+              user: req.user
+            });
+        })
+        .then(function(mailMessage) {
+          _mailMessage = mailMessage;
+          return _mailServerOut
+            .update({
+              state: 'CONNECTED',
+            });
+        })
+        .then(function() {
+          t.commit();
+          return res.status(201).send(_mailMessage);
+        })
+        .catch(function(err) {
+          console.error(err);
+          t.rollback();
+          return _mailServerOut
+            .update({
+              state: 'ERROR',
+            })
+            .then(function() {
+              return handleError(res, err);
+            })
+        });
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_server_out/mail_server_out.socket.js b/server/api/mail_server_out/mail_server_out.socket.js
index 05d35e1..d9e6e91 100644
--- a/server/api/mail_server_out/mail_server_out.socket.js
+++ b/server/api/mail_server_out/mail_server_out.socket.js
@@ -1 +1,26 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var MailServerOut = require('../../models').MailServerOut;
+
+exports.register = function(socket) {
+  MailServerOut.afterCreate(function(doc) {
+    console.log('MailServerOut afterCreate');
+    onSave(socket, doc);
+  });
+  MailServerOut.afterDestroy(function(doc) {
+    console.log('MailServerOut afterDestroy');
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_server_out:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_server_out:remove', doc);
+}
diff --git a/server/api/mail_template/index.js b/server/api/mail_template/index.js
index 4692b26..cb8e0b2 100644
--- a/server/api/mail_template/index.js
+++ b/server/api/mail_template/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./mail_template.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/mail_template/mail_template.controller.js b/server/api/mail_template/mail_template.controller.js
index 2130cbf..8853adc 100644
--- a/server/api/mail_template/mail_template.controller.js
+++ b/server/api/mail_template/mail_template.controller.js
@@ -1 +1,92 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var Util = require('../../config/util');
+
+var MailTemplate = require('../../models').MailTemplate;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return MailTemplate
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single mailTemplate
+exports.show = function(req, res) {
+  return MailTemplate
+    .findById(req.params.id)
+    .then(function(mailTemplate) {
+      if (!mailTemplate) {
+        return res.sendStatus(404);
+      }
+      return res.send(mailTemplate);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new mailTemplate in the DB.
+exports.create = function(req, res) {
+  return MailTemplate
+    .create(req.body)
+    .then(function(mailTemplate) {
+      return res.status(201).send(mailTemplate);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing mailTemplate in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return MailTemplate
+    .findById(req.params.id)
+    .then(function(mailTemplate) {
+      if (!mailTemplate) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(mailTemplate, req.body);
+      return updated
+        .save();
+    })
+    .then(function(mailTemplate) {
+      return res.status(200).send(mailTemplate);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a mailTemplate from the DB.
+exports.destroy = function(req, res) {
+  return MailTemplate
+    .findById(req.params.id)
+    .then(function(mailTemplate) {
+      if (!mailTemplate) {
+        return res.sendStatus(404);
+      }
+      mailTemplate
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/mail_template/mail_template.socket.js b/server/api/mail_template/mail_template.socket.js
index b97c57f..a01fb2e 100644
--- a/server/api/mail_template/mail_template.socket.js
+++ b/server/api/mail_template/mail_template.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var MailTemplate = require('../../models').MailTemplate;
+
+exports.register = function(socket) {
+  MailTemplate.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  MailTemplate.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('mail_template:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('mail_template:remove', doc);
+}
diff --git a/server/api/metric/index.js b/server/api/metric/index.js
index 917fd55..d565c16 100644
--- a/server/api/metric/index.js
+++ b/server/api/metric/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./metric.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/table', auth.isAuthenticated(), controller.getMetricsByTable);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.metricValidation);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/metric/metric.controller.js b/server/api/metric/metric.controller.js
index 4ef4590..ce77ec6 100644
--- a/server/api/metric/metric.controller.js
+++ b/server/api/metric/metric.controller.js
@@ -1 +1,180 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Metric = require('../../models').Metric;
+var util = require('util');
+var Util = require('../../config/util');
+
+// Get list of metrics
+exports.index = function(req, res, next) {
+  return Metric
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.metricValidation = function(req, res) {
+  Metric
+    .findAll({
+      where: {
+        name: req.body.name
+      }
+    })
+    .then(function(metrics) {
+      if (!metrics) {
+        return res.sendStatus(404);
+      }
+      return res.send(metrics);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.getMetricsByTable = function(req, res) {
+  Metric
+    .findAll({
+      where: {
+        table: req.query.table
+      }
+    })
+    .then(function(metrics) {
+      console.log(metrics);
+      if (!metrics) {
+        return res.sendStatus(404);
+      }
+      return res.status(200).send(metrics);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single metric
+exports.show = function(req, res) {
+  Metric
+    .findById(req.params.id)
+    .then(function(metric) {
+      if (!metric) {
+        return res.sendStatus(404);
+      }
+      return res.send(metric);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new metric in the DB.
+exports.create = function(req, res) {
+  Metric
+    .create(req.body)
+    .then(function(metric) {
+      return res.status(201).send(metric);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing metric in the DB.
+exports.update = function(req, res) {
+  Metric
+    .findAll({
+      where: {
+        name: req.body.name,
+        id: {
+          $ne: req.body.id
+        }
+      }
+    })
+    .then(function(metrics) {
+      if (!metrics) {
+        return res.sendStatus(404);
+      }
+      if (metrics.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_METRIC'
+        })
+      }
+      if (req.body.id) {
+        delete req.body.id;
+      }
+      return Metric
+        .find({
+          where: {
+            id: req.params.id
+          }
+        });
+    })
+    .then(function(metric) {
+      if (!metric) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(metric, req.body);
+      return updated.save();
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes a metric from the DB.
+exports.destroy = function(req, res) {
+  Metric
+    .findById(req.params.id)
+    .then(function(metric) {
+      if (!metric) {
+        return res.sendStatus(404);
+      }
+      metric.getZendeskTexts()
+        .then(function(texts) {
+          if (texts.length > 0) {
+            return res.status(500).send({
+              message: 'MESSAGE_METRIC_CONFIGURATION_ASSOCIATED'
+            });
+          }
+          metric.destroy()
+            .then(function() {
+              return res.sendStatus(204);
+            })
+            .catch(function(err) {
+              return handleError(res, err);
+            });
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  Metric
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/metric/metric.socket.js b/server/api/metric/metric.socket.js
index f72203f..e604bc9 100644
--- a/server/api/metric/metric.socket.js
+++ b/server/api/metric/metric.socket.js
@@ -1 +1,41 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Metric = require('../../models').Metric;
+var ReportField = require('../../models').ReportField;
+
+exports.register = function(socket) {
+  Metric.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Metric.afterUpdate(function(doc) {
+    updateFields(doc);
+  });
+  Metric.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('metric:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('metric:remove', doc);
+}
+
+function updateFields(doc) {
+  return ReportField.update({
+      field: doc.metric
+    }, {
+      where: {
+        MetricId: doc.id
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
diff --git a/server/api/metric/metric.spec.js b/server/api/metric/metric.spec.js
index 3020939..076cab1 100644
--- a/server/api/metric/metric.spec.js
+++ b/server/api/metric/metric.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/analytics/metrics', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/analytics/metrics')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/module/index.js b/server/api/module/index.js
index 617eb51..8204682 100644
--- a/server/api/module/index.js
+++ b/server/api/module/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./module.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/module/module.controller.js b/server/api/module/module.controller.js
index 433c64e..9d6517c 100644
--- a/server/api/module/module.controller.js
+++ b/server/api/module/module.controller.js
@@ -1 +1,92 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Module = require('../../models').Module;
+
+// Get list of modules
+exports.index = function(req, res) {
+  Module
+  .findAll()
+  .then(function (modules) {
+    return res.status(200).send(modules);
+  })
+  .catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+// Get a single module
+exports.show = function(req, res) {
+  var moduleId = req.params.id;
+
+  Module
+  .findById(moduleId)
+  .then(function (module) {
+    if(!module) { return res.sendStatus(404); }
+    return res.send(module);
+  })
+  .catch(function(err){
+    return handleError(res, err);
+  });
+};
+
+// Creates a new module in the DB.
+exports.create = function(req, res) {
+  Module
+  .create(req.body)
+  .then(function(module) {
+    return res.status(201).send(module);
+  })
+  .catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+// Updates an existing module in the DB.
+exports.update = function(req, res) {
+  var moduleId = req.body.id;
+
+  if(req.body.id) { delete req.body.id; }
+
+  Module
+  .findById(moduleId)
+  .then(function (module) {
+    if(!module) { return res.sendStatus(404); }
+    var updated = _.merge(module, req.body);
+    updated.save()
+    .then(function () {
+      return res.status(200).send(module);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+  })
+  .catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+// Deletes a module from the DB.
+exports.destroy = function(req, res) {
+  var moduleId = req.params.id;
+
+  Module
+  .findById(moduleId)
+  .then(function (module) {
+    if(!module) { return res.sendStatus(404); }
+    module.destroy()
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+  })
+  .catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/module/module.socket.js b/server/api/module/module.socket.js
index d23fa47..0eade3b 100644
--- a/server/api/module/module.socket.js
+++ b/server/api/module/module.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var module = require('../../models').Module;
+
+exports.register = function(socket) {
+  module.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  module.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('module:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('module:remove', doc);
+}
diff --git a/server/api/module/module.spec.js b/server/api/module/module.spec.js
index 46c329d..45aa629 100644
--- a/server/api/module/module.spec.js
+++ b/server/api/module/module.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/modules', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/modules')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/module/setting/index.js b/server/api/module/setting/index.js
index e7f2252..f341cb4 100644
--- a/server/api/module/setting/index.js
+++ b/server/api/module/setting/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./setting.controller');
+
+var router = express.Router();
+
+// router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/module/setting/setting.controller.js b/server/api/module/setting/setting.controller.js
index a16a0fc..3ebcb5c 100644
--- a/server/api/module/setting/setting.controller.js
+++ b/server/api/module/setting/setting.controller.js
@@ -1 +1,110 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var crypto = require('crypto');
+
+var Setting = require('../../models').Setting;
+
+// Get list of settings
+exports.index = function(req, res) {
+  Setting
+    .findAll()
+    .then(function(settings) {
+      return res.status(200).send(settings);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single setting
+exports.show = function(req, res) {
+  Setting
+    .findById(req.params.id)
+    .then(function(setting) {
+      if (!setting) {
+        return res.sendStatus(404);
+      }
+
+      if (setting.license) {
+        setting.license = decrypt('aes-256-ctr', util.format('%s:%s:%s',
+          setting.address, setting.netmask, setting.mac), setting.license);
+        setting.license = JSON.parse(setting.license);
+      }
+
+      return res.send(setting);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new setting in the DB.
+exports.create = function(req, res) {
+  Setting
+    .create(req.body)
+    .then(function(setting) {
+      return res.status(201).send(setting);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing setting in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  Setting
+    .findById(req.params.id)
+    .then(function(setting) {
+      if (!setting) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(setting, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(setting);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a setting from the DB.
+exports.destroy = function(req, res) {
+  Setting
+    .findById(req.params.id)
+    .then(function(setting) {
+      if (!setting) {
+        return res.sendStatus(404);
+      }
+      setting.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
+
+function decrypt(algorithm, password, text) {
+  var decipher = crypto.createDecipher(algorithm, password)
+  var dec = decipher.update(text, 'hex', 'utf8')
+  dec += decipher.final('utf8');
+  return dec;
+}
diff --git a/server/api/module/setting/setting.socket.js b/server/api/module/setting/setting.socket.js
index d34fe4a..0784c1f 100644
--- a/server/api/module/setting/setting.socket.js
+++ b/server/api/module/setting/setting.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Setting = require('../../models').Setting;
+
+exports.register = function(socket) {
+  Setting.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Setting.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('setting:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('setting:remove', doc);
+}
diff --git a/server/api/module/setting/setting.spec.js b/server/api/module/setting/setting.spec.js
index 285d4e4..eec76c9 100644
--- a/server/api/module/setting/setting.spec.js
+++ b/server/api/module/setting/setting.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/settings', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/settings')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/motionbar/index.js b/server/api/motionbar/index.js
index 546e97c..fd9d420 100644
--- a/server/api/motionbar/index.js
+++ b/server/api/motionbar/index.js
@@ -1 +1,11 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./motionbar.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/:action/:state', auth.isAuthenticated(), controller.action);
+
+module.exports = router;
diff --git a/server/api/motionbar/motionbar.controller.js b/server/api/motionbar/motionbar.controller.js
index 1a2c6d5..f0a79be 100644
--- a/server/api/motionbar/motionbar.controller.js
+++ b/server/api/motionbar/motionbar.controller.js
@@ -1 +1,32 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Motionbar = require('../../models').Motionbar;
+
+// Get list of motionbars
+exports.action = function(req, res) {
+
+  if (!req.query.userId) {
+    return handleError(res, {
+      message: 'Use userId'
+    });
+  }
+
+  req.body.UserId = parseInt(req.query.userId, 10);
+  req.body.action = req.params.action;
+  req.body.state = req.params.state;
+  req.body.AdminId = req.user.id;
+
+  Motionbar
+    .create(req.body)
+    .then(function(action) {
+      return res.status(201).send(action);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/motionbar/motionbar.socket.js b/server/api/motionbar/motionbar.socket.js
index 2bede78..0eb15d4 100644
--- a/server/api/motionbar/motionbar.socket.js
+++ b/server/api/motionbar/motionbar.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Motionbar = require('../../models').Motionbar;
+
+exports.register = function(socket) {
+  Motionbar.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Motionbar.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  // doc.UserId is string. You don't use ===
+  if (socket.userId == doc.UserId) {
+    socket.emit('motionbar:save', doc);
+  }
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('motionbar:remove', doc);
+}
diff --git a/server/api/motionbar/motionbar.spec.js b/server/api/motionbar/motionbar.spec.js
index fc63e66..bbbac7f 100644
--- a/server/api/motionbar/motionbar.spec.js
+++ b/server/api/motionbar/motionbar.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/motionbar', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/motionbar')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/pause/index.js b/server/api/pause/index.js
index a3d2dff..bdf03bc 100644
--- a/server/api/pause/index.js
+++ b/server/api/pause/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./pause.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/pause/pause.controller.js b/server/api/pause/pause.controller.js
index 93596d9..84c022b 100644
--- a/server/api/pause/pause.controller.js
+++ b/server/api/pause/pause.controller.js
@@ -1 +1,150 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/pauses              ->  index
+ * POST    /api/pauses              ->  create
+ * GET     /api/pauses/:id          ->  show
+ * PUT     /api/pauses/:id          ->  update
+ * DELETE  /api/pauses/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var Pause = require('../../models').Pause;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function (err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function (entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function (entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function (entity) {
+    return entity.updateAttributes(updates)
+      .then(function (updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function (entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function () {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Pauses
+exports.index = function (req, res) {
+  return Pause
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function (result) {
+      res.status(200).send(result);
+    })
+    .catch(function (err) {
+      return handleError(res, err);
+    });
+}
+
+// Gets a single Pause from the DB
+exports.show = function (req, res) {
+  Pause.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new Pause in the DB
+exports.create = function (req, res) {
+  Pause.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing Pause in the DB
+exports.update = function (req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  Pause.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a Pause from the DB
+exports.destroy = function (req, res) {
+  Pause.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function (req, res) {
+
+  return Pause
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function () {
+      return res.sendStatus(204);
+    })
+    .catch(function (err) {
+      return handleError(res, err);
+    });
+};
+
+function getQuery(query) {
+  var _query = {
+    where: {}
+  };
+
+  _.forEach(query, function (value, key) {
+    switch (key) {
+    case 'limit':
+    case 'offset':
+    case 'order':
+      _query[key] = value;
+      break;
+    default:
+      _query.where[key] = {
+        $like: '%' + value + '%'
+      };
+    }
+  });
+
+  return _query;
+}
diff --git a/server/api/pause/pause.socket.js b/server/api/pause/pause.socket.js
index c33b0a6..fd1c7b0 100644
--- a/server/api/pause/pause.socket.js
+++ b/server/api/pause/pause.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Pause = require('../../models').Pause;
+
+exports.register = function(socket) {
+  Pause.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Pause.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('pause:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('pause:remove', doc);
+}
diff --git a/server/api/report_agent/index.js b/server/api/report_agent/index.js
index 5490a34..92aec80 100644
--- a/server/api/report_agent/index.js
+++ b/server/api/report_agent/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_agent.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/describe', controller.describe);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/report_agent/report_agent.controller.js b/server/api/report_agent/report_agent.controller.js
index c43a86d..1b848da 100644
--- a/server/api/report_agent/report_agent.controller.js
+++ b/server/api/report_agent/report_agent.controller.js
@@ -1 +1,106 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ReportAgent = require('../../models').ReportAgent;
+var ReportAgentHistory = require('../../models').history.ReportAgentHistory;
+
+// Get list of report_agents
+exports.index = function(req, res) {
+  ReportAgent
+    .findAll()
+    .then(function(report_agents) {
+      return res.status(200).send(report_agents);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get list of fields
+exports.describe = function(req, res) {
+  ReportAgentHistory
+    .describe()
+    .then(function(fields) {
+      return res.status(200).send(fields);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single report_agent
+exports.show = function(req, res) {
+  ReportAgent
+    .findById(req.params.id)
+    .then(function(report_agent) {
+      if (!report_agent) {
+        return res.sendStatus(404);
+      }
+      return res.send(report_agent);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new report_agent in the DB.
+exports.create = function(req, res) {
+  ReportAgent
+    .create(req.body)
+    .then(function(report_agent) {
+      return res.status(201).send(report_agent);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing report_agent in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  ReportAgent
+    .findById(req.params.id)
+    .then(function(report_agent) {
+      if (!report_agent) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(report_agent, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(report_agent);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a report_agent from the DB.
+exports.destroy = function(req, res) {
+  ReportAgent
+    .findById(req.params.id)
+    .then(function(report_agent) {
+      if (!report_agent) {
+        return res.sendStatus(404);
+      }
+      report_agent.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_agent/report_agent.socket.js b/server/api/report_agent/report_agent.socket.js
index b54d349..86cb2bb 100644
--- a/server/api/report_agent/report_agent.socket.js
+++ b/server/api/report_agent/report_agent.socket.js
@@ -1 +1,30 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ReportAgent = require('../../models').ReportAgent;
+
+exports.register = function(socket) {
+  ReportAgent.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportAgent.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportAgent.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  if (socket.name === doc.membername || socket.role === 'admin') {
+    socket.emit('report_agent:save', doc);
+  }
+
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('report_agent:remove', doc);
+}
diff --git a/server/api/report_chat/index.js b/server/api/report_chat/index.js
index 51e097b..7701153 100644
--- a/server/api/report_chat/index.js
+++ b/server/api/report_chat/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_chat.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.get('/:id/accept/:uniqueid', auth.isAuthenticated(), controller.accept);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/report_chat/report_chat.controller.js b/server/api/report_chat/report_chat.controller.js
index d3619a2..25acee0 100644
--- a/server/api/report_chat/report_chat.controller.js
+++ b/server/api/report_chat/report_chat.controller.js
@@ -1 +1,156 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var md5 = require('md5');
+
+var ReportChat = require('../../models').ReportChat;
+var ChatRoom = require('../../models').ChatRoom;
+var ChatVisitor = require('../../models').ChatVisitor;
+var Agent = require('../../models').User;
+var sequelize = require('../../models').sequelize;
+
+var Util = require('../../config/util');
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return ReportChat
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Ge
+// Get a single reportChat
+exports.show = function(req, res) {
+  return ReportChat
+    .findById(req.params.id)
+    .then(function(reportChat) {
+      if (!reportChat) {
+        return res.sendStatus(404);
+      }
+      return res.send(reportChat);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single reportMail
+exports.accept = function(req, res) {
+  var _roomid = req.params.uniqueid.split('.')[1];
+  var _chatRoom;
+
+  return ChatRoom
+    .findById(_roomid)
+    .then(function(chatRoom) {
+      _chatRoom = chatRoom;
+      return _chatRoom
+        .addUser(req.user.id, {
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return _chatRoom
+        .update({
+          token: md5(JSON.stringify([req.user.id])).toString('base64')
+        })
+    })
+    .then(function() {
+      return ReportChat
+        .findById(req.params.id);
+    })
+    .then(function(reportChat) {
+      if (!reportChat) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(reportChat, {
+        connectid: req.params.uniqueid
+      });
+      return updated.save();
+    })
+    .then(function() {
+      return ChatVisitor.findById(_chatRoom.ChatVisitorId);
+    })
+    .then(function(chatVisitor) {
+      if (!chatVisitor) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(chatVisitor, {
+        status: 'accepted'
+      });
+      return updated.save();
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new reportChat in the DB.
+exports.create = function(req, res) {
+  return ReportChat
+    .create(req.body)
+    .then(function(reportChat) {
+      return res.status(201).send(reportChat);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing reportChat in the DB.
+exports.update = function(req, res, next) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return ReportChat
+    .findById(req.params.id)
+    .then(function(reportChat) {
+      if (!reportChat) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(reportChat, req.body);
+      return updated
+        .save()
+        .then(function() {
+          return res.status(200).send(reportChat);
+        });
+    })
+    .catch(sequelize.ValidationError, function(err) {
+      return handleError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a reportChat from the DB.
+exports.destroy = function(req, res) {
+  ReportChat
+    .findById(req.params.id)
+    .then(function(reportChat) {
+      if (!reportChat) {
+        return res.sendStatus(404);
+      }
+      reportChat.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_chat/report_chat.socket.js b/server/api/report_chat/report_chat.socket.js
index ccba5b3..dd9728d 100644
--- a/server/api/report_chat/report_chat.socket.js
+++ b/server/api/report_chat/report_chat.socket.js
@@ -1 +1,35 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ReportChat = require('../../models').ReportChat;
+
+exports.register = function(socket) {
+  ReportChat.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportChat.afterUpdate(function(doc) {
+    onUpdate(socket, doc);
+  });
+  ReportChat.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  if (socket.name === doc.agentname || socket.role === 'admin') {
+    socket.emit('report_chat:save', doc);
+  }
+}
+
+function onUpdate(socket, doc, cb) {
+  if (socket.name === doc.agentname || socket.role === 'admin') {
+    socket.emit('report_chat:update', doc);
+  }
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('report_chat:remove', doc);
+}
diff --git a/server/api/report_chat_session/index.js b/server/api/report_chat_session/index.js
index 754b3e7..030bd33 100644
--- a/server/api/report_chat_session/index.js
+++ b/server/api/report_chat_session/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_chat_session.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/describe', controller.describe);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/report_chat_session/report_chat_session.controller.js b/server/api/report_chat_session/report_chat_session.controller.js
index 3fafc21..c3cd755 100644
--- a/server/api/report_chat_session/report_chat_session.controller.js
+++ b/server/api/report_chat_session/report_chat_session.controller.js
@@ -1 +1,107 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ReportChatSession = require('../../models').ReportChatSession;
+var ReportChatSessionHistory = require('../../models').history.ReportChatSessionHistory;
+
+var Util = require('../../config/util');
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return ReportChatSession
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Get list of fields
+exports.describe = function(req, res) {
+  return ReportChatSessionHistory
+    .describe()
+    .then(function(fields) {
+      return res.status(200).send(fields);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single report_chat_session
+exports.show = function(req, res) {
+  return ReportChatSession
+    .findById(req.params.id)
+    .then(function(report_chat_session) {
+      if (!report_chat_session) {
+        return res.sendStatus(404);
+      }
+      return res.send(report_chat_session);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new report_chat_session in the DB.
+exports.create = function(req, res) {
+  return ReportChatSession
+    .create(req.body)
+    .then(function(report_chat_session) {
+      return res.status(201).send(report_chat_session);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing report_chat_session in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ReportChatSession
+    .findById(req.params.id)
+    .then(function(report_chat_session) {
+      if (!report_chat_session) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(report_chat_session, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(report_chat_session);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a report_chat_session from the DB.
+exports.destroy = function(req, res) {
+  return ReportChatSession
+    .findById(req.params.id)
+    .then(function(report_chat_session) {
+      if (!report_chat_session) {
+        return res.sendStatus(404);
+      }
+      report_chat_session.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_chat_session/report_chat_session.socket.js b/server/api/report_chat_session/report_chat_session.socket.js
index e30b6b9..a378e10 100644
--- a/server/api/report_chat_session/report_chat_session.socket.js
+++ b/server/api/report_chat_session/report_chat_session.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ReportChatSession = require('../../models').ReportChatSession;
+
+exports.register = function(socket) {
+  ReportChatSession.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportChatSession.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportChatSession.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('report_chat_session:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('report_chat_session:remove', doc);
+}
diff --git a/server/api/report_integration/index.js b/server/api/report_integration/index.js
index 017c000..451980f 100644
--- a/server/api/report_integration/index.js
+++ b/server/api/report_integration/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_integration.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/describe', controller.describe);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/report_integration/report_integration.controller.js b/server/api/report_integration/report_integration.controller.js
index a373b92..6fc6afb 100644
--- a/server/api/report_integration/report_integration.controller.js
+++ b/server/api/report_integration/report_integration.controller.js
@@ -1 +1,106 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ReportIntegration = require('../../models').ReportIntegration;
+var ReportIntegrationHistory = require('../../models').history.ReportIntegrationHistory;
+
+// Get list of report_integrations
+exports.index = function(req, res) {
+  ReportIntegration
+    .findAll()
+    .then(function(report_integrations) {
+      return res.status(200).send(report_integrations);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get list of fields
+exports.describe = function(req, res) {
+  ReportIntegrationHistory
+    .describe()
+    .then(function(fields) {
+      return res.status(200).send(fields);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single report_integration
+exports.show = function(req, res) {
+  ReportIntegration
+    .findById(req.params.id)
+    .then(function(report_integration) {
+      if (!report_integration) {
+        return res.sendStatus(404);
+      }
+      return res.send(report_integration);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new report_integration in the DB.
+exports.create = function(req, res) {
+  ReportIntegration
+    .create(req.body)
+    .then(function(report_integration) {
+      return res.status(201).send(report_integration);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing report_integration in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  ReportIntegration
+    .findById(req.params.id)
+    .then(function(report_integration) {
+      if (!report_integration) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(report_integration, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(report_integration);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a report_integration from the DB.
+exports.destroy = function(req, res) {
+  ReportIntegration
+    .findById(req.params.id)
+    .then(function(report_integration) {
+      if (!report_integration) {
+        return res.sendStatus(404);
+      }
+      report_integration.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_integration/report_integration.socket.js b/server/api/report_integration/report_integration.socket.js
index d26b79b..2bd59ab 100644
--- a/server/api/report_integration/report_integration.socket.js
+++ b/server/api/report_integration/report_integration.socket.js
@@ -1 +1,23 @@
\ No newline at end of file
+/**
+ * Broadcast integrations update when the model changes
+ */
+
+'use strict';
+
+var util = require('util');
+var ReportIntegration = require('../../models').ReportIntegration;
+
+exports.register = function(socket) {
+  ReportIntegration.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  console.log('socket name is ', socket.name);
+  console.log('doc membername is ', doc.membername);
+  if (socket.name === doc.membername) {
+    console.log('emitting');
+    socket.emit(util.format('report_integration:%s', doc.integration), doc);
+  }
+}
diff --git a/server/api/report_mail/index.js b/server/api/report_mail/index.js
index 447d350..2aadc12 100644
--- a/server/api/report_mail/index.js
+++ b/server/api/report_mail/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_mail.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/accept/:uniqueid', auth.isAuthenticated(), controller.accept);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/report_mail/report_mail.controller.js b/server/api/report_mail/report_mail.controller.js
index c4b80cb..9477b57 100644
--- a/server/api/report_mail/report_mail.controller.js
+++ b/server/api/report_mail/report_mail.controller.js
@@ -1 +1,140 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ReportMail = require('../../models').ReportMail;
+var Agent = require('../../models').User;
+var sequelize = require('../../models').sequelize;
+
+var Util = require('../../config/util');
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return ReportMail
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Get a single reportMail
+exports.show = function(req, res) {
+  return ReportMail
+    .findById(req.params.id)
+    .then(function(reportMail) {
+      if (!reportMail) {
+        return res.sendStatus(404);
+      }
+      return res.send(reportMail);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single reportMail
+exports.accept = function(req, res) {
+  var _agent;
+  var _roomid = req.params.uniqueid.split('.')[1];
+
+  return Agent
+    .findById(req.user.id, {
+      attributes: ['id', 'mailCapacity']
+    })
+    .then(function(agent) {
+      _agent = agent;
+      return agent
+        .getMailRooms({
+          where: {
+            status: {
+              $ne: 'CLOSED'
+            },
+            id: {
+              $ne: _roomid
+            }
+          },
+          attributes: ['id']
+        });
+    })
+    .then(function(mailRooms) {
+      return ReportMail
+        .findById(req.params.id);
+    })
+    .then(function(reportMail) {
+      if (!reportMail) {
+        return res.sendStatus(404);
+      }
+
+      var updated = _.merge(reportMail, {
+        connectid: req.params.uniqueid
+      });
+
+      return updated
+        .save();
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new reportMail in the DB.
+exports.create = function(req, res) {
+  return ReportMail
+    .create(req.body)
+    .then(function(reportMail) {
+      return res.status(201).send(reportMail);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing reportMail in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ReportMail
+    .findById(req.params.id)
+    .then(function(reportMail) {
+      if (!reportMail) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(reportMail, req.body);
+      return updated
+        .save();
+    })
+    .then(function(reportMail) {
+      return res.status(200).send(reportMail);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a reportMail from the DB.
+exports.destroy = function(req, res) {
+  return ReportMail
+    .findById(req.params.id)
+    .then(function(reportMail) {
+      if (!reportMail) {
+        return res.sendStatus(404);
+      }
+      return reportMail
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_mail/report_mail.socket.js b/server/api/report_mail/report_mail.socket.js
index 61c6638..1fec069 100644
--- a/server/api/report_mail/report_mail.socket.js
+++ b/server/api/report_mail/report_mail.socket.js
@@ -1 +1,35 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ReportMail = require('../../models').ReportMail;
+
+exports.register = function(socket) {
+  ReportMail.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportMail.afterUpdate(function(doc) {
+    onUpdate(socket, doc);
+  });
+  ReportMail.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  if (socket.name === doc.membername || socket.role === 'admin') {
+    socket.emit('report_mail:save', doc);
+  }
+}
+
+function onUpdate(socket, doc, cb) {
+  if (socket.name === doc.membername || socket.role === 'admin') {
+    socket.emit('report_mail:update', doc);
+  }
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('report_mail:remove', doc);
+}
diff --git a/server/api/report_mail_session/index.js b/server/api/report_mail_session/index.js
index ab3af32..e63d04d 100644
--- a/server/api/report_mail_session/index.js
+++ b/server/api/report_mail_session/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_mail_session.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/describe', controller.describe);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/report_mail_session/report_mail_session.controller.js b/server/api/report_mail_session/report_mail_session.controller.js
index 3df44d2..a964ba2 100644
--- a/server/api/report_mail_session/report_mail_session.controller.js
+++ b/server/api/report_mail_session/report_mail_session.controller.js
@@ -1 +1,104 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ReportMailSession = require('../../models').ReportMailSession;
+var ReportMailSessionHistory = require('../../models').history.ReportMailSessionHistory;
+
+var Util = require('../../config/util');
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return ReportMailSession
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get list of fields
+exports.describe = function(req, res) {
+  return ReportMailSessionHistory
+    .describe()
+    .then(function(fields) {
+      return res.status(200).send(fields);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single reportMailSession
+exports.show = function(req, res) {
+  return ReportMailSession
+    .findById(req.params.id)
+    .then(function(reportMailSession) {
+      if (!reportMailSession) {
+        return res.sendStatus(404);
+      }
+      return res.send(reportMailSession);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new reportMailSession in the DB.
+exports.create = function(req, res) {
+  return ReportMailSession
+    .create(req.body)
+    .then(function(reportMailSession) {
+      return res.status(201).send(reportMailSession);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing reportMailSession in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ReportMailSession
+    .findById(req.params.id)
+    .then(function(reportMailSession) {
+      if (!reportMailSession) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(reportMailSession, req.body);
+      return updated
+        .save();
+    })
+    .then(function(reportMailSession) {
+      return res.status(200).send(reportMailSession);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a reportMailSession from the DB.
+exports.destroy = function(req, res) {
+  return ReportMailSession
+    .findById(req.params.id)
+    .then(function(reportMailSession) {
+      if (!reportMailSession) {
+        return res.sendStatus(404);
+      }
+      return reportMailSession
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_mail_session/report_mail_session.socket.js b/server/api/report_mail_session/report_mail_session.socket.js
index 1c44a64..6489a0d 100644
--- a/server/api/report_mail_session/report_mail_session.socket.js
+++ b/server/api/report_mail_session/report_mail_session.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ReportMailSession = require('../../models').ReportMailSession;
+
+exports.register = function(socket) {
+  ReportMailSession.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportMailSession.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportMailSession.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('report_mail_session:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('report_mail_session:remove', doc);
+}
diff --git a/server/api/report_queue/index.js b/server/api/report_queue/index.js
index 0aa96fb..f92d5aa 100644
--- a/server/api/report_queue/index.js
+++ b/server/api/report_queue/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_queue.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/describe', controller.describe);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/report_queue/report_queue.controller.js b/server/api/report_queue/report_queue.controller.js
index 06985b7..7369229 100644
--- a/server/api/report_queue/report_queue.controller.js
+++ b/server/api/report_queue/report_queue.controller.js
@@ -1 +1,103 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ReportQueue = require('../../models').ReportQueue;
+var ReportQueueHistory = require('../../models').history.ReportQueueHistory;
+
+// Get list of reportQueues
+exports.index = function(req, res) {
+  return ReportQueue
+    .findAll()
+    .then(function(reportQueues) {
+      return res.status(200).send(reportQueues);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get list of fields
+exports.describe = function(req, res) {
+  return ReportQueueHistory
+    .describe()
+    .then(function(fields) {
+      return res.status(200).send(fields);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single reportQueue
+exports.show = function(req, res) {
+  return ReportQueue
+    .findById(req.params.id)
+    .then(function(reportQueue) {
+      if (!reportQueue) {
+        return res.sendStatus(404);
+      }
+      return res.send(reportQueue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new reportQueue in the DB.
+exports.create = function(req, res) {
+  return ReportQueue
+    .create(req.body)
+    .then(function(reportQueue) {
+      return res.status(201).send(reportQueue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing reportQueue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ReportQueue
+    .findById(req.params.id)
+    .then(function(reportQueue) {
+      if (!reportQueue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(reportQueue, req.body);
+      return updated
+        .save();
+    })
+    .then(function(reportQueue) {
+      return res.status(200).send(reportQueue);
+    })
+    .catch(function(err) {
+      console.error('err', err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes a reportQueue from the DB.
+exports.destroy = function(req, res) {
+  return ReportQueue
+    .findById(req.params.id)
+    .then(function(reportQueue) {
+      if (!reportQueue) {
+        return res.sendStatus(404);
+      }
+      return reportQueue
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_queue/report_queue.socket.js b/server/api/report_queue/report_queue.socket.js
index 0ff57d8..9f57869 100644
--- a/server/api/report_queue/report_queue.socket.js
+++ b/server/api/report_queue/report_queue.socket.js
@@ -1 +1,29 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ReportQueue = require('../../models').ReportQueue;
+
+exports.register = function(socket) {
+  ReportQueue.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportQueue.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportQueue.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  if (socket.role === 'admin') {
+    socket.emit('report_queue:save', doc);
+  }
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('report_queue:remove', doc);
+}
diff --git a/server/api/report_tree/index.js b/server/api/report_tree/index.js
index 845a2a1..75fa91c 100644
--- a/server/api/report_tree/index.js
+++ b/server/api/report_tree/index.js
@@ -1 +1,12 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./report_tree.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+
+module.exports = router;
diff --git a/server/api/report_tree/report_tree.controller.js b/server/api/report_tree/report_tree.controller.js
index fd80b0f..a6b11b0 100644
--- a/server/api/report_tree/report_tree.controller.js
+++ b/server/api/report_tree/report_tree.controller.js
@@ -1 +1,46 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var ReportTree = require('../../models').ReportTree;
+
+// Get the reports trees
+exports.index = function(req, res) {
+  ReportTree
+    .findAll()
+    .then(function(trees) {
+      if (!trees) {
+        return res.sendStatus(404);
+      }
+      return res.status(200).send(trees);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing report in the DB.
+exports.update = function(req, res) {
+  ReportTree
+    .findById(req.params.id)
+    .then(function(tree) {
+      if (!tree) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(tree, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(tree);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/report_tree/report_tree.socket.js b/server/api/report_tree/report_tree.socket.js
index 1df63dc..389fa7a 100644
--- a/server/api/report_tree/report_tree.socket.js
+++ b/server/api/report_tree/report_tree.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ReportTree = require('../../models').ReportTree;
+
+exports.register = function(socket) {
+  ReportTree.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportTree.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  ReportTree.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('report_tree:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('report_tree:remove', doc);
+}
diff --git a/server/api/report_tree/report_tree.spec.js b/server/api/report_tree/report_tree.spec.js
index 1a873ef..37d897a 100644
--- a/server/api/report_tree/report_tree.spec.js
+++ b/server/api/report_tree/report_tree.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/analytics/reports/trees', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/analytic/reports/trees')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/salesforce_account/index.js b/server/api/salesforce_account/index.js
index 2696645..d8b47b0 100644
--- a/server/api/salesforce_account/index.js
+++ b/server/api/salesforce_account/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./salesforce_account.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/check', auth.isAuthenticated(), controller.checkAccount);
+router.get('/:id/fields', auth.isAuthenticated(), controller.getFields);
+router.post('/validate/:field', auth.isAuthenticated(), controller.accountValidation);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/salesforce_account/index.spec.js b/server/api/salesforce_account/index.spec.js
index 0e02667..a03b303 100644
--- a/server/api/salesforce_account/index.spec.js
+++ b/server/api/salesforce_account/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var salesforceAccountCtrlStub = {
+  index: 'salesforceAccountCtrl.index',
+  show: 'salesforceAccountCtrl.show',
+  create: 'salesforceAccountCtrl.create',
+  update: 'salesforceAccountCtrl.update',
+  destroy: 'salesforceAccountCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var salesforceAccountIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './salesforce_account.controller': salesforceAccountCtrlStub
+});
+
+describe('SalesforceAccount API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(salesforceAccountIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/salesforce/accounts', function() {
+
+    it('should route to salesforceAccount.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'salesforceAccountCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/salesforce/accounts/:id', function() {
+
+    it('should route to salesforceAccount.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'salesforceAccountCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/salesforce/accounts', function() {
+
+    it('should route to salesforceAccount.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'salesforceAccountCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/salesforce/accounts/:id', function() {
+
+    it('should route to salesforceAccount.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'salesforceAccountCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/salesforce/accounts/:id', function() {
+
+    it('should route to salesforceAccount.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'salesforceAccountCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/salesforce/accounts/:id', function() {
+
+    it('should route to salesforceAccount.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'salesforceAccountCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/salesforce_account/salesforce_account.controller.js b/server/api/salesforce_account/salesforce_account.controller.js
index 01b59e0..69c55e6 100644
--- a/server/api/salesforce_account/salesforce_account.controller.js
+++ b/server/api/salesforce_account/salesforce_account.controller.js
@@ -1 +1,253 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/salesforce/accounts              ->  index
+ * POST    /api/salesforce/accounts              ->  create
+ * GET     /api/salesforce/accounts/:id          ->  show
+ * PUT     /api/salesforce/accounts/:id          ->  update
+ * DELETE  /api/salesforce/accounts/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var util = require('util');
+var jsforce = require('jsforce');
+
+var SalesforceAccount = require('../../models').SalesforceAccount;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of SalesforceAccounts
+exports.index = function(req, res) {
+  var attributes = ['description', 'name', 'username', 'remoteUri'];
+  var per_page = req.query.per_page ? parseInt(req.query.per_page, 10) : 100;
+  var page = req.query.page ? parseInt(req.query.page, 10) : 0;
+
+  var query = {
+    where: {},
+    limit: per_page,
+    offset: page * per_page
+  };
+
+  _.forIn(req.query, function(value, key) {
+    switch (key) {
+      case 'per_page':
+      case 'page':
+        break;
+      case 'sort_by':
+        query.order = util.format('%s %s', req.query.sort_by, req.query.sort_order || 'ASC') || null;
+        break;
+      case 'sort_order':
+        break;
+      case '$':
+        query.where.$or = [];
+        attributes.forEach(function(attribute) {
+          var tmp = {};
+          tmp[attribute] = {
+            $like: '%' + value + '%'
+          };
+
+          query.where.$or.push(tmp);
+        });
+        break;
+      default:
+        query.where[key] = {
+          $like: {}
+        };
+        query.where[key].$like = '%' + value + '%';
+    }
+  });
+
+  SalesforceAccount
+    .findAndCountAll(query)
+    .then(function(result) {
+
+      var total_pages = Math.ceil(result.count / per_page);
+      var next_page = total_pages > (query.offset + 1) ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page + 1) : null;
+      var previous_page = page > 0 ? util.format('%s://%s%s?page=%d', req.protocol, req.headers.host, req.baseUrl, page - 1) : null;
+
+      return res.status(200).send({
+        count: result.count,
+        rows: result.rows,
+        next_page: next_page,
+        previous_page: previous_page,
+        total_pages: total_pages
+      });
+
+    })
+    .catch(function(err) {
+      return res.status(500).send({
+        error: 'Something blew up!'
+      });
+    });
+}
+
+// Gets a single SalesforceAccount from the DB
+exports.show = function(req, res) {
+  SalesforceAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new SalesforceAccount in the DB
+exports.create = function(req, res) {
+  SalesforceAccount.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing SalesforceAccount in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  SalesforceAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a SalesforceAccount from the DB
+exports.destroy = function(req, res) {
+  SalesforceAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+exports.checkAccount = function(req, res) {
+  SalesforceAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(account) {
+      var conn = new jsforce.Connection({
+        oauth2: {
+          loginUrl: account.remoteUri,
+          clientId: account.clientId,
+          clientSecret: account.clientSecret,
+        }
+      });
+      var pwd = account.password.concat(account.securityToken);
+      conn.login(account.username, pwd, function(err, userInfo) {
+        if (err) {
+          return res.status(400).send(err);
+        }
+        return res.sendStatus(200);
+      });
+    })
+    .catch(handleError(res));
+}
+
+exports.accountValidation = function(req, res) {
+  var where = {};
+  where[req.params.field] = req.body.value;
+  SalesforceAccount
+    .findAndCountAll({
+      where: where
+    })
+    .then(function(result) {
+      if (result.count) {
+        return res.status(200).send({
+          isValid: false,
+          value: req.body.value
+        });
+      }
+      return res.status(200).send({
+        isValid: true,
+        value: req.body.value
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.getFields = function(req, res) {
+  SalesforceAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(account) {
+      var conn = new jsforce.Connection({
+        oauth2: {
+          loginUrl: account.remoteUri,
+          clientId: account.clientId,
+          clientSecret: account.clientSecret,
+        }
+      });
+      var pwd = account.password.concat(account.securityToken);
+      conn.login(account.username, pwd, function(err, userInfo) {
+        if (err) {
+          return res.status(400).send(err);
+        }
+        conn.sobject("Task").describe(function(err, meta) {
+          if (err) {
+            return res.status(400).send(err);
+          }
+          return res.status(200).send(meta.fields);
+        });
+      });
+    })
+    .catch(handleError(res));
+};
+
+exports.bulkDestroy = function(req, res) {
+  SalesforceAccount
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
diff --git a/server/api/salesforce_account/salesforce_account.socket.js b/server/api/salesforce_account/salesforce_account.socket.js
index 8a1ad4a..2c58d15 100644
--- a/server/api/salesforce_account/salesforce_account.socket.js
+++ b/server/api/salesforce_account/salesforce_account.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SalesforceAccount = require('../../models').SalesforceAccount;
+
+exports.register = function(socket) {
+  SalesforceAccount.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SalesforceAccount.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('salesforce_account:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('salesforce_account:remove', doc);
+}
diff --git a/server/api/salesforce_configuration/index.js b/server/api/salesforce_configuration/index.js
index 09c8d29..7936a09 100644
--- a/server/api/salesforce_configuration/index.js
+++ b/server/api/salesforce_configuration/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./salesforce_configuration.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.post('/:id/:type', auth.isAuthenticated(), controller.addConfigurationField);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/salesforce_configuration/index.spec.js b/server/api/salesforce_configuration/index.spec.js
index 17ce4b9..0d1e211 100644
--- a/server/api/salesforce_configuration/index.spec.js
+++ b/server/api/salesforce_configuration/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var salesforceConfigurationCtrlStub = {
+  index: 'salesforceConfigurationCtrl.index',
+  show: 'salesforceConfigurationCtrl.show',
+  create: 'salesforceConfigurationCtrl.create',
+  update: 'salesforceConfigurationCtrl.update',
+  destroy: 'salesforceConfigurationCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var salesforceConfigurationIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './salesforce_configuration.controller': salesforceConfigurationCtrlStub
+});
+
+describe('SalesforceConfiguration API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(salesforceConfigurationIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/salesforce/configurations', function() {
+
+    it('should route to salesforceConfiguration.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'salesforceConfigurationCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/salesforce/configurations/:id', function() {
+
+    it('should route to salesforceConfiguration.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'salesforceConfigurationCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/salesforce/configurations', function() {
+
+    it('should route to salesforceConfiguration.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'salesforceConfigurationCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/salesforce/configurations/:id', function() {
+
+    it('should route to salesforceConfiguration.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'salesforceConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/salesforce/configurations/:id', function() {
+
+    it('should route to salesforceConfiguration.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'salesforceConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/salesforce/configurations/:id', function() {
+
+    it('should route to salesforceConfiguration.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'salesforceConfigurationCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/salesforce_configuration/salesforce_configuration.controller.js b/server/api/salesforce_configuration/salesforce_configuration.controller.js
index 8628048..523aef6 100644
--- a/server/api/salesforce_configuration/salesforce_configuration.controller.js
+++ b/server/api/salesforce_configuration/salesforce_configuration.controller.js
@@ -1 +1,147 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/salesforce/configurations              ->  index
+ * POST    /api/salesforce/configurations              ->  create
+ * GET     /api/salesforce/configurations/:id          ->  show
+ * PUT     /api/salesforce/configurations/:id          ->  update
+ * DELETE  /api/salesforce/configurations/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var SalesforceConfiguration = require('../../models').SalesforceConfiguration;
+var SalesforceField = require('../../models').SalesforceField;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of SalesforceConfigurations
+exports.index = function(req, res) {
+  SalesforceConfiguration.findAll({
+      where: req.query
+    })
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single SalesforceConfiguration from the DB
+exports.show = function(req, res) {
+  SalesforceConfiguration.find({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        model: SalesforceField,
+        as: 'Subject'
+      }, {
+        model: SalesforceField,
+        as: 'Description'
+      }, {
+        model: SalesforceField,
+        as: 'Field'
+      }]
+    })
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new SalesforceConfiguration in the DB
+exports.create = function(req, res) {
+  SalesforceConfiguration.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing SalesforceConfiguration in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  SalesforceConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a SalesforceConfiguration from the DB
+exports.destroy = function(req, res) {
+  SalesforceConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+exports.addConfigurationField = function(req, res, next) {
+  // console.log(req.params);
+  var salesforceConfiguration;
+  SalesforceConfiguration
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(salesforce_configuration) {
+      salesforceConfiguration = salesforce_configuration;
+      return SalesforceField.create(req.body)
+    })
+    .then(function(salesforceField) {
+      switch (req.params.type) {
+        case 'subject':
+          return [salesforceConfiguration.addSubject(salesforceField), salesforceField];
+        case 'description':
+          return [salesforceConfiguration.addDescription(salesforceField), salesforceField];
+        case 'field':
+          return [salesforceConfiguration.addField(salesforceField), salesforceField];
+      }
+    })
+    .spread(function(data, salesforceField) {
+      return res.status(201).json(salesforceField);
+    })
+    .catch(handleError(res));
+};
diff --git a/server/api/salesforce_configuration/salesforce_configuration.socket.js b/server/api/salesforce_configuration/salesforce_configuration.socket.js
index 11b2639..c45a482 100644
--- a/server/api/salesforce_configuration/salesforce_configuration.socket.js
+++ b/server/api/salesforce_configuration/salesforce_configuration.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SalesforceConfiguration = require('../../models').SalesforceConfiguration;
+
+exports.register = function(socket) {
+  SalesforceConfiguration.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SalesforceConfiguration.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('salesforce_configuration:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('salesforce_configuration:remove', doc);
+}
diff --git a/server/api/salesforce_field/index.js b/server/api/salesforce_field/index.js
index 170e1b8..4a0ccb2 100644
--- a/server/api/salesforce_field/index.js
+++ b/server/api/salesforce_field/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./salesforce_field.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/salesforce_field/index.spec.js b/server/api/salesforce_field/index.spec.js
index 93f1e7f..639e318 100644
--- a/server/api/salesforce_field/index.spec.js
+++ b/server/api/salesforce_field/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var salesforceFieldCtrlStub = {
+  index: 'salesforceFieldCtrl.index',
+  show: 'salesforceFieldCtrl.show',
+  create: 'salesforceFieldCtrl.create',
+  update: 'salesforceFieldCtrl.update',
+  destroy: 'salesforceFieldCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var salesforceFieldIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './salesforce_field.controller': salesforceFieldCtrlStub
+});
+
+describe('SalesforceField API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(salesforceFieldIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/salesforce/fields', function() {
+
+    it('should route to salesforceField.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'salesforceFieldCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/salesforce/fields/:id', function() {
+
+    it('should route to salesforceField.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'salesforceFieldCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/salesforce/fields', function() {
+
+    it('should route to salesforceField.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'salesforceFieldCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/salesforce/fields/:id', function() {
+
+    it('should route to salesforceField.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'salesforceFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/salesforce/fields/:id', function() {
+
+    it('should route to salesforceField.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'salesforceFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/salesforce/fields/:id', function() {
+
+    it('should route to salesforceField.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'salesforceFieldCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/salesforce_field/salesforce_field.controller.js b/server/api/salesforce_field/salesforce_field.controller.js
index 7355895..bab61d7 100644
--- a/server/api/salesforce_field/salesforce_field.controller.js
+++ b/server/api/salesforce_field/salesforce_field.controller.js
@@ -1 +1,104 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/salesforce/fields              ->  index
+ * POST    /api/salesforce/fields              ->  create
+ * GET     /api/salesforce/fields/:id          ->  show
+ * PUT     /api/salesforce/fields/:id          ->  update
+ * DELETE  /api/salesforce/fields/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var SalesforceField = require('../../models').SalesforceField;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of SalesforceFields
+exports.index = function(req, res) {
+  SalesforceField.findAll()
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single SalesforceField from the DB
+exports.show = function(req, res) {
+  SalesforceField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new SalesforceField in the DB
+exports.create = function(req, res) {
+  SalesforceField.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing SalesforceField in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  SalesforceField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a SalesforceField from the DB
+exports.destroy = function(req, res) {
+  SalesforceField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
diff --git a/server/api/salesforce_field/salesforce_field.socket.js b/server/api/salesforce_field/salesforce_field.socket.js
index ae646a2..b3f5775 100644
--- a/server/api/salesforce_field/salesforce_field.socket.js
+++ b/server/api/salesforce_field/salesforce_field.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SalesforceField = require('../../models').SalesforceField;
+
+exports.register = function(socket) {
+  SalesforceField.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SalesforceField.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('salesforce_field:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('salesforce_field:remove', doc);
+}
diff --git a/server/api/service/index.js b/server/api/service/index.js
index 5cacd6c..c1bf88f 100644
--- a/server/api/service/index.js
+++ b/server/api/service/index.js
@@ -1 +1,24 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./service.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/customfields/:id', auth.isAuthenticated(), controller.showCustomField);
+router.get('/:id/customfields', auth.isAuthenticated(), controller.getCustomFields);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.serviceValidation);
+router.post('/:id/agents', auth.isAuthenticated(), controller.addAgents);
+router.post('/:id/customfields', auth.isAuthenticated(), controller.createCustomField);
+router.put('/customfields/:id', auth.isAuthenticated(), controller.updateCustomField);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.put('/:id/agents', auth.isAuthenticated(), controller.removeAgents);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/customfields/:id', auth.isAuthenticated(), controller.deactivateCustomField);
+
+module.exports = router;
diff --git a/server/api/service/service.controller.js b/server/api/service/service.controller.js
index 6e1e5bc..bb36742 100644
--- a/server/api/service/service.controller.js
+++ b/server/api/service/service.controller.js
@@ -1 +1,379 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+
+var User = require('../../models').User;
+var Service = require('../../models').Service;
+var UserHasService = require('../../models').UserHasService;
+var CustomField = require('../../models').CustomField;
+var Contact = require('../../models').Contact;
+var Util = require('../../config/util');
+
+// Get list of services
+exports.index = function(req, res, next) {
+  return Service
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get all custom_fields
+exports.getCustomFields = function(req, res) {
+  CustomField
+    .findAll({
+      where: {
+        ServiceId: req.params.id,
+        active: true
+      }
+    })
+    .then(function(customFields) {
+      return res.status(200).send(customFields);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.showCustomField = function(req, res) {
+  CustomField
+    .findById(req.params.id)
+    .then(function(customField) {
+      if (!customField) {
+        return res.sendStatus(404);
+      }
+      return res.send(customField);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.updateCustomField = function(req, res) {
+  CustomField
+    .findAll({
+      where: {
+        name: req.body.name,
+        id: {
+          $ne: req.body.id
+        }
+      }
+    })
+    .then(function(customFields) {
+      if (!customFields) {
+        return res.sendStatus(404);
+      }
+      if (customFields.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_CUSTOM_FIELD'
+        })
+      }
+      if (req.body.id) {
+        delete req.body.id;
+      }
+      CustomField
+        .findById(req.params.id)
+        .then(function(customField) {
+          if (!customField) {
+            return res.sendStatus(404);
+          }
+          var updated = _.merge(customField, req.body);
+          updated.save()
+            .then(function() {
+              return res.status(200).send(customField);
+            })
+            .catch(function(err) {
+              return handleError(res, err);
+            });
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.createCustomField = function(req, res) {
+  req.body.ServiceId = req.params.id;
+  CustomField
+    .create(req.body)
+    .then(function(customField) {
+      return res.status(201).send(customField);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single service
+exports.show = function(req, res) {
+  Service
+    .findById(req.params.id)
+    .then(function(service) {
+      if (!service) {
+        return res.sendStatus(404);
+      }
+      return res.send(service);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Validate the existence of a service
+exports.serviceValidation = function(req, res) {
+  console.log(req.body);
+  Service
+    .findAll({
+      where: {
+        id: req.body.id
+      }
+    })
+    .then(function(service) {
+      if (!service) {
+        return res.sendStatus(404);
+      }
+      return res.send(service);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new service in the DB.
+exports.create = function(req, res) {
+  Service
+    .findAll({
+      where: {
+        name: req.body.name
+      }
+    })
+    .then(function(existingServices) {
+      console.log(existingServices);
+      console.log('Finding app row.....');
+      if (!existingServices) {
+        return res.sendStatus(404);
+      }
+      if (existingServices.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_SERVICE'
+        });
+      }
+      Service
+        .create(req.body)
+        .then(function(service) {
+          return res.status(201).send(service);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing service in the DB.
+exports.update = function(req, res) {
+  Service
+    .findAll({
+      where: {
+        name: req.body.name
+      }
+    })
+    .then(function(existingServices) {
+      console.log(existingServices);
+      console.log('Finding app row.....');
+      if (!existingServices) {
+        return res.sendStatus(404);
+      }
+      if (existingServices.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_SERVICE'
+        });
+      }
+      return Service
+        .findById(req.params.id);
+    })
+    .then(function(service) {
+      if (!service) {
+        return res.sendStatus(404);
+      }
+      if (req.body.id) {
+        delete req.body.id;
+      }
+      var updated = _.merge(service, req.body);
+      return updated.save();
+    })
+    .then(function(service) {
+      return res.status(200).send(service);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+
+// Updates an existing user_has_service in the DB.
+exports.addAgents = function(req, res, next) {
+
+  console.log('addAgents');
+  console.log(req.params);
+  console.log(req.body);
+  User
+    .findAll({
+      where: {
+        id: {
+          $in: req.body.agents
+        }
+      }
+    })
+    .then(function(users) {
+      // console.log(users);
+      var userHasServices = _.map(users, function(user) {
+        return {
+          service: req.params.id,
+          membername: user.name,
+          UserId: user.id
+        };
+      });
+
+      UserHasService
+        .bulkCreate(userHasServices, {
+          individualHooks: true
+        })
+        .then(function() {
+          return res.sendStatus(200);
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+exports.removeAgents = function(req, res, next) {
+  Service
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(service) {
+      if (service) {
+        // SERVICE FOUND
+        // REMOVE AGENTS
+        service
+          .removeUsers(req.body.agents, {
+            individualHooks: true
+          })
+          .then(function() {
+            return res.sendStatus(200);
+          })
+          .catch(function(err) {
+            return next(err);
+          });
+      } else {
+        return next(new Error('no service found'));
+      }
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Deletes a service from the DB.
+exports.destroy = function(req, res) {
+  destroyService(req.params.id, res);
+};
+
+// Deletes multiple services from the DB.
+exports.bulkDestroy = function(req, res) { //add the check for associated contacts
+  destroyService(req.query.id, res);
+};
+
+function destroyService(id, res) {
+  return delOrUpdateCf(id)
+    .all()
+    .then(function() {
+      return Service.destroy({
+        where: {
+          id: id
+        },
+        individualHooks: true
+      });
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+}
+
+function delOrUpdateCf(id) {
+  return CustomField.findAll({
+      where: {
+        ServiceId: id
+      },
+      include: [{
+        model: Contact
+      }]
+    })
+    .then(function(customFields) {
+      console.log(customFields.length);
+      var delOrDeact = [];
+      _.forEach(customFields, function(elem) {
+        if (elem.Contacts.length) {
+          delOrDeact.push(elem.update({
+            active: false
+          }));
+        } else {
+          delOrDeact.push(elem.destroy());
+        }
+      });
+      return delOrDeact;
+    })
+}
+
+exports.deactivateCustomField = function(req, res) {
+  CustomField
+    .findById(req.params.id, {
+      include: [{
+        model: Contact
+      }]
+    })
+    .then(function(customField) {
+      if (!customField) {
+        return res.sendStatus(404);
+      }
+      if (customField.Contacts.length) {
+        return customField.update({
+          active: false
+        });
+      } else {
+        return customField.destroy();
+      }
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/service/service.socket.js b/server/api/service/service.socket.js
index 950a8f8..981784a 100644
--- a/server/api/service/service.socket.js
+++ b/server/api/service/service.socket.js
@@ -1 +1,33 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Service = require('../../models').Service;
+var CustomField = require('../../models').CustomField;
+
+exports.register = function(socket) {
+  Service.afterCreate(function(doc) {
+    onSave(socket, doc, 'service');
+  });
+  Service.afterDestroy(function(doc) {
+    console.log('destroy');
+    onRemove(socket, doc, 'service');
+  });
+  CustomField.afterCreate(function(doc) {
+    onSave(socket, doc, 'custom_field');
+  });
+  CustomField.afterDestroy(function(doc) {
+    onRemove(socket, doc, 'custom_field');
+  });
+}
+
+function onSave(socket, doc, type, cb) {
+  socket.emit(type + ':save', doc);
+}
+
+function onRemove(socket, doc, type, cb) {
+  console.log(type);
+  socket.emit(type + ':remove', doc);
+}
diff --git a/server/api/service/service.spec.js b/server/api/service/service.spec.js
index 25db25a..7d84147 100644
--- a/server/api/service/service.spec.js
+++ b/server/api/service/service.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/contactmanager/services', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/contactmanager/services')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/setting/index.js b/server/api/setting/index.js
index a16d57d..e30286b 100644
--- a/server/api/setting/index.js
+++ b/server/api/setting/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./setting.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/internal', auth.isAuthenticated(), controller.getInternal);
+router.get('/mailbox', auth.isAuthenticated(), controller.getMailbox);
+router.get('/info', controller.getInfo);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/setting/setting.controller.js b/server/api/setting/setting.controller.js
index b2b2657..6c2943d 100644
--- a/server/api/setting/setting.controller.js
+++ b/server/api/setting/setting.controller.js
@@ -1 +1,172 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Settings = require('../../models').Settings;
+var Agent = require('../../models').User;
+var VoiceMail = require('../../models').VoiceVoicemail;
+
+// Get list of settings
+exports.index = function(req, res) {
+  return Settings
+    .findAll()
+    .then(function(settings) {
+      return res.status(200).send(settings);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get internal
+exports.getInternal = function(req, res) {
+  var _settings;
+  return Settings
+    .findById(1)
+    .then(function(settings) {
+      _settings = settings;
+      return Agent
+        .max('internal');
+    })
+    .then(function(internal) {
+      var _internal = {
+        value: 0
+      };
+
+      if (internal) {
+        _internal.value = ++internal;
+      } else {
+        _internal.value = _settings.min_internal;
+      }
+
+      return res.status(200).send(_internal);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get internal
+exports.getMailbox = function(req, res) {
+  var _settings;
+  return Settings
+    .findById(1)
+    .then(function(settings) {
+      _settings = settings;
+      return VoiceMail
+        .max('mailbox');
+    })
+    .then(function(mailbox) {
+      var _mailbox = {
+        value: 0
+      };
+
+      if (mailbox) {
+        _mailbox.value = ++mailbox;
+      } else {
+        _mailbox.value = _settings.min_mailbox;
+      }
+
+      return res.status(200).send(_mailbox);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Get list of settings
+exports.getInfo = function(req, res) {
+
+  var info = require('../../../package.json');
+
+  return res.status(200).send({
+    name: info.name,
+    version: info.version,
+    engines: info.engines
+  });
+};
+
+// Get a single setting
+exports.show = function(req, res) {
+  Settings
+    .findById(req.params.id)
+    .then(function(setting) {
+      if (!setting) {
+        return res.sendStatus(404);
+      }
+      return res.send(setting);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new setting in the DB.
+exports.create = function(req, res) {
+  Settings
+    .create(req.body)
+    .then(function(setting) {
+      return res.status(201).send(setting);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing setting in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  Settings
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(setting) {
+      if (!setting) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(setting, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(setting);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a setting from the DB.
+exports.destroy = function(req, res) {
+  Settings
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(setting) {
+      if (!setting) {
+        return res.sendStatus(404);
+      }
+      setting.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/setting/setting.socket.js b/server/api/setting/setting.socket.js
index 2dcd5c3..611827e 100644
--- a/server/api/setting/setting.socket.js
+++ b/server/api/setting/setting.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Settings = require('../../models').Settings;
+
+exports.register = function(socket) {
+  Settings.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Settings.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('setting:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('setting:remove', doc);
+}
diff --git a/server/api/sound/index.js b/server/api/sound/index.js
index 194292b..4ade980 100644
--- a/server/api/sound/index.js
+++ b/server/api/sound/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./sound.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/download', auth.isAuthenticated(), controller.download);
+router.get('/:id/stream', auth.isAuthenticated(), controller.stream);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/sound/sound.controller.js b/server/api/sound/sound.controller.js
index b624d91..335d2c9 100644
--- a/server/api/sound/sound.controller.js
+++ b/server/api/sound/sound.controller.js
@@ -1 +1,266 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Sound = require('../../models').Sound;
+var uploadFile = require('upload-file');
+var sr = require('simple-random');
+var sox = require('sox');
+var path = require('path');
+var config = require('../../config/environment');
+var fs = require('fs');
+var sequelize = require('../../models').sequelize;
+var base64 = require('file-base64');
+var Promise = require('bluebird');
+
+// Get list of Sounds
+exports.index = function(req, res) {
+  Sound
+    .findAndCountAll()
+    .then(function(Sounds) {
+      return res.status(200).send(Sounds);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single Sound
+exports.show = function(req, res) {
+  Sound
+    .findById(req.params.id)
+    .then(function(Sound) {
+      if (!Sound) {
+        return res.sendStatus(404);
+      }
+      return res.send(Sound);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new Sound in the DB.
+exports.create = function(req, res) {
+  var upload = new uploadFile({
+    dest: path.join(config.originalSounds),
+    maxFileSize: 15 * 1000000,
+    maxNumberOfFiles: 10,
+    minNumberOfFiles: 0,
+    acceptFileTypes: /(\.|\/)(wav|mp3|gsm|ogg)$/i,
+    rename: function(name, file) {
+      return sr() + path.extname(file.filename);
+    },
+    messages: {
+      maxNumberOfFiles: "Max number of files upload exceeded",
+      acceptFileTypes: "Only wav,mp3,gsm, and ogg format accepted",
+      maxFileSize: "The maximum file size is 15 Mb",
+      invalidRequest: "Invalid request"
+    }
+  });
+
+  upload.on('end', function(fields, files) {
+    if (!files.file.filename) {
+      return res.status(500).send(upload);
+    }
+
+    var job_details = {};
+    var original_format = path.extname(files.file.filename);
+    job_details.description = "";
+    job_details.original_format = path.extname(files.file.filename).substring(1);
+    job_details.converted_format = 'wav';
+    job_details.name = job_details.display_name = path.basename(files.file.path, original_format);
+    job_details.save_name = path.basename(files.file.filename, original_format);
+
+
+
+    var filepath = path.join(config.originalSounds, job_details.save_name + original_format);
+    var destination = path.join(config.convertedSounds, job_details.save_name + '.' + job_details.converted_format);
+    //
+
+    fs.chmodSync(filepath, parseInt('0777', 8));
+    sox.identify(filepath, function(err, result) {
+      // console.log('************** Result: ', result);
+    });
+
+    var job = sox.transcode(filepath, destination, {
+      sampleRate: 8000,
+      format: 'wav',
+      channelCount: 1,
+      bitRate: 192 * 1024,
+      compressionQuality: 5,
+    });
+
+
+
+    job.on('error', function(err) {
+      console.error('********** Conversion Error: ', err);
+      fs.unlink(filepath, function(err) {
+        if (err)
+          return handleError(res, err);
+      });
+      return handleError(res, err);
+    });
+
+    job.on('progress', function(amountDone, amountTotal) {
+      // console.log("*********** Progress", amountDone, amountTotal);
+    });
+
+    job.on('src', function(info) {
+      // console.log('********** src: ', info);
+      /* info looks like:
+      {
+        format: 'wav',
+        duration: 1.5,
+        sampleCount: 66150,
+        channelCount: 1,
+        bitRate: 722944,
+        sampleRate: 44100,
+      }
+      */
+      job_details.original_duration = isNaN(info.duration) ? null : info.duration * 1000;
+      job_details.original_sampleCount = isNaN(info.sampleCount) ? null : info.sampleCount;
+      job_details.original_channelCount = isNaN(info.channelCount) ? null : info.channelCount;
+      job_details.original_bitRate = isNaN(info.bitRate) ? null : info.bitRate;
+      job_details.original_sampleRate = isNaN(info.sampleRate) ? null : info.sampleRate;
+    });
+
+    job.on('dest', function(info) {
+      // console.log('********** dest: ', info);
+      /* info looks like:
+      {
+        sampleRate: 44100,
+        format: 'mp3',
+        channelCount: 2,
+        sampleCount: 67958,
+        duration: 1.540998,
+        bitRate: 196608,
+      }
+      */
+      job_details.converted_duration = isNaN(info.duration) ? null : info.duration * 1000;
+      job_details.converted_sampleCount = isNaN(info.sampleCount) ? null : info.sampleCount;
+      job_details.converted_channelCount = isNaN(info.channelCount) ? null : info.channelCount;
+      job_details.converted_bitRate = isNaN(info.bitRate) ? null : info.bitRate;
+      job_details.converted_sampleRate = isNaN(info.sampleRate) ? null : info.sampleRate;
+    });
+
+    job.on('end', function() {
+      console.log("Conversion completed");
+      fs.chmodSync(destination, parseInt('0777', 8));
+      Sound
+        .create(job_details)
+        .then(function(sound) {
+          return res.status(201).send(sound);
+        })
+        .catch(function(err) {
+          console.log(err);
+          return handleError(res, err);
+        });
+    });
+
+    job.start();
+
+  });
+
+  upload.on('error', function(err) {
+    console.log('********** Upload error :', err)
+    return handleError(res, err);
+  });
+
+  upload.parse(req);
+};
+
+// Updates an existing upload in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  Sound
+    .findById(req.params.id)
+    .then(function(sound) {
+      if (!sound) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(sound, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(sound);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a Sound from the DB.
+exports.destroy = function(req, res) {
+  Sound
+    .findById(req.params.id)
+    .then(function(sound) {
+      if (!sound) {
+        return res.status(404).send(sound);
+      }
+      var original_filepath = path.join(config.originalSounds, sound.save_name + '.' + sound.original_format);
+      var converted_filepath = path.join(config.convertedSounds, sound.save_name + '.' + sound.converted_format);
+      return sequelize.transaction(function(t) {
+          return sound.destroy({
+              transaction: t
+            })
+            .then(function() {
+              fs.unlink(original_filepath, function(err) {
+                if (err)
+                  return handleError(res, err);
+              });
+              fs.unlink(converted_filepath, function(err) {
+                if (err)
+                  return handleError(res, err);
+              });
+              return res.status(200).send(sound);
+            })
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+//Download an uploaded file
+exports.download = function(req, res) {
+  getStream(req, res, 'original');
+};
+
+//Stream an uploaded file
+exports.stream = function(req, res) {
+  getStream(req, res, 'converted');
+}
+
+
+function getStream(req, res, value) {
+  return Sound
+    .findById(req.params.id)
+    .then(function(sound) {
+      var readFile = Promise.promisify(fs.readFile);
+      return readFile(path.join(config[value + 'Sounds'], sound.save_name + '.' + sound[value + '_format']));
+    })
+    .then(function(result) {
+      var base64String = new Buffer(result, 'binary').toString('base64');
+      return base64String;
+    })
+    .then(function(base64String) {
+      return res.status(200).send({
+        downloadString: base64String
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+}
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/sound/sound.socket.js b/server/api/sound/sound.socket.js
index 925f82e..cef5b4b 100644
--- a/server/api/sound/sound.socket.js
+++ b/server/api/sound/sound.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Sound = require('../../models').Sound;
+
+exports.register = function(socket) {
+  Sound.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Sound.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('sound:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('sound:remove', doc);
+}
diff --git a/server/api/sound/sound.spec.js b/server/api/sound/sound.spec.js
index 4e87541..970195e 100644
--- a/server/api/sound/sound.spec.js
+++ b/server/api/sound/sound.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/sounds', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/sounds')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/square_odbc/index.js b/server/api/square_odbc/index.js
index 8490143..5f8a51e 100644
--- a/server/api/square_odbc/index.js
+++ b/server/api/square_odbc/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./square_odbc.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id/check', auth.isAuthenticated(), controller.checkConnection);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/square_odbc/square_odbc.controller.js b/server/api/square_odbc/square_odbc.controller.js
index e6b7145..69af149 100644
--- a/server/api/square_odbc/square_odbc.controller.js
+++ b/server/api/square_odbc/square_odbc.controller.js
@@ -1 +1,135 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var db = require("odbc")();
+
+var SquareOdbc = require('../../models').SquareOdbc;
+var Util = require('../../config/util');
+
+// Get list of square_odbcs
+exports.index = function(req, res) {
+  return SquareOdbc
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single square_odbc
+exports.show = function(req, res) {
+  return SquareOdbc
+    .findById(req.params.id)
+    .then(function(square_odbc) {
+      if (!square_odbc) {
+        return res.sendStatus(404);
+      }
+      return res.send(square_odbc);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new square_odbc in the DB.
+exports.create = function(req, res) {
+  return SquareOdbc
+    .create(req.body)
+    .then(function(square_odbc) {
+      return res.status(201).send(square_odbc);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing square_odbc in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return SquareOdbc
+    .findById(req.params.id)
+    .then(function(square_odbc) {
+      if (!square_odbc) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(square_odbc, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(square_odbc);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a square_odbc from the DB.
+exports.destroy = function(req, res) {
+  return SquareOdbc
+    .findById(req.params.id)
+    .then(function(odbc) {
+      if (!odbc) {
+        return res.sendStatus(404);
+      }
+      return odbc
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a square_project from the DB.
+exports.bulkDestroy = function(req, res) {
+  return SquareOdbc
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.checkConnection = function(req, res) {
+  return SquareOdbc
+    .findById(req.params.id)
+    .then(function(square_odbc) {
+      if (!square_odbc) {
+        return res.sendStatus(404);
+      }
+      db.open(square_odbc.dsn, function(err) {
+        if (err) {
+          return handleError(res, err);
+        }
+        db.close(function(err) {
+          if (err) {
+            return handleError(res, err);
+          }
+          return res.sendStatus(200);
+        })
+      })
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/square_odbc/square_odbc.socket.js b/server/api/square_odbc/square_odbc.socket.js
index c80cdc0..af0c04a 100644
--- a/server/api/square_odbc/square_odbc.socket.js
+++ b/server/api/square_odbc/square_odbc.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SquareOdbc = require('../../models').SquareOdbc;
+
+exports.register = function(socket) {
+  SquareOdbc.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SquareOdbc.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('square_odbc:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('square_odbc:remove', doc);
+}
diff --git a/server/api/square_odbc/square_odbc.spec.js b/server/api/square_odbc/square_odbc.spec.js
index 4a90011..ce9350c 100644
--- a/server/api/square_odbc/square_odbc.spec.js
+++ b/server/api/square_odbc/square_odbc.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/square/odbc', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/square/odbc')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/square_project/index.js b/server/api/square_project/index.js
index b11fdcb..614ce23 100644
--- a/server/api/square_project/index.js
+++ b/server/api/square_project/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./square_project.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/download', controller.download);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/square_project/square_project.controller.js b/server/api/square_project/square_project.controller.js
index e1d536b..3531326 100644
--- a/server/api/square_project/square_project.controller.js
+++ b/server/api/square_project/square_project.controller.js
@@ -1 +1,132 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var path = require('path');
+var config = require('../../config/environment');
+var Util = require('../../config/util');
+
+var SquareProject = require('../../models').SquareProject;
+
+// Get list of square_projects
+exports.index = function(req, res) {
+  return SquareProject
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single square_project
+exports.show = function(req, res) {
+  return SquareProject
+    .findById(req.params.id)
+    .then(function(square_project) {
+      if (!square_project) {
+        return res.sendStatus(404);
+      }
+      return res.send(square_project);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new square_project in the DB.
+exports.create = function(req, res) {
+  return SquareProject
+    .create(req.body)
+    .then(function(square_project) {
+      return res.status(201).send(square_project);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing square_project in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return SquareProject
+    .findById(req.params.id)
+    .then(function(square_project) {
+      if (!square_project) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(square_project, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(square_project);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a square_project from the DB.
+exports.destroy = function(req, res) {
+  return SquareProject
+    .findById(req.params.id)
+    .then(function(project) {
+      if (!project) {
+        return res.sendStatus(404);
+      }
+      return project
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a square_project from the DB.
+exports.bulkDestroy = function(req, res) {
+  return SquareProject
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.download = function(req, res, next) {
+  SquareProject
+    .findById(req.params.id)
+    .then(function(square_project) {
+      if (!square_project) {
+        return res.sendStatus(404);
+      }
+      console.log(req.query.filename);
+      res.set({
+        "Content-Disposition": "attachment; filename=\"" + req.query.filename +
+          ".xml\"",
+        "Content-Type": "txt/xml"
+      });
+      return res.send(square_project.preproduction);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/square_project/square_project.socket.js b/server/api/square_project/square_project.socket.js
index 45dc6fa..bc43e25 100644
--- a/server/api/square_project/square_project.socket.js
+++ b/server/api/square_project/square_project.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SquareProject = require('../../models').SquareProject;
+
+exports.register = function(socket) {
+  SquareProject.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SquareProject.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  SquareProject.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('square_project:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('square_project:remove', doc);
+}
diff --git a/server/api/sugarcrm_account/index.js b/server/api/sugarcrm_account/index.js
index 250fbba..e87249c 100644
--- a/server/api/sugarcrm_account/index.js
+++ b/server/api/sugarcrm_account/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./sugarcrm_account.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id/check', auth.isAuthenticated(), controller.checkAccount);
+router.get('/:id/fields', auth.isAuthenticated(), controller.getFields);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/validate/:field', auth.isAuthenticated(), controller.accountValidation);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/sugarcrm_account/index.spec.js b/server/api/sugarcrm_account/index.spec.js
index ec7864d..758de1a 100644
--- a/server/api/sugarcrm_account/index.spec.js
+++ b/server/api/sugarcrm_account/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var sugarcrmAccountCtrlStub = {
+  index: 'sugarcrmAccountCtrl.index',
+  show: 'sugarcrmAccountCtrl.show',
+  create: 'sugarcrmAccountCtrl.create',
+  update: 'sugarcrmAccountCtrl.update',
+  destroy: 'sugarcrmAccountCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var sugarcrmAccountIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './sugarcrm_account.controller': sugarcrmAccountCtrlStub
+});
+
+describe('SugarcrmAccount API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(sugarcrmAccountIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/sugarcrm/accounts', function() {
+
+    it('should route to sugarcrmAccount.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'sugarcrmAccountCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/sugarcrm/accounts/:id', function() {
+
+    it('should route to sugarcrmAccount.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'sugarcrmAccountCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/sugarcrm/accounts', function() {
+
+    it('should route to sugarcrmAccount.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'sugarcrmAccountCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/sugarcrm/accounts/:id', function() {
+
+    it('should route to sugarcrmAccount.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'sugarcrmAccountCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/sugarcrm/accounts/:id', function() {
+
+    it('should route to sugarcrmAccount.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'sugarcrmAccountCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/sugarcrm/accounts/:id', function() {
+
+    it('should route to sugarcrmAccount.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'sugarcrmAccountCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/sugarcrm_account/sugarcrm_account.controller.js b/server/api/sugarcrm_account/sugarcrm_account.controller.js
index 7ecdc70..afa4692 100644
--- a/server/api/sugarcrm_account/sugarcrm_account.controller.js
+++ b/server/api/sugarcrm_account/sugarcrm_account.controller.js
@@ -1 +1,200 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/sugarcrm/accounts              ->  index
+ * POST    /api/sugarcrm/accounts              ->  create
+ * GET     /api/sugarcrm/accounts/:id          ->  show
+ * PUT     /api/sugarcrm/accounts/:id          ->  update
+ * DELETE  /api/sugarcrm/accounts/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var SugarcrmAccount = require('../../models').SugarcrmAccount;
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function responseWithoutResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function() {
+    res.sendStatus(statusCode);
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of SugarcrmAccounts
+exports.index = function(req, res) {
+  return SugarcrmAccount
+    .scope('configurations')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single SugarcrmAccount from the DB
+exports.show = function(req, res) {
+  return SugarcrmAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new SugarcrmAccount in the DB
+exports.create = function(req, res) {
+  return SugarcrmAccount.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing SugarcrmAccount in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return SugarcrmAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a SugarcrmAccount from the DB
+exports.destroy = function(req, res) {
+  return SugarcrmAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+exports.checkAccount = function(req, res) {
+  return SugarcrmAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(account) {
+      var sugar = require('node-sugarcrm-client');
+      sugar.init({
+        apiURL: Util.stripTrailingSlash(account.remoteUri) + "/service/v4_1/rest.php",
+        login: account.username,
+        passwd: account.password
+      });
+      sugar.login(function(sessionId) {
+        if (!sessionId) {
+          return res.sendStatus(400);
+        }
+        return res.sendStatus(200);
+      });
+    })
+    .catch(handleError(res));
+}
+
+exports.bulkDestroy = function(req, res) {
+  SugarcrmAccount
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(handleError(res));
+};
+
+exports.accountValidation = function(req, res) {
+  var where = {};
+  where[req.params.field] = req.body.value;
+  return SugarcrmAccount
+    .findAndCountAll({
+      where: where
+    })
+    .then(function(result) {
+      if (result.count) {
+        return res.status(200).send({
+          isValid: false,
+          value: req.body.value
+        });
+      }
+      return res.status(200).send({
+        isValid: true,
+        value: req.body.value
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.getFields = function(req, res) {
+  var sugar = require('node-sugarcrm-client');
+  return SugarcrmAccount.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(account) {
+      sugar.init({
+        apiURL: Util.stripTrailingSlash(account.remoteUri) + "/service/v4_1/rest.php",
+        login: account.username,
+        passwd: account.password
+      });
+      sugar.login(function(sessionId) {
+        if (!sessionId) {
+          return res.sendStatus(400);
+        }
+        var params = {
+          session: sessionId,
+          module_name: "Cases"
+        };
+        sugar.call("get_module_fields", params, function(result, err) {
+          if (err) {
+            return res.sendStatus(400);
+          }
+          return res.status(200).send(result.module_fields);
+        });
+      });
+    })
+    .catch(handleError(res));
+};
diff --git a/server/api/sugarcrm_account/sugarcrm_account.socket.js b/server/api/sugarcrm_account/sugarcrm_account.socket.js
index f1d8d5b..859bf6d 100644
--- a/server/api/sugarcrm_account/sugarcrm_account.socket.js
+++ b/server/api/sugarcrm_account/sugarcrm_account.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SugarcrmAccount = require('../../models').SugarcrmAccount;
+
+exports.register = function(socket) {
+  SugarcrmAccount.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SugarcrmAccount.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('sugarcrm_account:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('sugarcrm_account:remove', doc);
+}
diff --git a/server/api/sugarcrm_configuration/index.js b/server/api/sugarcrm_configuration/index.js
index d2036ad..90111bf 100644
--- a/server/api/sugarcrm_configuration/index.js
+++ b/server/api/sugarcrm_configuration/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./sugarcrm_configuration.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.post('/:id/:type', auth.isAuthenticated(), controller.addConfigurationField);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/sugarcrm_configuration/index.spec.js b/server/api/sugarcrm_configuration/index.spec.js
index f9621bc..ba1b022 100644
--- a/server/api/sugarcrm_configuration/index.spec.js
+++ b/server/api/sugarcrm_configuration/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var sugarcrmConfigurationCtrlStub = {
+  index: 'sugarcrmConfigurationCtrl.index',
+  show: 'sugarcrmConfigurationCtrl.show',
+  create: 'sugarcrmConfigurationCtrl.create',
+  update: 'sugarcrmConfigurationCtrl.update',
+  destroy: 'sugarcrmConfigurationCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var sugarcrmConfigurationIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './sugarcrm_configuration.controller': sugarcrmConfigurationCtrlStub
+});
+
+describe('SugarcrmConfiguration API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(sugarcrmConfigurationIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/sugarcrm/configurations', function() {
+
+    it('should route to sugarcrmConfiguration.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'sugarcrmConfigurationCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/sugarcrm/configurations/:id', function() {
+
+    it('should route to sugarcrmConfiguration.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'sugarcrmConfigurationCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/sugarcrm/configurations', function() {
+
+    it('should route to sugarcrmConfiguration.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'sugarcrmConfigurationCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/sugarcrm/configurations/:id', function() {
+
+    it('should route to sugarcrmConfiguration.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'sugarcrmConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/sugarcrm/configurations/:id', function() {
+
+    it('should route to sugarcrmConfiguration.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'sugarcrmConfigurationCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/sugarcrm/configurations/:id', function() {
+
+    it('should route to sugarcrmConfiguration.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'sugarcrmConfigurationCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/sugarcrm_configuration/sugarcrm_configuration.controller.js b/server/api/sugarcrm_configuration/sugarcrm_configuration.controller.js
index a82a747..5918283 100644
--- a/server/api/sugarcrm_configuration/sugarcrm_configuration.controller.js
+++ b/server/api/sugarcrm_configuration/sugarcrm_configuration.controller.js
@@ -1 +1,145 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/sugarcrm/configurations              ->  index
+ * POST    /api/sugarcrm/configurations              ->  create
+ * GET     /api/sugarcrm/configurations/:id          ->  show
+ * PUT     /api/sugarcrm/configurations/:id          ->  update
+ * DELETE  /api/sugarcrm/configurations/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var SugarcrmConfiguration = require('../../models').SugarcrmConfiguration;
+var SugarcrmField = require('../../models').SugarcrmField;
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of SugarcrmConfigurations
+exports.index = function(req, res) {
+  SugarcrmConfiguration.findAll({
+      where: req.query
+    })
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single SugarcrmConfiguration from the DB
+exports.show = function(req, res) {
+  SugarcrmConfiguration.find({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        model: SugarcrmField,
+        as: 'Subject'
+      }, {
+        model: SugarcrmField,
+        as: 'Description'
+      }, {
+        model: SugarcrmField,
+        as: 'Field'
+      }]
+    })
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new SugarcrmConfiguration in the DB
+exports.create = function(req, res) {
+  SugarcrmConfiguration.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing SugarcrmConfiguration in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  SugarcrmConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a SugarcrmConfiguration from the DB
+exports.destroy = function(req, res) {
+  SugarcrmConfiguration.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+exports.addConfigurationField = function(req, res, next) {
+  var sugarcrmConfiguration;
+  SugarcrmConfiguration
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(sugarcrm_configuration) {
+      sugarcrmConfiguration = sugarcrm_configuration;
+      return SugarcrmField.create(req.body)
+    })
+    .then(function(sugarcrmField) {
+      switch (req.params.type) {
+        case 'subject':
+          return [sugarcrmConfiguration.addSubject(sugarcrmField), sugarcrmField];
+        case 'description':
+          return [sugarcrmConfiguration.addDescription(sugarcrmField), sugarcrmField];
+        case 'field':
+          return [sugarcrmConfiguration.addField(sugarcrmField), sugarcrmField];
+      }
+    })
+    .spread(function(data, sugarcrmField) {
+      return res.status(201).json(sugarcrmField);
+    })
+    .catch(handleError(res));
+};
diff --git a/server/api/sugarcrm_configuration/sugarcrm_configuration.socket.js b/server/api/sugarcrm_configuration/sugarcrm_configuration.socket.js
index ac15aed..c4464da 100644
--- a/server/api/sugarcrm_configuration/sugarcrm_configuration.socket.js
+++ b/server/api/sugarcrm_configuration/sugarcrm_configuration.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SugarcrmConfiguration = require('../../models').SugarcrmConfiguration;
+
+exports.register = function(socket) {
+  SugarcrmConfiguration.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SugarcrmConfiguration.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('sugarcrm_configuration:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('sugarcrm_configuration:remove', doc);
+}
diff --git a/server/api/sugarcrm_field/index.js b/server/api/sugarcrm_field/index.js
index 668a7eb..edc0dc9 100644
--- a/server/api/sugarcrm_field/index.js
+++ b/server/api/sugarcrm_field/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./sugarcrm_field.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/sugarcrm_field/index.spec.js b/server/api/sugarcrm_field/index.spec.js
index 3c89fde..f8a20f7 100644
--- a/server/api/sugarcrm_field/index.spec.js
+++ b/server/api/sugarcrm_field/index.spec.js
@@ -1 +1,97 @@
\ No newline at end of file
+'use strict';
+
+var proxyquire = require('proxyquire').noPreserveCache();
+
+var sugarcrmFieldCtrlStub = {
+  index: 'sugarcrmFieldCtrl.index',
+  show: 'sugarcrmFieldCtrl.show',
+  create: 'sugarcrmFieldCtrl.create',
+  update: 'sugarcrmFieldCtrl.update',
+  destroy: 'sugarcrmFieldCtrl.destroy'
+};
+
+var routerStub = {
+  get: sinon.spy(),
+  put: sinon.spy(),
+  patch: sinon.spy(),
+  post: sinon.spy(),
+  delete: sinon.spy()
+};
+
+// require the index with our stubbed out modules
+var sugarcrmFieldIndex = proxyquire('./index.js', {
+  'express': {
+    Router: function() {
+      return routerStub;
+    }
+  },
+  './sugarcrm_field.controller': sugarcrmFieldCtrlStub
+});
+
+describe('SugarcrmField API Router:', function() {
+
+  it('should return an express router instance', function() {
+    expect(sugarcrmFieldIndex).to.equal(routerStub);
+  });
+
+  describe('GET /api/sugarcrm/fields', function() {
+
+    it('should route to sugarcrmField.controller.index', function() {
+      expect(routerStub.get
+        .withArgs('/', 'sugarcrmFieldCtrl.index')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('GET /api/sugarcrm/fields/:id', function() {
+
+    it('should route to sugarcrmField.controller.show', function() {
+      expect(routerStub.get
+        .withArgs('/:id', 'sugarcrmFieldCtrl.show')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('POST /api/sugarcrm/fields', function() {
+
+    it('should route to sugarcrmField.controller.create', function() {
+      expect(routerStub.post
+        .withArgs('/', 'sugarcrmFieldCtrl.create')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PUT /api/sugarcrm/fields/:id', function() {
+
+    it('should route to sugarcrmField.controller.update', function() {
+      expect(routerStub.put
+        .withArgs('/:id', 'sugarcrmFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('PATCH /api/sugarcrm/fields/:id', function() {
+
+    it('should route to sugarcrmField.controller.update', function() {
+      expect(routerStub.patch
+        .withArgs('/:id', 'sugarcrmFieldCtrl.update')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+  describe('DELETE /api/sugarcrm/fields/:id', function() {
+
+    it('should route to sugarcrmField.controller.destroy', function() {
+      expect(routerStub.delete
+        .withArgs('/:id', 'sugarcrmFieldCtrl.destroy')
+        ).to.have.been.calledOnce;
+    });
+
+  });
+
+});
diff --git a/server/api/sugarcrm_field/sugarcrm_field.controller.js b/server/api/sugarcrm_field/sugarcrm_field.controller.js
index 2b39dc4..eb6fe3d 100644
--- a/server/api/sugarcrm_field/sugarcrm_field.controller.js
+++ b/server/api/sugarcrm_field/sugarcrm_field.controller.js
@@ -1 +1,104 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/sugarcrm/fields              ->  index
+ * POST    /api/sugarcrm/fields              ->  create
+ * GET     /api/sugarcrm/fields/:id          ->  show
+ * PUT     /api/sugarcrm/fields/:id          ->  update
+ * DELETE  /api/sugarcrm/fields/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+
+var SugarcrmField = require('../../models').SugarcrmField;
+
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) { 
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of SugarcrmFields
+exports.index = function(req, res) {
+  SugarcrmField.findAll()
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single SugarcrmField from the DB
+exports.show = function(req, res) {
+  SugarcrmField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Creates a new SugarcrmField in the DB
+exports.create = function(req, res) {
+  SugarcrmField.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing SugarcrmField in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  SugarcrmField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a SugarcrmField from the DB
+exports.destroy = function(req, res) {
+  SugarcrmField.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
diff --git a/server/api/sugarcrm_field/sugarcrm_field.socket.js b/server/api/sugarcrm_field/sugarcrm_field.socket.js
index a491226..bcc89cc 100644
--- a/server/api/sugarcrm_field/sugarcrm_field.socket.js
+++ b/server/api/sugarcrm_field/sugarcrm_field.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var SugarcrmField = require('../../models').SugarcrmField;
+
+exports.register = function(socket) {
+  SugarcrmField.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  SugarcrmField.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('sugarcrm_field:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('sugarcrm_field:remove', doc);
+}
diff --git a/server/api/tag/index.js b/server/api/tag/index.js
index 0ea526d..17d7f06 100644
--- a/server/api/tag/index.js
+++ b/server/api/tag/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./tag.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/tag/tag.controller.js b/server/api/tag/tag.controller.js
index e07f283..38b0632 100644
--- a/server/api/tag/tag.controller.js
+++ b/server/api/tag/tag.controller.js
@@ -1 +1,116 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Tag = require('../../models').Tag;
+var Util = require('../../config/util');
+
+// Get list of tags
+exports.index = function(req, res, next) {
+  return Tag
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single tag
+exports.show = function(req, res) {
+  Tag
+    .findById(req.params.id)
+    .then(function(tag) {
+      if (!tag) {
+        return res.sendStatus(404);
+      }
+      return res.send(tag);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new tag in the DB.
+exports.create = function(req, res) {
+  Tag
+    .create(req.body)
+    .then(function(tag) {
+      return res.status(201).send(tag);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing tag in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return Tag
+    .findById(req.params.id)
+    .then(function(tag) {
+      if (!tag) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(tag, req.body);
+      return updated
+        .save();
+    })
+    .then(function(tag) {
+      return res.status(200).send(tag);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a tag from the DB.
+exports.destroy = function(req, res) {
+  return Tag
+    .scope('configuration')
+    .findById(req.params.id)
+    .then(function(tag) {
+      if (!tag) {
+        return res.sendStatus(404);
+      }
+      if (tagHasAssociation(tag)) {
+        var err = new Error();
+        err.message = 'Tag is associated with an integration\'s configuration';
+        throw err;
+      }
+      return tag.destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  Tag
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
+
+function tagHasAssociation(tag) {
+  return ((tag.ZendeskConfigurations && tag.ZendeskConfigurations.length > 0));
+}
diff --git a/server/api/tag/tag.socket.js b/server/api/tag/tag.socket.js
index 7f7ad64..790b0a2 100644
--- a/server/api/tag/tag.socket.js
+++ b/server/api/tag/tag.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Tag = require('../../models').Tag;
+
+exports.register = function(socket) {
+  Tag.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Tag.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('tag:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('tag:remove', doc);
+}
diff --git a/server/api/tag/tag.spec.js b/server/api/tag/tag.spec.js
index f435dbf..794a03a 100644
--- a/server/api/tag/tag.spec.js
+++ b/server/api/tag/tag.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/tags', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/tags')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/team/index.js b/server/api/team/index.js
index d947449..afa9ea4 100644
--- a/server/api/team/index.js
+++ b/server/api/team/index.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./team.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/agents', auth.isAuthenticated(), controller.showAgents);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/agents', auth.isAuthenticated(), controller.addAgents);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/:id/agents', auth.isAuthenticated(), controller.removeAgents);
+
+module.exports = router;
diff --git a/server/api/team/team.controller.js b/server/api/team/team.controller.js
index 3d56704..e911873 100644
--- a/server/api/team/team.controller.js
+++ b/server/api/team/team.controller.js
@@ -1 +1,170 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var Team = require('../../models').Team;
+var Agent = require('../../models').User;
+
+// Get list of teams
+exports.index = function(req, res) {
+  return Team
+    .scope('default')
+    .findAndCountAll(_.merge(Util.getQuery(req.query)))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single team
+exports.show = function(req, res) {
+  return Team
+    .scope('default')
+    .findById(req.params.id)
+    .then(function(team) {
+      if (!team) {
+        return res.sendStatus(404);
+      }
+      return res.send(team);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single team
+exports.showAgents = function(req, res) {
+  return Team
+    .scope('default')
+    .findById(req.params.id)
+    .then(function(team) {
+      if (!team) {
+        return res.sendStatus(404);
+      }
+      return res.status(200).send(team.Users);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new team in the DB.
+exports.create = function(req, res) {
+  return Team
+    .scope('default')
+    .create(req.body)
+    .then(function(team) {
+      return res.status(201).send(team);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing team in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return Team
+    .findById(req.params.id)
+    .then(function(team) {
+      if (!team) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(team, req.body);
+      return updated
+        .save();
+    })
+    .then(function(team) {
+      return res.status(200).send(team);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a team from the DB.
+exports.destroy = function(req, res) {
+  return Team
+    .findById(req.params.id)
+    .then(function(team) {
+      if (!team) {
+        return res.sendStatus(404);
+      }
+      return team.destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return Team
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing user_has_mail_queue in the DB.
+exports.addAgents = function(req, res, next) {
+  return Team
+    .findById(req.params.id)
+    .then(function(team) {
+      if (!team) {
+        return res.sendStatus(404);
+      }
+      return team
+        .addUsers(req.body.agents);
+    })
+    .then(function() {
+      return res.sendStatus(201);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+exports.removeAgents = function(req, res, next) {
+  return Team
+    .findById(req.params.id)
+    .then(function(team) {
+      if (!team) {
+        return res.sendStatus(404);
+      }
+      return team
+        .removeUser(req.query.agents);
+    })
+    .then(function() {
+      return res.sendStatus(202);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/team/team.socket.js b/server/api/team/team.socket.js
index 5473cf9..3f89dc2 100644
--- a/server/api/team/team.socket.js
+++ b/server/api/team/team.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var team = require('../../models').Team;
+
+exports.register = function(socket) {
+  team.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  team.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  team.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('team:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('team:remove', doc);
+}
diff --git a/server/api/team/team.spec.js b/server/api/team/team.spec.js
index 9fb580d..b76928a 100644
--- a/server/api/team/team.spec.js
+++ b/server/api/team/team.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/teams', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/teams')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/telephone/index.js b/server/api/telephone/index.js
index addf649..f7e10cb 100644
--- a/server/api/telephone/index.js
+++ b/server/api/telephone/index.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./telephone.controller');
+var license = require('../../config/license/license.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/internal', auth.isAuthenticated(), controller.getInternal);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), license.isValidDate(), license.isTelephonesLimit(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+
+module.exports = router;
diff --git a/server/api/telephone/telephone.controller.js b/server/api/telephone/telephone.controller.js
index de79331..75bb5f7 100644
--- a/server/api/telephone/telephone.controller.js
+++ b/server/api/telephone/telephone.controller.js
@@ -1 +1,174 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/telephones              ->  index
+ * POST    /api/telephones              ->  create
+ * GET     /api/telephones/:id          ->  show
+ * PUT     /api/telephones/:id          ->  update
+ * DELETE  /api/telephones/:id          ->  destroy
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var Telephone = require('../../models').User;
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Gets a list of Telephones
+exports.index = function(req, res) {
+  return Telephone
+    .scope('telephone')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+}
+
+// Gets a single Telephone from the DB
+exports.show = function(req, res) {
+  Telephone.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Get list of settings
+exports.getInternal = function(req, res) {
+
+  var _tinternal;
+
+  return Telephone
+    .max('internal')
+    .then(function(tinternal) {
+      _tinternal = tinternal ? tinternal : 0;
+
+      res.status(200).send({
+        internal: _tinternal + 1
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Validate the existence of an internal number
+exports.getInternalValidation = function(req, res) {
+  console.log('req.params', req.params);
+  return Telephone
+    .findAll({
+      where: {
+        internal: req.body.internal
+      }
+    })
+    .then(function(internals) {
+      if (!internals) {
+        return res.sendStatus(404);
+      }
+      return res.send(internals);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new Telephone in the DB
+exports.create = function(req, res) {
+  req.body.role = 'telephone';
+  return Telephone
+    .create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(handleError(res));
+}
+
+// Updates an existing Telephone in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return Telephone.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(handleError(res));
+}
+
+// Deletes a Telephone from the DB
+exports.destroy = function(req, res) {
+  return Telephone.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return Telephone
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
diff --git a/server/api/telephone/telephone.socket.js b/server/api/telephone/telephone.socket.js
index 12166d9..11f6d7a 100644
--- a/server/api/telephone/telephone.socket.js
+++ b/server/api/telephone/telephone.socket.js
@@ -1 +1,24 @@
-var _0x7359=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x72\x65\x67\x69\x73\x74\x65\x72","\x74\x65\x6C\x65\x70\x68\x6F\x6E\x65\x3A\x73\x61\x76\x65","\x65\x6D\x69\x74","\x74\x65\x6C\x65\x70\x68\x6F\x6E\x65\x3A\x72\x65\x6D\x6F\x76\x65"];_0x7359[0];exports[_0x7359[1]]=function(_0x24dax1){};function onSave(_0x24dax1,_0x24dax3,_0x24dax4){_0x24dax1[_0x7359[3]](_0x7359[2],_0x24dax3)}function onRemove(_0x24dax1,_0x24dax3,_0x24dax4){_0x24dax1[_0x7359[3]](_0x7359[4],_0x24dax3)}
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+// var Telephone = require('../../models').Telephone;
+
+exports.register = function (socket) {
+  // Telephone.afterCreate(function(doc) {
+  //   onSave(socket, doc);
+  // });
+  // Telephone.afterDestroy(function(doc) {
+  //   onRemove(socket, doc);
+  // });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('telephone:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('telephone:remove', doc);
+}
diff --git a/server/api/trigger/index.js b/server/api/trigger/index.js
index c2535be..e2c0971 100644
--- a/server/api/trigger/index.js
+++ b/server/api/trigger/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./trigger.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+
+module.exports = router;
diff --git a/server/api/trigger/trigger.controller.js b/server/api/trigger/trigger.controller.js
index 13c4f92..97bbaf4 100644
--- a/server/api/trigger/trigger.controller.js
+++ b/server/api/trigger/trigger.controller.js
@@ -1 +1,199 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var Util = require('../../config/util');
+var sequelize = require('../../models').sequelize;
+
+var Trigger = require('../../models').Trigger;
+var ToolsCondition = require('../../models').ToolsCondition;
+var ToolsAction = require('../../models').ToolsAction;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return Trigger
+    .scope('all')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single Trigger
+exports.show = function(req, res) {
+  Trigger
+    .scope('all')
+    .findById(req.params.id)
+    .then(function(Trigger) {
+      if (!Trigger) {
+        return res.sendStatus(404);
+      }
+      return res.send(Trigger);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new Trigger in the DB.
+exports.create = function(req, res) {
+  Trigger
+    .create(req.body, {
+      include: [{
+        model: ToolsCondition,
+        as: 'All'
+      }, {
+        model: ToolsCondition,
+        as: 'Any'
+      }, {
+        model: ToolsAction,
+        as: 'Actions'
+      }]
+    })
+    .then(function() {
+      return res.sendStatus(201);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing Trigger in the DB.
+exports.update = function(req, res, next) {
+  var _trigger;
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return sequelize.transaction(function(t) {
+      return Trigger
+        .findById(req.params.id)
+        .then(function(trigger) {
+          if (!trigger) {
+            return res.sendStatus(404);
+          }
+          var updated = _.merge(trigger, req.body);
+          return updated
+            .save({
+              transaction: t
+            });
+        })
+        .then(function(trigger) {
+          _trigger = trigger;
+          if (req.body.Any) {
+            return ToolsCondition
+              .destroy({
+                where: {
+                  TriggerAnyId: req.params.id
+                },
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.Any && Array.isArray(req.body.Any) && req.body.Any.length) {
+            _.forEach(req.body.Any, function(elem) {
+              elem.TriggerAnyId = req.params.id;
+            });
+            return ToolsCondition
+              .bulkCreate(req.body.Any, {
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.All) {
+            return ToolsCondition
+              .destroy({
+                where: {
+                  TriggerAllId: req.params.id
+                },
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.All && Array.isArray(req.body.All) && req.body.All.length) {
+            _.forEach(req.body.All, function(elem) {
+              elem.TriggerAllId = req.params.id;
+            });
+            return ToolsCondition
+              .bulkCreate(req.body.All, {
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.Actions) {
+            return ToolsAction
+              .destroy({
+                where: {
+                  TriggerId: req.params.id
+                },
+                transaction: t
+              });
+          }
+        })
+        .then(function() {
+          if (req.body.Actions && Array.isArray(req.body.Actions) && req.body.Actions.length) {
+            _.forEach(req.body.Actions, function(elem) {
+              elem.TriggerId = req.params.id;
+            });
+            return ToolsAction
+              .bulkCreate(req.body.Actions, {
+                transaction: t
+              });
+          }
+        });
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes a Trigger from the DB.
+exports.destroy = function(req, res) {
+  Trigger
+    .findById(req.params.id)
+    .then(function(Trigger) {
+      if (!Trigger) {
+        return res.sendStatus(404);
+      }
+      Trigger.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  Trigger
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/trigger/trigger.socket.js b/server/api/trigger/trigger.socket.js
index afd9a75..7fe5fa2 100644
--- a/server/api/trigger/trigger.socket.js
+++ b/server/api/trigger/trigger.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Trigger = require('../../models').Trigger;
+
+exports.register = function(socket) {
+  Trigger.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Trigger.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('trigger:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('trigger:remove', doc);
+}
diff --git a/server/api/trigger/trigger.spec.js b/server/api/trigger/trigger.spec.js
index 372a658..c88586d 100644
--- a/server/api/trigger/trigger.spec.js
+++ b/server/api/trigger/trigger.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/triggers', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/triggers')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/trunk/index.js b/server/api/trunk/index.js
index ae95f2f..ea72fdd 100644
--- a/server/api/trunk/index.js
+++ b/server/api/trunk/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./trunk.controller');
+var config = require('../../config/environment');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.trunkValidation);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/trunk/trunk.controller.js b/server/api/trunk/trunk.controller.js
index e1391e2..eff129a 100644
--- a/server/api/trunk/trunk.controller.js
+++ b/server/api/trunk/trunk.controller.js
@@ -1 +1,160 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Trunk = require('../../models').Trunk;
+var VoiceExtension = require('../../models').VoiceExtension;
+var sequelize = require('../../models').sequelize;
+var util = require('util');
+var Util = require('../../config/util');
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return Trunk
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single trunk
+exports.show = function(req, res) {
+  return Trunk
+    .findById(req.params.id)
+    .then(function(trunk) {
+      if (!trunk) {
+        return res.sendStatus(404);
+      }
+      return res.send(trunk);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Validate the existence of a trunk
+exports.trunkValidation = function(req, res) {
+  return Trunk
+    .findAll({
+      where: {
+        name: req.body.name
+      }
+    })
+    .then(function(trunks) {
+      if (!trunks) {
+        return res.sendStatus(404);
+      }
+      return res.send(trunks);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new trunk in the DB.
+exports.create = function(req, res) {
+  return Trunk
+    .create(req.body)
+    .then(function(trunk) {
+      return res.status(201).send(trunk);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return Trunk
+    .findById(req.params.id)
+    .then(function(trunk) {
+      if (!trunk) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(trunk, req.body);
+      return updated
+        .save();
+    })
+    .then(function(trunk) {
+      return res.status(200).send(trunk);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes a trunk from the DB.
+exports.destroy = function(req, res) {
+  return Trunk
+    .findById(req.params.id)
+    .then(function(trunk) {
+      if (!trunk) {
+        return res.sendStatus(404);
+      }
+      return trunk
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes multiple trunk from the DB.
+exports.bulkDestroy = function(req, res) {
+  var associatedTrunks = false;
+
+  Trunk
+    .findAll({
+      where: {
+        id: req.query.id
+      },
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(trunks) {
+      if (!trunks) {
+        return res.sendStatus(404);
+      }
+      trunks.forEach(function(element) {
+        if (element.VoiceExtensions.length > 0) {
+          associatedTrunks = true;
+        }
+      });
+      if (associatedTrunks) {
+        return res.status(500).send({
+          message: 'MESSAGE_TRUNK_OUTBOUND_ASSOCIATED'
+        });
+      }
+      Trunk
+        .destroy({
+          where: {
+            id: req.query.id
+          },
+          individualHooks: true
+        })
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/trunk/trunk.socket.js b/server/api/trunk/trunk.socket.js
index 044d5f6..bf99ee7 100644
--- a/server/api/trunk/trunk.socket.js
+++ b/server/api/trunk/trunk.socket.js
@@ -1 +1,30 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Trunk = require('../../models').Trunk;
+
+exports.register = function(socket) {
+  Trunk.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Trunk.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+
+  Trunk.beforeUpdate(function(doc) {
+    if (doc.changed('name')) {
+      throw new Error("You can't modify a trunk name");
+    }
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('trunk:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('trunk:remove', doc);
+}
diff --git a/server/api/update/index.js b/server/api/update/index.js
index b06718f..44410d3 100644
--- a/server/api/update/index.js
+++ b/server/api/update/index.js
@@ -1 +1,12 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./update.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), auth.hasRole('admin'), controller.index);
+router.get('/pull', auth.isAuthenticated(), auth.hasRole('admin'), controller.pull);
+
+module.exports = router;
diff --git a/server/api/update/update.controller.js b/server/api/update/update.controller.js
index 14554a0..f882d67 100644
--- a/server/api/update/update.controller.js
+++ b/server/api/update/update.controller.js
@@ -1 +1,50 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var path = require('path');
+var moment = require('moment');
+var Promise = require('bluebird');
+var exec = Promise.promisify(require('child_process').exec);
+
+var config = require('../../config/environment');
+
+var Git = require('simple-git')(path.join(config.root));
+
+// Get list of updates
+exports.index = function(req, res) {
+  return Git
+    .log(function(err, logs) {
+      if (err) {
+        return handleError(res, err);
+      }
+
+      var rows = [];
+      for (var i = 0; i < 10 && i < logs.all.length; i++) {
+        rows.push({
+          version: logs.all[i].message.split('|')[1] || 'unknown',
+          message: logs.all[i].message.split('|')[0],
+          createdAt: moment(logs.all[i].date).format("YYYY-MM-DD HH:mm:ss")
+        });
+      }
+
+      return res.status(200).send({
+        rows: rows,
+        count: rows.length
+      });
+    });
+};
+
+// Get a single update
+exports.pull = function(req, res) {
+  return exec('git pull')
+    .then(function() {
+      return res.sendStatus(201).send();
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user/index.js b/server/api/user/index.js
index 0c2ab0e..3d8b8b6 100644
--- a/server/api/user/index.js
+++ b/server/api/user/index.js
@@ -1 +1,26 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./user.controller');
+var config = require('../../config/environment');
+var auth = require('../../auth/auth.service');
+var license = require('../../config/license/license.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/me', auth.isAuthenticated(), controller.me);
+router.get('/all', auth.isAuthenticated(), controller.getUsers);
+router.get('/avatar/:filename', controller.getAvatar);
+router.get('/:id/avatar', controller.getAvatarById);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/validate/:field', auth.isAuthenticated(), controller.userValidation);
+router.put('/password', auth.isAuthenticated(), controller.changePassword);
+router.put('/:id/password', auth.isAuthenticated(), auth.hasRole('admin'), controller.resetPassword);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.post('/', auth.isAuthenticated(), license.isValidDate(), license.isUsersLimit(), controller.create);
+router.post('/:id/avatar', auth.isAuthenticated(), controller.changeAvatar);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/user/user.controller.js b/server/api/user/user.controller.js
index 15732d0..2e46525 100644
--- a/server/api/user/user.controller.js
+++ b/server/api/user/user.controller.js
@@ -1 +1,369 @@
\ No newline at end of file
+'use strict';
+
+var User = require('../../models').User;
+var Module = require('../../models').Module;
+var sequelize = require('../../models').sequelize;
+
+var config = require('../../config/environment');
+var Util = require('../../config/util');
+
+var passport = require('passport');
+var moment = require('moment');
+var jwt = require('jsonwebtoken');
+var uploadFile = require('upload-file');
+var path = require('path');
+var fs = require('fs');
+var _ = require('lodash');
+var util = require('util');
+var sr = require('simple-random');
+
+/**
+ * Get list of admin/user
+ */
+exports.index = function(req, res, next) {
+  return User
+    .scope('user')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Get list of admin/user/user
+ */
+exports.getUsers = function(req, res, next) {
+  return User
+    .findAll({
+      where: {
+        role: {
+          $in: ['admin', 'user', 'user']
+        }
+      }
+    })
+    .then(function(users) {
+      return res.status(200).send(users);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Creates a new user
+ */
+exports.create = function(req, res, next) {
+  return User
+    .create(req.body)
+    .then(function(user) {
+      return res.status(201).send(user);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      return Util.errorManager.handleUniqueConstraintError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Get a single user
+ */
+exports.show = function(req, res, next) {
+  return User
+    .findById(req.params.id)
+    .then(function(user) {
+      if (!user) {
+        return res.sendStatus(404);
+      }
+
+      return res.status(200).send(user);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Deletes a user
+ */
+exports.destroy = function(req, res, next) {
+  var userId = req.params.id;
+  var _user;
+
+  return User
+    .findById(userId)
+    .then(function(user) {
+      _user = user;
+      if (!user) {
+        return res.sendStatus(404);
+      }
+      return user
+        .destroy();
+    })
+    .then(function(user) {
+      return res.status(200).send(user);
+    })
+    .catch(sequelize.ForeignKeyConstraintError, function(err) {
+      return Util.errorManager.handleForeignKeyConstraintError(res, 'user', _user.name, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Change a user password
+ */
+exports.changePassword = function(req, res, next) {
+  var userId = req.user.id;
+  var oldPass = String(req.body.oldPassword);
+  var newPass = String(req.body.newPassword);
+
+  return User
+    .findById(userId)
+    .then(function(user) {
+      if (user.authenticate(oldPass)) {
+        user.password = newPass;
+        user.save()
+          .then(function() {
+            return res.sendStatus(200);
+          })
+          .catch(function(err) {
+            return handleError(res, err);
+          });
+      } else {
+        return res.sendStatus(403);
+      }
+    });
+};
+
+/**
+ * Change a user password by admin
+ */
+exports.resetPassword = function(req, res, next) {
+  var newPass = String(req.body.newPassword);
+  return User
+    .findById(req.params.id)
+    .then(function(user) {
+      user.password = newPass;
+      return user
+        .save();
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Change the avatar of the user
+ */
+exports.changeAvatar = function(req, res, next) {
+
+  var upload = new uploadFile({
+    dest: path.join(config.root, 'server', 'files', 'images'),
+    maxNumberOfFiles: 1,
+    minNumberOfFiles: 0,
+    acceptFileTypes: /(\.|\/)(jpeg|png)$/i,
+    rename: function(name, file) {
+      return sr() + path.extname(file.filename);
+    },
+    messages: {
+      maxNumberOfFiles: "Max number of files upload exceeded",
+      acceptFileTypes: "Only jpeg and png format accepted",
+      invalidRequest: "Invalid request"
+    }
+  });
+
+  upload.on('end', function(fields, files) {
+    if (!files.file.filename) {
+      return res.status(500).send(upload);
+    }
+
+    return User
+      .findById(req.params.id)
+      .then(function(user) {
+        if (!user) {
+          return res.sendStatus(404);
+        }
+
+        return user
+          .update({
+            userpic: files.file.filename
+          });
+      })
+      .then(function(user) {
+        return res.status(200).send(user);
+      })
+      .catch(function(err) {
+        return handleError(res, err);
+      });
+  });
+
+  upload.on('error', function(err) {
+    console.log('********** Upload error :', err)
+    return handleError(res, err);
+  });
+
+  upload.parse(req);
+};
+
+
+/**
+ * Get my info
+ */
+exports.me = function(req, res, next) {
+  var userId = req.user.id;
+
+  return User
+    .findOne({
+      where: {
+        id: userId
+      },
+      attributes: ['id', 'fullname', 'name', 'email', 'role', 'userpic'],
+      include: [Module]
+    })
+    .then(function(user) {
+      if (!user) {
+        return res.sendStatus(401);
+      }
+      return res.status(200).send(user);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Update my user
+ */
+exports.update = function(req, res, next) {
+  var userId = req.user.id;
+  delete req.body.password;
+
+  return User
+    .findById(req.params.id)
+    .then(function(user) {
+      if (!user) {
+        return res.sendStatus(500);
+      }
+      return user
+        .update(req.body);
+    })
+    .then(function(user) {
+      return res.status(200).send(user);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+/**
+ * Get my avatar
+ */
+exports.getAvatar = function(req, res, next) {
+  var imgPath = path.join(config.root, 'server', 'files', 'images', req.params.filename);
+
+  fs.exists(imgPath, function(exists) {
+    if (!exists) {
+      imgPath = path.join(config.root, 'server', 'files', 'images', 'userpic.png');
+    }
+
+    return res.sendFile(imgPath);
+  });
+};
+
+/**
+ * Get my avatar by id
+ */
+exports.getAvatarById = function(req, res, next) {
+  if (req.params.id === 'visitor') {
+    var imgPath = path.join(config.root, 'server', 'files', 'images', 'customer.png');
+
+    fs.exists(imgPath, function(exists) {
+      return res.sendFile(imgPath);
+    });
+
+  } else {
+    return User
+      .findById(req.params.id)
+      .then(function(user) {
+        if (user) {
+          var imgPath = path.join(config.root, 'server', 'files', 'images', user.userpic ? user.userpic : 'userpic.png');
+
+          fs.exists(imgPath, function(exists) {
+            if (!exists) {
+              imgPath = path.join(config.root, 'server', 'files', 'images', 'userpic.png');
+            }
+
+            return res.sendFile(imgPath);
+          });
+        } else {
+          return res.sendStatus(500);
+        }
+      })
+      .catch(function(err) {
+        return handleError(res, err);
+      });
+  }
+};
+
+// Deletes a user from the DB.
+exports.bulkDestroy = function(req, res) {
+  return User
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.userValidation = function(req, res) {
+  var where = {};
+  where[req.params.field] = req.body.value;
+
+  return User
+    .findAndCountAll({
+      where: where
+    })
+    .then(function(result) {
+      if (result.count) {
+        return res.status(200).send({
+          isValid: false,
+          value: req.body.value
+        });
+      }
+      return res.status(200).send({
+        isValid: true,
+        value: req.body.value
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+
+};
+
+/**
+ * Authentication callback
+ */
+exports.authCallback = function(req, res, next) {
+  res.redirect('/');
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user/user.socket.js b/server/api/user/user.socket.js
index c33f2dd..e1071aa 100644
--- a/server/api/user/user.socket.js
+++ b/server/api/user/user.socket.js
@@ -1 +1,31 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var user = require('../../models').User;
+
+exports.register = function(socket) {
+  user.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  user.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  user.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  if (doc.role === 'admin') {
+    socket.emit('user:save', doc);
+  }
+}
+
+function onRemove(socket, doc, cb) {
+  if (doc.role === 'admin') {
+    socket.emit('user:remove', doc);
+  }
+}
diff --git a/server/api/user_has_chat_queue/index.js b/server/api/user_has_chat_queue/index.js
index ec120b7..bce94e7 100644
--- a/server/api/user_has_chat_queue/index.js
+++ b/server/api/user_has_chat_queue/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./user_has_chat_queue.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/user_has_chat_queue/user_has_chat_queue.controller.js b/server/api/user_has_chat_queue/user_has_chat_queue.controller.js
index f78c05a..b399892 100644
--- a/server/api/user_has_chat_queue/user_has_chat_queue.controller.js
+++ b/server/api/user_has_chat_queue/user_has_chat_queue.controller.js
@@ -1 +1,103 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var UserHasChatQueue = require('../../models').UserHasChatQueue;
+
+// Get list of userHasChatQueues
+exports.index = function(req, res) {
+  UserHasChatQueue
+    .findAll(req.query ? {
+      where: req.query
+    } : {})
+    .then(function(userHasChatQueues) {
+      return res.status(200).send(userHasChatQueues);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single userHasChatQueue
+exports.show = function(req, res) {
+  UserHasChatQueue
+    .findById(req.params.id)
+    .then(function(userHasChatQueue) {
+      if (!userHasChatQueue) {
+        return res.sendStatus(404);
+      }
+      return res.send(userHasChatQueue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new userHasChatQueue in the DB.
+exports.create = function(req, res) {
+  UserHasChatQueue
+    .create(req.body)
+    .then(function(userHasChatQueue) {
+      return res.status(201).send(userHasChatQueue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing userHasChatQueue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  UserHasChatQueue
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(userHasChatQueue) {
+      if (!userHasChatQueue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(userHasChatQueue, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(userHasChatQueue);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a userHasChatQueue from the DB.
+exports.destroy = function(req, res) {
+  UserHasChatQueue
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(userHasChatQueue) {
+      if (!userHasChatQueue) {
+        return res.sendStatus(404);
+      }
+      userHasChatQueue.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user_has_chat_queue/user_has_chat_queue.socket.js b/server/api/user_has_chat_queue/user_has_chat_queue.socket.js
index e48c4a7..882e78a 100644
--- a/server/api/user_has_chat_queue/user_has_chat_queue.socket.js
+++ b/server/api/user_has_chat_queue/user_has_chat_queue.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var UserHasChatQueue = require('../../models').UserHasChatQueue;
+
+exports.register = function(socket) {
+  UserHasChatQueue.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasChatQueue.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('user_has_chat_queue:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('user_has_chat_queue:remove', doc);
+}
diff --git a/server/api/user_has_chat_queue/user_has_chat_queue.spec.js b/server/api/user_has_chat_queue/user_has_chat_queue.spec.js
index f4dc8b9..539163a 100644
--- a/server/api/user_has_chat_queue/user_has_chat_queue.spec.js
+++ b/server/api/user_has_chat_queue/user_has_chat_queue.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/chat/user_has_queues', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/chat/user_has_queues')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/user_has_chat_room/index.js b/server/api/user_has_chat_room/index.js
index e45fde8..381448d 100644
--- a/server/api/user_has_chat_room/index.js
+++ b/server/api/user_has_chat_room/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express     = require('express');
+var controller  = require('./user_has_chat_room.controller');
+var auth        = require('../../auth/auth.service');
+
+var router = express.Router();
+
+// router.get('/', controller.index);
+// router.get('/:id', controller.show);
+// router.post('/', controller.create);
+router.put('/:userId/:chatRoomId', controller.update);
+// router.patch('/:id', controller.update);
+// router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/user_has_chat_room/user_has_chat_room.controller.js b/server/api/user_has_chat_room/user_has_chat_room.controller.js
index c1e9538..d747380 100644
--- a/server/api/user_has_chat_room/user_has_chat_room.controller.js
+++ b/server/api/user_has_chat_room/user_has_chat_room.controller.js
@@ -1 +1,96 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var UserHasChatRoom = require('../../models').UserHasChatRoom;
+
+// Get list of user_has_chat_rooms
+exports.index = function(req, res) {
+  UserHasChatRoom
+  .findAll()
+  .then(function (user_has_chat_rooms) {
+    return res.status(200).send(user_has_chat_rooms);
+  })
+  .catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+// Get a single user_has_chat_room
+exports.show = function(req, res) {
+  UserHasChatRoom
+  .findById(req.params.id)
+  .then(function (user_has_chat_room) {
+    if(!user_has_chat_room) { return res.sendStatus(404); }
+    return res.send(user_has_chat_room);
+  })
+  .catch(function(err){
+    return handleError(res, err);
+  });
+};
+
+// Creates a new user_has_chat_room in the DB.
+exports.create = function(req, res) {
+
+  UserHasChatRoom
+    .create(req.body)
+    .then(function(user_has_chat_room) {
+      return res.status(201).send(user_has_chat_room);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing user_has_chat_room in the DB.
+exports.update = function(req, res) {
+
+  UserHasChatRoom
+    .findOne({
+      where: {
+        UserId: req.params.userId,
+        ChatRoomId: req.params.chatRoomId
+      },
+      limit: 1
+    })
+    .then(function (user_has_chat_room) {
+      if (!user_has_chat_room) {
+        return res.sendStatus(404);
+      }
+
+      user_has_chat_room
+        .updateAttributes(req.body)
+        .then(function () {
+          return res.status(200).send(user_has_chat_room);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a user_has_chat_room from the DB.
+exports.destroy = function(req, res) {
+
+  UserHasChatRoom
+  .findById(req.params.id)
+  .then(function (user_has_chat_room) {
+    if(!user_has_chat_room) { return res.sendStatus(404); }
+    user_has_chat_room.destroy()
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+  })
+  .catch(function(err) {
+    return handleError(res, err);
+  });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user_has_chat_room/user_has_chat_room.socket.js b/server/api/user_has_chat_room/user_has_chat_room.socket.js
index 1e3ca01..e0be09b 100644
--- a/server/api/user_has_chat_room/user_has_chat_room.socket.js
+++ b/server/api/user_has_chat_room/user_has_chat_room.socket.js
@@ -1 +1,66 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+var _ = require('lodash');
+var User = require('../../models').User;
+var ChatRoom = require('../../models').ChatRoom;
+var ChatVisitor = require('../../models').ChatVisitor;
+var ChatMessage = require('../../models').ChatMessage;
+var UserHasChatRoom = require('../../models').UserHasChatRoom;
+
+exports.register = function(socket) {
+  UserHasChatRoom.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasChatRoom.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc) {
+
+  var _chatRoom;
+
+  return ChatRoom
+    .findById(doc.ChatRoomId, {
+      include: [{
+        model: ChatVisitor,
+        attributes: ['id', 'email', 'fullname']
+      }, {
+        model: User,
+        attributes: ['id', 'email', 'name', 'fullname']
+      }, {
+        model: ChatMessage,
+        include: [{
+          model: ChatVisitor,
+          attributes: ['id', 'email', 'fullname']
+        }, {
+          model: User,
+          attributes: ['id', 'email', 'name', 'fullname']
+        }]
+      }]
+    })
+    .then(function(chatRoom) {
+      _chatRoom = chatRoom;
+      // _chatRoom.dataValues.ChatMessages = [];
+
+      var ids = _.map(_chatRoom.Users, 'id');
+
+      ids.forEach(function(id) {
+        if (socket.userId == id) {
+          socket.emit('user_has_chat_room:save', _chatRoom.dataValues)
+        }
+      });
+
+      return;
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+}
+
+function onRemove(socket, doc) {
+  socket.emit('user_has_chat_room:remove', doc);
+}
diff --git a/server/api/user_has_chat_room/user_has_chat_room.spec.js b/server/api/user_has_chat_room/user_has_chat_room.spec.js
index e8f46cb..b33382a 100644
--- a/server/api/user_has_chat_room/user_has_chat_room.spec.js
+++ b/server/api/user_has_chat_room/user_has_chat_room.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/chat/user_has_rooms', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/chat/user_has_rooms')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/user_has_fax_queue/index.js b/server/api/user_has_fax_queue/index.js
index 9dbdc82..5805e3d 100644
--- a/server/api/user_has_fax_queue/index.js
+++ b/server/api/user_has_fax_queue/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./user_has_fax_queue.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/user_has_fax_queue/user_has_fax_queue.controller.js b/server/api/user_has_fax_queue/user_has_fax_queue.controller.js
index 18e26c0..f92e570 100644
--- a/server/api/user_has_fax_queue/user_has_fax_queue.controller.js
+++ b/server/api/user_has_fax_queue/user_has_fax_queue.controller.js
@@ -1 +1,103 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var UserHasFaxQueue = require('../../models').UserHasFaxQueue;
+
+// Get list of user_has_fax_queues
+exports.index = function(req, res) {
+  UserHasFaxQueue
+    .findAll(req.query ? {
+      where: req.query
+    } : {})
+    .then(function(user_has_fax_queues) {
+      return res.status(200).send(user_has_fax_queues);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single user_has_fax_queue
+exports.show = function(req, res) {
+  UserHasFaxQueue
+    .findById(req.params.id)
+    .then(function(user_has_fax_queue) {
+      if (!user_has_fax_queue) {
+        return res.sendStatus(404);
+      }
+      return res.send(user_has_fax_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new user_has_fax_queue in the DB.
+exports.create = function(req, res) {
+  UserHasFaxQueue
+    .create(req.body)
+    .then(function(user_has_fax_queue) {
+      return res.status(201).send(user_has_fax_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing user_has_fax_queue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  UserHasFaxQueue
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(user_has_fax_queue) {
+      if (!user_has_fax_queue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(user_has_fax_queue, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(user_has_fax_queue);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a user_has_fax_queue from the DB.
+exports.destroy = function(req, res) {
+  UserHasFaxQueue
+    .find({
+      where: {
+        id: req.params.id
+      }
+    })
+    .then(function(user_has_fax_queue) {
+      if (!user_has_fax_queue) {
+        return res.sendStatus(404);
+      }
+      user_has_fax_queue.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user_has_fax_queue/user_has_fax_queue.socket.js b/server/api/user_has_fax_queue/user_has_fax_queue.socket.js
index e661c0f..4b0602a 100644
--- a/server/api/user_has_fax_queue/user_has_fax_queue.socket.js
+++ b/server/api/user_has_fax_queue/user_has_fax_queue.socket.js
@@ -1 +1,30 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var UserHasFaxQueue = require('../../models').UserHasFaxQueue;
+
+exports.register = function(socket) {
+  UserHasFaxQueue.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasFaxQueue.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+  UserHasFaxQueue.afterBulkCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasFaxQueue.afterBulkDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('user_has_fax_queue:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('user_has_fax_queue:remove', doc);
+}
diff --git a/server/api/user_has_fax_queue/user_has_fax_queue.spec.js b/server/api/user_has_fax_queue/user_has_fax_queue.spec.js
index cd09278..992672c 100644
--- a/server/api/user_has_fax_queue/user_has_fax_queue.spec.js
+++ b/server/api/user_has_fax_queue/user_has_fax_queue.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/fax/user_has_queues', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/fax/user_has_queues')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/user_has_mail_queue/index.js b/server/api/user_has_mail_queue/index.js
index f88c366..50b083e 100644
--- a/server/api/user_has_mail_queue/index.js
+++ b/server/api/user_has_mail_queue/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./user_has_mail_queue.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/user_has_mail_queue/user_has_mail_queue.controller.js b/server/api/user_has_mail_queue/user_has_mail_queue.controller.js
index 87be2c3..c15bc9d 100644
--- a/server/api/user_has_mail_queue/user_has_mail_queue.controller.js
+++ b/server/api/user_has_mail_queue/user_has_mail_queue.controller.js
@@ -1 +1,95 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var UserHasMailQueue = require('../../models').UserHasMailQueue;
+
+// Get list of user_has_mail_queues
+exports.index = function(req, res) {
+  UserHasMailQueue
+    .findAll(req.query ? {
+      where: req.query
+    } : {})
+    .then(function(user_has_mail_queues) {
+      return res.status(200).send(user_has_mail_queues);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single user_has_mail_queue
+exports.show = function(req, res) {
+  UserHasMailQueue
+    .findById(req.params.id)
+    .then(function(user_has_mail_queue) {
+      if (!user_has_mail_queue) {
+        return res.sendStatus(404);
+      }
+      return res.send(user_has_mail_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new user_has_mail_queue in the DB.
+exports.create = function(req, res) {
+  UserHasMailQueue
+    .create(req.body)
+    .then(function(user_has_mail_queue) {
+      return res.status(201).send(user_has_mail_queue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing user_has_mail_queue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  UserHasMailQueue
+    .findById(req.params.id)
+    .then(function(user_has_mail_queue) {
+      if (!user_has_mail_queue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(user_has_mail_queue, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(user_has_mail_queue);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a user_has_mail_queue from the DB.
+exports.destroy = function(req, res) {
+  UserHasMailQueue
+    .findById(req.params.id)
+    .then(function(user_has_mail_queue) {
+      if (!user_has_mail_queue) {
+        return res.sendStatus(404);
+      }
+      user_has_mail_queue.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user_has_mail_queue/user_has_mail_queue.socket.js b/server/api/user_has_mail_queue/user_has_mail_queue.socket.js
index a5b817d..bea513c 100644
--- a/server/api/user_has_mail_queue/user_has_mail_queue.socket.js
+++ b/server/api/user_has_mail_queue/user_has_mail_queue.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var UserHasMailQueue = require('../../models').UserHasMailQueue;
+
+exports.register = function(socket) {
+  UserHasMailQueue.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasMailQueue.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('user_has_mail_queue:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('user_has_mail_queue:remove', doc);
+}
diff --git a/server/api/user_has_service/index.js b/server/api/user_has_service/index.js
index 0ad82ef..1e7edf7 100644
--- a/server/api/user_has_service/index.js
+++ b/server/api/user_has_service/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./user_has_service.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/user_has_service/user_has_service.controller.js b/server/api/user_has_service/user_has_service.controller.js
index 6bed7df..377090e 100644
--- a/server/api/user_has_service/user_has_service.controller.js
+++ b/server/api/user_has_service/user_has_service.controller.js
@@ -1 +1,98 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var UserHasService = require('../../models').UserHasService;
+
+// Get list of user_has_voice_queues
+exports.index = function(req, res) {
+  UserHasService
+    .findAll(req.query ? {
+      where: req.query
+    } : {})
+    .then(function(user_has_voice_queues) {
+      return res.status(200).send(user_has_voice_queues);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single userHasService
+exports.show = function(req, res) {
+  UserHasService
+    .findById(req.params.id)
+    .then(function(userHasService) {
+      if (!userHasService) {
+        return res.sendStatus(404);
+      }
+      return res.send(userHasService);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new userHasService in the DB.
+exports.create = function(req, res, next) {
+  console.log(req.body);
+  UserHasService
+    .create(req.body)
+    .then(function(userHasService) {
+      return res.status(201).send(userHasService);
+    })
+    .catch(function(err) {
+      console.error(err);
+      return next(err);
+      // return handleError(res, err);
+    });
+};
+
+// Updates an existing userHasService in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  UserHasService
+    .findById(req.params.id)
+    .then(function(userHasService) {
+      if (!userHasService) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(userHasService, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(userHasService);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a userHasService from the DB.
+exports.destroy = function(req, res) {
+  UserHasService
+    .findById(req.params.id)
+    .then(function(userHasService) {
+      if (!userHasService) {
+        return res.sendStatus(404);
+      }
+      userHasService.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user_has_service/user_has_service.socket.js b/server/api/user_has_service/user_has_service.socket.js
index 444816d..816d536 100644
--- a/server/api/user_has_service/user_has_service.socket.js
+++ b/server/api/user_has_service/user_has_service.socket.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var UserHasService = require('../../models').UserHasService;
+
+exports.register = function(socket) {
+  UserHasService.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasService.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasService.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('user_has_service:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('user_has_service:remove', doc);
+}
diff --git a/server/api/user_has_service/user_has_service.spec.js b/server/api/user_has_service/user_has_service.spec.js
index 2ec8b65..0822ab1 100644
--- a/server/api/user_has_service/user_has_service.spec.js
+++ b/server/api/user_has_service/user_has_service.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/contactmanager/user_has_service', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/contactmanager/user_has_service')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/user_has_team/index.js b/server/api/user_has_team/index.js
index 387db96..723bdc1 100644
--- a/server/api/user_has_team/index.js
+++ b/server/api/user_has_team/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./user_has_team.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/user_has_team/user_has_team.controller.js b/server/api/user_has_team/user_has_team.controller.js
index f8c0d6f..7f0f426 100644
--- a/server/api/user_has_team/user_has_team.controller.js
+++ b/server/api/user_has_team/user_has_team.controller.js
@@ -1 +1,96 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var UserHasTeam = require('../../models').UserHasTeam;
+
+// Get list of user_has_teams
+exports.index = function(req, res) {
+  console.log(req.query);
+  UserHasTeam
+    .findAll({
+      where: req.query
+    })
+    .then(function(user_has_teams) {
+      return res.status(200).send(user_has_teams);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single user_has_team
+exports.show = function(req, res) {
+  UserHasTeam
+    .findById(req.params.id)
+    .then(function(user_has_team) {
+      if (!user_has_team) {
+        return res.sendStatus(404);
+      }
+      return res.send(user_has_team);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new user_has_team in the DB.
+exports.create = function(req, res) {
+  UserHasTeam
+    .create(req.body)
+    .then(function(user_has_team) {
+      return res.status(201).send(user_has_team);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing user_has_team in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  UserHasTeam
+    .findById(req.params.id)
+    .then(function(user_has_team) {
+      if (!user_has_team) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(user_has_team, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(user_has_team);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a user_has_team from the DB.
+exports.destroy = function(req, res) {
+  UserHasTeam
+    .findById(req.params.id)
+    .then(function(user_has_team) {
+      if (!user_has_team) {
+        return res.sendStatus(404);
+      }
+      user_has_team.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user_has_team/user_has_team.model.js b/server/api/user_has_team/user_has_team.model.js
index 583a7c7..6d5861d 100644
--- a/server/api/user_has_team/user_has_team.model.js
+++ b/server/api/user_has_team/user_has_team.model.js
@@ -1 +1,13 @@
-var _0x81a7=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x65\x78\x70\x6F\x72\x74","\x55\x73\x65\x72\x48\x61\x73\x54\x65\x61\x6D","\x64\x65\x66\x69\x6E\x65"];_0x81a7[0];module[_0x81a7[1]]=function(_0x1d3ex1,_0x1d3ex2){var _0x1d3ex3=_0x1d3ex1[_0x81a7[3]](_0x81a7[2],{name:String,info:String,active:Boolean});return _0x1d3ex3}
\ No newline at end of file
+'use strict';
+
+module.export = function(sequelize, DataTypes) {
+
+  var UserHasTeam = sequelize.define('UserHasTeam', {
+    name: String,
+    info: String,
+    active: Boolean
+  });
+
+  return UserHasTeam;
+
+};
diff --git a/server/api/user_has_team/user_has_team.socket.js b/server/api/user_has_team/user_has_team.socket.js
index cfef21d..879043c 100644
--- a/server/api/user_has_team/user_has_team.socket.js
+++ b/server/api/user_has_team/user_has_team.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var user_has_team = require('../../models').UserHasTeam;
+
+exports.register = function(socket) {
+  user_has_team.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  user_has_team.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('user_has_team:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('user_has_team:remove', doc);
+}
diff --git a/server/api/user_has_team/user_has_team.spec.js b/server/api/user_has_team/user_has_team.spec.js
index ed55981..5cc5226 100644
--- a/server/api/user_has_team/user_has_team.spec.js
+++ b/server/api/user_has_team/user_has_team.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/user_has_teams', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/user_has_teams')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/user_has_voice_queue/index.js b/server/api/user_has_voice_queue/index.js
index f96b1e2..72551d5 100644
--- a/server/api/user_has_voice_queue/index.js
+++ b/server/api/user_has_voice_queue/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./user_has_voice_queue.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/api/user_has_voice_queue/user_has_voice_queue.controller.js b/server/api/user_has_voice_queue/user_has_voice_queue.controller.js
index aac7fae..f3e5d22 100644
--- a/server/api/user_has_voice_queue/user_has_voice_queue.controller.js
+++ b/server/api/user_has_voice_queue/user_has_voice_queue.controller.js
@@ -1 +1,98 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var UserHasVoiceQueue = require('../../models').UserHasVoiceQueue;
+
+// Get list of user_has_voice_queues
+exports.index = function(req, res) {
+  UserHasVoiceQueue
+    .findAll(req.query ? {
+      where: req.query
+    } : {})
+    .then(function(user_has_voice_queues) {
+      return res.status(200).send(user_has_voice_queues);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single userHasVoiceQueue
+exports.show = function(req, res) {
+  UserHasVoiceQueue
+    .findById(req.params.id)
+    .then(function(userHasVoiceQueue) {
+      if (!userHasVoiceQueue) {
+        return res.sendStatus(404);
+      }
+      return res.send(userHasVoiceQueue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new userHasVoiceQueue in the DB.
+exports.create = function(req, res, next) {
+  console.log(req.body);
+  UserHasVoiceQueue
+    .create(req.body)
+    .then(function(userHasVoiceQueue) {
+      return res.status(201).send(userHasVoiceQueue);
+    })
+    .catch(function(err) {
+      console.error(err);
+      return next(err);
+      // return handleError(res, err);
+    });
+};
+
+// Updates an existing userHasVoiceQueue in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  UserHasVoiceQueue
+    .findById(req.params.id)
+    .then(function(userHasVoiceQueue) {
+      if (!userHasVoiceQueue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(userHasVoiceQueue, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(userHasVoiceQueue);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a userHasVoiceQueue from the DB.
+exports.destroy = function(req, res) {
+  UserHasVoiceQueue
+    .findById(req.params.id)
+    .then(function(userHasVoiceQueue) {
+      if (!userHasVoiceQueue) {
+        return res.sendStatus(404);
+      }
+      userHasVoiceQueue.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/user_has_voice_queue/user_has_voice_queue.socket.js b/server/api/user_has_voice_queue/user_has_voice_queue.socket.js
index 65a79d9..1ed4063 100644
--- a/server/api/user_has_voice_queue/user_has_voice_queue.socket.js
+++ b/server/api/user_has_voice_queue/user_has_voice_queue.socket.js
@@ -1 +1,42 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var UserHasVoiceQueue = require('../../models').UserHasVoiceQueue;
+
+exports.register = function(socket) {
+  UserHasVoiceQueue.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasVoiceQueue.afterUpdate(function(doc) {
+    onSave(socket, doc);
+  });
+  UserHasVoiceQueue.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('user_has_voice_queue:save', {
+    logged: doc.logged,
+    loggedAt: doc.loggedAt,
+    queue: doc.queue,
+    membername: doc.membername,
+    interface: doc.interface,
+    callstaken: doc.callstaken,
+    lastcall: doc.lastcall,
+    status: doc.status,
+    statusdesc: doc.statusdesc,
+    statusAt: doc.statusAt,
+    paused: doc.paused,
+    pausedAt: doc.pausedAt,
+    reason: doc.reason,
+    UserId: doc.UserId
+  });
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('user_has_voice_queue:remove', doc);
+}
diff --git a/server/api/user_has_voice_queue/user_has_voice_queue.spec.js b/server/api/user_has_voice_queue/user_has_voice_queue.spec.js
index d1d4718..0ac501f 100644
--- a/server/api/user_has_voice_queue/user_has_voice_queue.spec.js
+++ b/server/api/user_has_voice_queue/user_has_voice_queue.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/voice/user_has_queues', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/voice/user_has_queues')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/api/variable/index.js b/server/api/variable/index.js
index 559f1a6..4c9decb 100644
--- a/server/api/variable/index.js
+++ b/server/api/variable/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./variable.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/variable/variable.controller.js b/server/api/variable/variable.controller.js
index eb123c5..2378a95 100644
--- a/server/api/variable/variable.controller.js
+++ b/server/api/variable/variable.controller.js
@@ -1 +1,117 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Variable = require('../../models').Variable;
+var Util = require('../../config/util');
+
+// Get list of variables
+exports.index = function(req, res, next) {
+  return Variable
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single variable
+exports.show = function(req, res) {
+  return Variable
+    .findById(req.params.id)
+    .then(function(variable) {
+      if (!variable) {
+        return res.sendStatus(404);
+      }
+      return res.send(variable);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new variable in the DB.
+exports.create = function(req, res) {
+  return Variable
+    .create(req.body)
+    .then(function(variable) {
+      return res.status(201).send(variable);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing variable in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return Variable
+    .findById(req.params.id)
+    .then(function(variable) {
+      if (!variable) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(variable, req.body);
+      return updated
+        .save();
+    })
+    .then(function(variable) {
+      return res.status(200).send(variable);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a variable from the DB.
+exports.destroy = function(req, res) {
+  return Variable
+    .scope('fields')
+    .findById(req.params.id)
+    .then(function(variable) {
+      if (!variable) {
+        return res.sendStatus(404);
+      }
+      if (variableHasAssociation(variable)) {
+        var err = new Error();
+        err.message = 'Variable is associated with an integration\'s configuration';
+        throw err;
+      }
+      return variable.destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.bulkDestroy = function(req, res) {
+  return Variable
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
+
+function variableHasAssociation(variable) {
+  return ((variable.ZendeskFields && variable.ZendeskFields.length > 0) || (variable.DeskFields && variable.DeskFields.length > 0) || (variable.SalesforceFields && variable.SalesforceFields.length >
+    0) || (variable.FreshdeskFields && variable.FreshdeskFields.length > 0) || (variable.SugarcrmFields && variable.SugarcrmFields.length > 0));
+}
diff --git a/server/api/variable/variable.socket.js b/server/api/variable/variable.socket.js
index 34e2ba8..739f11c 100644
--- a/server/api/variable/variable.socket.js
+++ b/server/api/variable/variable.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var Variable = require('../../models').Variable;
+
+exports.register = function(socket) {
+  Variable.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  Variable.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('variable:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('variable:remove', doc);
+}
diff --git a/server/api/voice_context/index.js b/server/api/voice_context/index.js
index f6e74b8..1b436c8 100644
--- a/server/api/voice_context/index.js
+++ b/server/api/voice_context/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./voice_context.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.contextValidation);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+module.exports = router;
diff --git a/server/api/voice_context/voice_context.controller.js b/server/api/voice_context/voice_context.controller.js
index e8688e1..c6ec224 100644
--- a/server/api/voice_context/voice_context.controller.js
+++ b/server/api/voice_context/voice_context.controller.js
@@ -1 +1,227 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+
+var Util = require('../../config/util');
+var sequelize = require('../../models').sequelize;
+
+var VoiceContext = require('../../models').VoiceContext;
+var VoiceExtension = require('../../models').VoiceExtension;
+
+// Get list of agents
+exports.index = function(req, res, next) {
+  return VoiceContext
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single voiceContext
+exports.show = function(req, res) {
+  return VoiceContext
+    .findById(req.params.id)
+    .then(function(voiceContext) {
+      if (!voiceContext) {
+        return res.sendStatus(404);
+      }
+      return res.send(voiceContext);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Validate the existence of a context
+exports.contextValidation = function(req, res) {
+  return VoiceContext
+    .findAll({
+      where: {
+        name: req.body.name
+      }
+    })
+    .then(function(voiceContexts) {
+      if (!voiceContexts) {
+        return res.sendStatus(404);
+      }
+      return res.send(voiceContexts);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new voiceContext in the DB.
+exports.create = function(req, res) {
+  return VoiceContext
+    .create(req.body)
+    .then(function(voiceContext) {
+      return res.status(201).send(voiceContext);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      err.message = 'The context name already exists';
+      err.translatedMessage = 'MESSAGE_EXIST_CONTEXT'
+      return handleError(res, err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+
+};
+
+// Updates an existing voiceContext in the DB.
+exports.update = function(req, res) {
+  return VoiceContext
+    .findAll({
+      where: {
+        name: req.body.name,
+        id: {
+          $ne: req.body.id
+        }
+      }
+    })
+    .then(function(existingContexts) {
+      if (!existingContexts) {
+        return res.sendStatus(404);
+      }
+      if (existingContexts.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_CONTEXT'
+        });
+      }
+      if (req.body.id) {
+        delete req.body.id;
+      }
+      return VoiceContext
+        .findById(req.params.id);
+    })
+    .then(function(voiceContext) {
+      if (!voiceContext) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(voiceContext, req.body);
+      return updated
+        .save();
+    })
+    .then(function(voiceContext) {
+      return res.status(200).send(voiceContext);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+
+};
+
+// Deletes a voiceContext from the DB.
+exports.destroy = function(req, res) {
+  var _voiceContext;
+
+  return VoiceContext
+    .findById(req.params.id)
+    .then(function(voiceContext) {
+      _voiceContext = voiceContext;
+      if (!_voiceContext) {
+        return res.sendStatus(404);
+      }
+      if (_voiceContext.defaultEntry) {
+        return res.status(500).send({
+          message: 'You can\'t delete a default entry!',
+          translatedMessage: 'MESSAGE_DEFAULT_ENTRY'
+        });
+      }
+      return VoiceExtension
+        .findAll({
+          where: {
+            context: _voiceContext.name,
+            VoiceContextId: null
+          }
+        });
+    })
+    .then(function(contextExtensions) {
+      console.log(contextExtensions);
+      if (contextExtensions.length > 0) {
+        return res.status(500).send({
+          message: 'The context is associated to a route, delete or change it first',
+          translatedMessage: 'MESSAGE_CONTEXT_ROUTE_ASSOCIATED'
+        });
+      }
+      return _voiceContext
+        .destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      console.log('err', err);
+      return handleError(res, err);
+    });
+
+};
+
+// Deletes a context from the DB.
+exports.bulkDestroy = function(req, res) {
+
+  var _voiceContexts;
+
+  return VoiceContext
+    .findAll({
+      where: {
+        id: req.query.ids
+      }
+    })
+    .then(function(voiceContexts) {
+      _voiceContexts = voiceContexts;
+      var isDefault = _.find(_voiceContexts, {
+        defaultEntry: true
+      });
+      if (isDefault) {
+        return res.status(500).send({
+          message: 'You can\'t delete a default entry!',
+          translatedMessage: 'MESSAGE_DEFAULT_ENTRY'
+        });
+      }
+      if (!_voiceContexts) {
+        return res.sendStatus(404);
+      }
+      var destroyContextsNames = _.map(_.map(_voiceContexts, 'dataValues'), 'name');
+
+      return VoiceExtension
+        .findAll({
+          where: {
+            context: destroyContextsNames,
+            VoiceContextId: null
+          }
+        });
+    })
+    .then(function(contextExtensions) {
+      if (contextExtensions.length > _voiceContexts.length) {
+        return res.status(500).send({
+          message: 'The context is associated to a route, delete or change it first',
+          translatedMessage: 'MESSAGE_CONTEXT_ROUTE_ASSOCIATED'
+        });
+      }
+
+      return VoiceContext
+        .destroy({
+          where: {
+            id: req.query.ids
+          },
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/voice_context/voice_context.socket.js b/server/api/voice_context/voice_context.socket.js
index 7141070..c3841ec 100644
--- a/server/api/voice_context/voice_context.socket.js
+++ b/server/api/voice_context/voice_context.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var VoiceContext = require('../../models').VoiceContext;
+
+exports.register = function(socket) {
+  VoiceContext.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  VoiceContext.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('voice_context:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('voice_context:remove', doc);
+}
diff --git a/server/api/voice_context/voice_context.spec.js b/server/api/voice_context/voice_context.spec.js
index 36ee238..1a60777 100644
--- a/server/api/voice_context/voice_context.spec.js
+++ b/server/api/voice_context/voice_context.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/voice/contexts', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/voice/contexts')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/voice_extension/index.js b/server/api/voice_extension/index.js
index 806fe4f..b0215fc 100644
--- a/server/api/voice_extension/index.js
+++ b/server/api/voice_extension/index.js
@@ -1 +1,16 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./voice_extension.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy); //check it for apps
+router.delete('/:id', auth.isAuthenticated(), controller.destroy); //check it for apps
+
+module.exports = router;
diff --git a/server/api/voice_extension/voice_extension.controller.js b/server/api/voice_extension/voice_extension.controller.js
index 7b05303..6dc7233 100644
--- a/server/api/voice_extension/voice_extension.controller.js
+++ b/server/api/voice_extension/voice_extension.controller.js
@@ -1 +1,146 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var VoiceExtension = require('../../models').VoiceExtension;
+var Interval = require('../../models').Interval;
+var sequelize = require('../../models').sequelize;
+var Sequelize = require('../../models').Sequelize;
+var util = require('util');
+var async = require('async');
+var veUtil = require('../../utils/voice_extension');
+var Util = require('../../config/util');
+
+var Applications = veUtil.Applications;
+
+// Get list of extensions
+exports.index = function(req, res, next) {
+  return VoiceExtension
+    .scope('routes', 'application')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Get a single voice_extension or application
+exports.show = function(req, res) {
+  VoiceExtension
+    .scope('application')
+    .findOne({
+      where: {
+        id: req.params.id,
+        VoiceExtensionId: null
+      }
+    })
+    .then(function(voice_extension) {
+      if (!voice_extension) {
+        return res.sendStatus(404);
+      }
+      return res.send(voice_extension);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.create = function(req, res) {
+  var _route;
+  return sequelize.transaction(function(t) {
+      return VoiceExtension
+        .create(req.body, {
+          transaction: t
+        })
+        .then(function(route) {
+          _route = route;
+          if (req.body.Applications && Array.isArray(req.body.Applications) && req.body.Applications.length) {
+            return route;
+          }
+        })
+        .then(veUtil.createApps(req.body.Applications, t, res));
+    })
+    .then(function() {
+      return res.status(201).send(_route);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      err.message = 'Extension/context combination already exist';
+      err.translatedMessage = 'MESSAGE_EXIST_ROUTE'
+      return handleError(res, err);
+    })
+    .catch(sequelize.ValidationError, function(err) {
+      err.message = 'Extension/context combination already exist';
+      err.translatedMessage = 'MESSAGE_EXIST_ROUTE'
+      return handleError(res, err);
+    })
+    .catch(function(err) {
+      console.log('err', err);
+      return handleError(res, err);
+    });
+}
+
+
+
+// Updates an existing voice_extension in the DB.
+exports.update = function(req, res) {
+  return sequelize.transaction(function(t) {
+      return veUtil.routeUpdate(req, t);
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      err.message = 'Extension/context combination already exist';
+      err.translatedMessage = 'MESSAGE_EXIST_ROUTE'
+      console.log(err);
+      return handleError(res, err);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+}
+
+// Deletes a voice_extension from the DB.
+exports.destroy = function(req, res, next) {
+  return sequelize.transaction(function(t) {
+      return VoiceExtension
+        .findById(req.params.id)
+        .then(function(voice_extension) {
+          if (!voice_extension) {
+            return res.sendStatus(404);
+          }
+          return voice_extension.destroy();
+        });
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes from the DB.
+exports.bulkDestroy = function(req, res) {
+  VoiceExtension
+    .destroy({
+      where: {
+        id: req.query.id
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/voice_extension/voice_extension.socket.js b/server/api/voice_extension/voice_extension.socket.js
index e2b0872..8da1778 100644
--- a/server/api/voice_extension/voice_extension.socket.js
+++ b/server/api/voice_extension/voice_extension.socket.js
@@ -1 +1,34 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var VoiceExtension = require('../../models').VoiceExtension;
+
+exports.register = function(socket) {
+  VoiceExtension.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  VoiceExtension.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+  // VoiceExtension.afterBulkDestroy(function(doc) {//causes a maximum stack range error due to the object sent being too big!!
+  //   onRemove(socket, doc);
+  // });
+  VoiceExtension.afterUpdate(function(doc) {
+    onUpdate(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('voice_extension:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('voice_extension:remove', doc);
+}
+
+function onUpdate(socket, doc, cb) {
+  socket.emit('voice_extension:update', doc);
+}
diff --git a/server/api/voice_extension/voice_extension.spec.js b/server/api/voice_extension/voice_extension.spec.js
index 7c8525a..0971536 100644
--- a/server/api/voice_extension/voice_extension.spec.js
+++ b/server/api/voice_extension/voice_extension.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/voice/extensions', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/voice/extensions')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/voice_musiconhold/index.js b/server/api/voice_musiconhold/index.js
index 1b235ec..59cc818 100644
--- a/server/api/voice_musiconhold/index.js
+++ b/server/api/voice_musiconhold/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./voice_musiconhold.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/stream', auth.isAuthenticated(), controller.stream);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/files', auth.isAuthenticated(), controller.showAudioFiles);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.mohValidation);
+router.post('/:id/files', auth.isAuthenticated(), controller.addFiles);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/:id/files', auth.isAuthenticated(), controller.deleteAudioFiles);
+
+module.exports = router;
diff --git a/server/api/voice_musiconhold/voice_musiconhold.controller.js b/server/api/voice_musiconhold/voice_musiconhold.controller.js
index 8cf6316..6c74d50 100644
--- a/server/api/voice_musiconhold/voice_musiconhold.controller.js
+++ b/server/api/voice_musiconhold/voice_musiconhold.controller.js
@@ -1 +1,283 @@
\ No newline at end of file
+'use strict';
+
+var fs = require('fs');
+var _ = require('lodash');
+var path = require('path');
+var util = require('util');
+var fse = require('fs-extra');
+var remove = require('remove');
+var base64 = require('file-base64');
+
+var config = require('../../config/environment');
+var Util = require('../../config/util');
+
+var Sound = require('../../models').Sound;
+var sequelize = require('../../models').sequelize;
+var VoiceMusicOnHold = require('../../models').VoiceMusicOnHold;
+
+
+// Get list of voice_musiconholds
+exports.index = function(req, res) {
+  return VoiceMusicOnHold
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single voiceMusiconhold
+exports.show = function(req, res) {
+  return VoiceMusicOnHold
+    .findById(req.params.id)
+    .then(function(voiceMusiconhold) {
+      if (!voiceMusiconhold) {
+        return res.sendStatus(404);
+      }
+      return res.send(voiceMusiconhold);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Add files to moh class
+exports.addFiles = function(req, res) {
+  var _voiceMusiconhold;
+  return VoiceMusicOnHold
+    .findById(req.params.id)
+    .then(function(voiceMusiconhold) {
+      if (!voiceMusiconhold) {
+        return res.sendStatus(404);
+      }
+      _voiceMusiconhold = voiceMusiconhold;
+
+      return Sound
+        .findAll({
+          where: {
+            id: req.body.sounds
+          }
+        });
+    })
+    .then(function(soundFiles) {
+      var origin = path.join(config.root, 'server/files/sounds/converted');
+      var dest;
+      soundFiles.forEach(function(elem) {
+        dest = path.join(_voiceMusiconhold.directory, elem.display_name + '.wav');
+        fse.copySync(path.join(origin, elem.save_name + '.wav'), dest);
+      });
+      fs.chmodSync(dest, parseInt('0777', 8));
+      res.sendStatus(200);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.showAudioFiles = function(req, res) {
+  var fileObject = {};
+  VoiceMusicOnHold
+    .findById(req.params.id)
+    .then(function(voiceMusiconhold) {
+      if (!voiceMusiconhold) {
+        return res.sendStatus(404);
+      }
+      fileObject.path = voiceMusiconhold.directory;
+      fs.readdir(voiceMusiconhold.directory, function(err, files) {
+        if (!err) {
+          fileObject.files = files;
+          return res.send(fileObject);
+        } else {
+          throw err;
+        }
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.deleteAudioFiles = function(req, res) {
+  VoiceMusicOnHold
+    .findById(req.params.id)
+    .then(function(voiceMusiconhold) {
+      if (!voiceMusiconhold) {
+        return res.sendStatus(404);
+      }
+      var deletePath = voiceMusiconhold.directory + '/' + req.query.filename;
+      fs.unlink(deletePath, function(err) {
+        if (err) {
+          return handleError(res, err);
+        } else {
+          return res.sendStatus(204);
+        }
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Validate the existence of a route
+exports.mohValidation = function(req, res) {
+  console.log(req.body);
+  VoiceMusicOnHold
+    .findAll({
+      where: {
+        name: req.body.name,
+      }
+    })
+    .then(function(voice_mohs) {
+      if (!voice_mohs) {
+        return res.sendStatus(404);
+      }
+      return res.send(voice_mohs);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new voiceMusiconhold in the DB.
+exports.create = function(req, res) {
+  return sequelize.transaction().then(function(t) {
+    req.body.directory = path.join(config.root, 'server/files/moh', req.body.name);
+    return VoiceMusicOnHold
+      .create(req.body, {
+        transaction: t
+      })
+      .then(function(voiceMusiconhold) {
+        fs.mkdir(voiceMusiconhold.directory, parseInt('0777', 8), function(err) {
+          if (err) {
+            console.log('file error');
+            t.rollback();
+            return res.status(500).send({
+              message: 'MESSAGE_FILE_CREATION_ERROR'
+            });
+          }
+          t.commit();
+          console.log('file created');
+          return res.status(201).send(voiceMusiconhold);
+        });
+      })
+      .catch(function(err) {
+        t.rollback();
+      });
+  })
+};
+
+// Updates an existing voiceMusiconhold in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return VoiceMusicOnHold
+    .findById(req.params.id)
+    .then(function(voiceMusiconhold) {
+      if (!voiceMusiconhold) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(voiceMusiconhold, req.body);
+
+      return updated
+        .save();
+    })
+    .then(function(voiceMusiconhold) {
+      return res.status(200).send(voiceMusiconhold);
+    })
+    .catch(function(err) {
+      console.error('err', err);
+      return handleError(res, err);
+    });
+};
+
+// Deletes a voiceMusiconhold from the DB.
+exports.destroy = function(req, res) {
+  var _voicemoh;
+  return sequelize.transaction(function(t) {
+      return VoiceMusicOnHold
+        .findById(req.params.id)
+        .then(function(voiceMusiconhold) {
+          if (!voiceMusiconhold) {
+            return res.sendStatus(404);
+          }
+          _voicemoh = voiceMusiconhold;
+          return voiceMusiconhold.destroy({
+            transaction: t
+          });
+        })
+        .then(function() {
+          remove(_voicemoh.directory, function(err) {
+            if (err) {
+              console.log(err);
+            }
+            return res.sendStatus(204);
+          });
+        })
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes multiple voice_musiconholds from the DB.
+exports.bulkDestroy = function(req, res) {
+  var _voicemohs;
+  return sequelize.transaction(function(t) {
+      return VoiceMusicOnHold
+        .findAll({
+          where: {
+            id: req.query.ids
+          }
+        })
+        .then(function(voice_musiconholds) {
+          if (!voice_musiconholds) {
+            return res.sendStatus(404);
+          }
+          _voicemohs = voice_musiconholds;
+          var bulkDest = [];
+          voice_musiconholds.forEach(function(element) {
+            bulkDest.push(element.destroy({
+              transaction: t
+            }));
+          });
+          return bulkDest;
+        })
+        .all()
+        .then(function() {
+          _voicemohs.forEach(function(element) {
+            remove(element.directory, function(err) {
+              if (err) {
+                console.log(err);
+                throw err
+              }
+            });
+          })
+        });
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// stream a moh file
+exports.stream = function(req, res) {
+  var filepath = decodeURI(req.query.path);
+  base64.encode(filepath, function(err, base64String) {
+    if (err) {
+      return handleError(res, err);
+    } else {
+      return res.status(200).send(base64String);
+    }
+  });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/voice_musiconhold/voice_musiconhold.socket.js b/server/api/voice_musiconhold/voice_musiconhold.socket.js
index 677278f..efa073f 100644
--- a/server/api/voice_musiconhold/voice_musiconhold.socket.js
+++ b/server/api/voice_musiconhold/voice_musiconhold.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var VoiceMusicOnHold = require('../../models').VoiceMusicOnHold;
+
+exports.register = function(socket) {
+  VoiceMusicOnHold.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  VoiceMusicOnHold.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('voice_musiconhold:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('voice_musiconhold:remove', doc);
+}
diff --git a/server/api/voice_musiconhold/voice_musiconhold.spec.js b/server/api/voice_musiconhold/voice_musiconhold.spec.js
index 789ca27..e992daf 100644
--- a/server/api/voice_musiconhold/voice_musiconhold.spec.js
+++ b/server/api/voice_musiconhold/voice_musiconhold.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/voice/musiconhold', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/voice/musiconhold')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/voice_queue/index.js b/server/api/voice_queue/index.js
index 287c56e..f864580 100644
--- a/server/api/voice_queue/index.js
+++ b/server/api/voice_queue/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./voice_queue.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:name', auth.isAuthenticated(), controller.show);
+router.get('/:name/agents', auth.isAuthenticated(), controller.showAgents);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:name/agents', auth.isAuthenticated(), controller.addAgents);
+router.put('/:name', auth.isAuthenticated(), controller.update);
+router.delete('/:name/agents', auth.isAuthenticated(), controller.removeAgents);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:name', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/voice_queue/voice_queue.controller.js b/server/api/voice_queue/voice_queue.controller.js
index 0142d77..4143afc 100644
--- a/server/api/voice_queue/voice_queue.controller.js
+++ b/server/api/voice_queue/voice_queue.controller.js
@@ -1 +1,211 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+
+var util = require('util');
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var User = require('../../models').User;
+var VoiceQueue = require('../../models').VoiceQueue;
+var UserHasVoiceQueue = require('../../models').UserHasVoiceQueue;
+var VoiceExtension = require('../../models').VoiceExtension;
+
+// Get list of voiceQueues
+exports.index = function(req, res) {
+  return VoiceQueue
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single voiceQueue
+exports.show = function(req, res) {
+  return VoiceQueue
+    .findById(req.params.name)
+    .then(function(voiceQueue) {
+      if (!voiceQueue) {
+        return res.sendStatus(404);
+      }
+      return res.send(voiceQueue);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single queue
+exports.showAgents = function(req, res) {
+  return VoiceQueue
+    .findById(req.params.name)
+    .then(function(queue) {
+      if (!queue) {
+        return res.sendStatus(404);
+      }
+      return queue
+        .getUsers({
+          where: {
+            role: 'agent'
+          },
+          attributes: ['id', 'name', 'fullname', 'email']
+        });
+    })
+    .then(function(agents) {
+      return res.status(200).send(agents);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new voiceQueue in the DB.
+exports.create = function(req, res) {
+  return VoiceQueue
+    .create(req.body)
+    .then(function(voiceQueue) {
+      return res.status(201).send(voiceQueue);
+    })
+    .catch(sequelize.UniqueConstraintError, function(err) {
+      var paths = [];
+
+      err.errors.forEach(function(error) {
+        switch (error.path) {
+          case 'PRIMARY':
+            paths.push('name');
+            break;
+          default:
+            paths.push(error.path);
+        }
+      });
+
+      paths.push(' must be unique');
+      err.message = paths.join();
+      err.translatedMessage = 'MESSAGE_EXIST_QUEUE';
+
+      return res.status(500).send(err);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing voiceQueue in the DB.
+exports.update = function(req, res) {
+  if (req.body.name) {
+    delete req.body.name;
+  }
+
+  return VoiceQueue
+    .findById(req.params.name)
+    .then(function(voiceQueue) {
+      if (!voiceQueue) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(voiceQueue, req.body);
+      return updated
+        .save();
+    })
+    .then(function(voiceQueue) {
+      return res.status(200).send(voiceQueue);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+
+// Add agents to voice queues
+exports.addAgents = function(req, res, next) {
+  return User
+    .findAll({
+      where: {
+        id: {
+          $in: req.body.agents
+        }
+      }
+    })
+    .then(function(users) {
+      var userHasVoiceQueues = _.map(users, function(user) {
+        return {
+          queue: req.params.name,
+          location: util.format('SIP/%s', user.name),
+          membername: user.name,
+          UserId: user.id,
+          penalty: req.body.penalty
+        };
+      });
+
+      return UserHasVoiceQueue
+        .bulkCreate(userHasVoiceQueues, {
+          individualHooks: true
+        });
+    })
+    .then(function() {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return next(err);
+    });
+};
+
+exports.removeAgents = function(req, res, next) {
+  return UserHasVoiceQueue
+    .destroy({
+      where: {
+        UserId: req.query.agents,
+        queue: req.params.name
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(202);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Deletes a voiceQueue from the DB.
+exports.destroy = function(req, res) {
+  return VoiceQueue
+    .destroy({
+      where: {
+        name: req.params.name
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes multiple voiceQueues from the DB.
+exports.bulkDestroy = function(req, res) {
+  return VoiceQueue
+    .destroy({
+      where: {
+        name: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/voice_queue/voice_queue.socket.js b/server/api/voice_queue/voice_queue.socket.js
index a3d4239..6cb2204 100644
--- a/server/api/voice_queue/voice_queue.socket.js
+++ b/server/api/voice_queue/voice_queue.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var VoiceQueue = require('../../models').VoiceQueue;
+
+exports.register = function(socket) {
+  VoiceQueue.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  VoiceQueue.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('voice_queue:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('voice_queue:remove', doc);
+}
diff --git a/server/api/voice_recording/index.js b/server/api/voice_recording/index.js
index 95dd7cb..61bbb0d 100644
--- a/server/api/voice_recording/index.js
+++ b/server/api/voice_recording/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./voice_recording.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/stream', auth.isAuthenticated(), controller.getStream);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/voice_recording/voice_recording.controller.js b/server/api/voice_recording/voice_recording.controller.js
index fd2e787..0c800bc 100644
--- a/server/api/voice_recording/voice_recording.controller.js
+++ b/server/api/voice_recording/voice_recording.controller.js
@@ -1 +1,172 @@
\ No newline at end of file
+/**
+ * Using Rails-like standard naming convention for endpoints.
+ * GET     /api/voice/recordings              ->  index
+ * POST    /api/voice/recordings              ->  create
+ * GET     /api/voice/recordings/:id          ->  show
+ * PUT     /api/voice/recordings/:id          ->  update
+ * DELETE  /api/voice/recordings/:id          ->  destroy
+ */
+
+'use strict';
+
+
+var _ = require('lodash');
+var util = require('util');
+
+var VoiceRecording = require('../../models').VoiceRecording;
+var Promise = require('bluebird');
+// var base64 = Promise.promisifyAll(require('file-base64'));
+var base64 = require('file-base64');
+var fs = require('fs');
+
+function handleError(res) {
+  return function(err) {
+    if (err.cause && err.cause.code === 'ENOENT') {
+      err = {
+        message: 'No file or directory',
+        translatedMessage: 'MESSAGE_NO_FILE_OR_DIRECTORY'
+      };
+    }
+    return res.status(500).send(err);
+  }
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(body) {
+  return function(entity) {
+    return entity
+      .updateAttributes(body);
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+function getQuery(query) {
+  var _query = {};
+  _query.where = {};
+
+  _.forEach(query, function(value, key) {
+    switch (key) {
+      case 'limit':
+      case 'offset':
+        _query[key] = Number(value);
+        break;
+      case 'order':
+        _query[key] = value;
+        break;
+      default:
+        _query.where[key] = {
+          $like: '%' + value + '%'
+        };
+    }
+  });
+
+  return _query;
+}
+
+// Gets a list of VoiceRecordings
+exports.index = function(req, res) {
+  var _query = getQuery(req.query);
+  return VoiceRecording
+    .findAndCountAll(_query)
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single VoiceRecording from the DB
+exports.show = function(req, res) {
+  return VoiceRecording.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Gets a single VoiceRecording from the DB
+exports.getStream = function(req, res) {
+  return VoiceRecording
+    .findById(req.params.id)
+    .then(function(recording) {
+      var readFile = Promise.promisify(fs.readFile);
+      return readFile(recording.value);
+    })
+    .then(function(result) {
+      var base64String = new Buffer(result, 'binary').toString('base64');
+      return base64String;
+    })
+    .then(function(base64String) {
+      return res.status(200).send({
+        downloadString: base64String
+      });
+    })
+    .catch(handleError(res));
+}
+
+
+// Creates a new VoiceRecording in the DB
+exports.create = function(req, res) {
+  return VoiceRecording.create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+}
+
+// Updates an existing VoiceRecording in the DB
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return VoiceRecording.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+// Deletes a VoiceRecording from the DB
+exports.destroy = function(req, res) {
+  return VoiceRecording.findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+}
+
+// Deletes a agent from the DB.
+exports.bulkDestroy = function(req, res) {
+  return VoiceRecording
+    .destroy({
+      where: {
+        uniqueid: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(handleError(res));
+};
diff --git a/server/api/voice_recording/voice_recording.socket.js b/server/api/voice_recording/voice_recording.socket.js
index dbe71c0..a0f969a 100644
--- a/server/api/voice_recording/voice_recording.socket.js
+++ b/server/api/voice_recording/voice_recording.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var VoiceRecording = require('../../models').VoiceRecording;
+
+exports.register = function(socket) {
+  VoiceRecording.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  VoiceRecording.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+};
+
+function onSave(socket, doc, cb) {
+  socket.emit('voice_recording:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('voice_recording:remove', doc);
+}
diff --git a/server/api/voice_voicemail/index.js b/server/api/voice_voicemail/index.js
index fa46a92..96f6298 100644
--- a/server/api/voice_voicemail/index.js
+++ b/server/api/voice_voicemail/index.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./voice_voicemail.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:uniqueid', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/validate', auth.isAuthenticated(), controller.mailboxValidation);
+router.put('/:uniqueid', auth.isAuthenticated(), controller.update);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:uniqueid', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/voice_voicemail/voice_voicemail.controller.js b/server/api/voice_voicemail/voice_voicemail.controller.js
index e7fbe05..69a1139 100644
--- a/server/api/voice_voicemail/voice_voicemail.controller.js
+++ b/server/api/voice_voicemail/voice_voicemail.controller.js
@@ -1 +1,190 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var stream = require('stream');
+var Util = require('../../config/util');
+
+var VoiceVoicemail = require('../../models').VoiceVoicemail;
+var VoiceVoicemailMessages = require('../../models').VoiceVoicemailMessages;
+
+// Get list of voice_voicemails
+exports.index = function(req, res) {
+  return VoiceVoicemail
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single voice_voicemail
+exports.show = function(req, res) {
+  VoiceVoicemail
+    .find({
+      where: {
+        uniqueid: req.params.uniqueid
+      }
+    })
+    .then(function(voice_voicemail) {
+      if (!voice_voicemail) {
+        return res.sendStatus(404);
+      }
+      return res.send(voice_voicemail);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// validate mailbox uniqueness
+exports.mailboxValidation = function(req, res) {
+  console.log(req.body);
+  VoiceVoicemail
+    .findAll({
+      where: {
+        mailbox: req.body.mailbox,
+        context: req.body.context
+      }
+    })
+    .then(function(voicemails) {
+      if (!voicemails) {
+        return res.sendStatus(404);
+      }
+      return res.send(voicemails);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+// Creates a new voice_voicemail in the DB.
+exports.create = function(req, res) {
+  VoiceVoicemail
+    .findAll({
+      where: {
+        mailbox: req.body.mailbox,
+        context: req.body.context
+      }
+    })
+    .then(function(voicemails) {
+      if (!voicemails) {
+        return res.sendStatus(404);
+      }
+      if (voicemails.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_MAILBOX'
+        })
+      }
+      VoiceVoicemail
+        .create(req.body)
+        .then(function(voice_voicemail) {
+          return res.status(201).send(voice_voicemail);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+};
+
+// Updates an existing voice_voicemail in the DB.
+exports.update = function(req, res) {
+  VoiceVoicemail
+    .findAll({
+      where: {
+        mailbox: req.body.mailbox,
+        context: req.body.context,
+        uniqueid: {
+          $ne: req.body.uniqueid
+        }
+      }
+    })
+    .then(function(voicemails) {
+      if (!voicemails) {
+        return res.sendStatus(404);
+      }
+      if (voicemails.length > 0) {
+        return res.status(500).send({
+          message: 'MESSAGE_EXIST_MAILBOX'
+        })
+      }
+      if (req.body.uniqueid) {
+        delete req.body.uniqueid;
+      }
+      VoiceVoicemail
+        .find({
+          where: {
+            uniqueid: req.params.uniqueid
+          }
+        })
+        .then(function(voice_voicemail) {
+          if (!voice_voicemail) {
+            return res.sendStatus(404);
+          }
+          var updated = _.merge(voice_voicemail, req.body);
+          updated.save()
+            .then(function() {
+              return res.status(200).send(voice_voicemail);
+            })
+            .catch(function(err) {
+              return handleError(res, err);
+            });
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+
+};
+
+// Deletes a voice_voicemail from the DB.
+exports.destroy = function(req, res) {
+  VoiceVoicemail
+    .find({
+      where: {
+        uniqueid: req.params.uniqueid
+      }
+    })
+    .then(function(voice_voicemail) {
+      if (!voice_voicemail) {
+        return res.sendStatus(404);
+      }
+      voice_voicemail.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a voice_voicemail from the DB.
+exports.bulkDestroy = function(req, res) {
+  VoiceVoicemail
+    .destroy({
+      where: {
+        uniqueid: req.query.uniqueid
+      },
+      individualHooks: true
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/voice_voicemail/voice_voicemail.socket.js b/server/api/voice_voicemail/voice_voicemail.socket.js
index 643da78..41e7a38 100644
--- a/server/api/voice_voicemail/voice_voicemail.socket.js
+++ b/server/api/voice_voicemail/voice_voicemail.socket.js
@@ -1 +1,39 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var VoiceVoicemail = require('../../models').VoiceVoicemail;
+var VoiceVoicemailMessages = require('../../models').VoiceVoicemailMessages;
+
+exports.register = function(socket) {
+  VoiceVoicemail.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  VoiceVoicemail.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+  VoiceVoicemailMessages.afterCreate(function(doc) {
+    onMessageSave(socket, doc);
+  });
+  VoiceVoicemailMessages.afterDestroy(function(doc) {
+    onMessageRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('voice_voicemail:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('voice_voicemail:remove', doc);
+}
+
+function onMessageSave(socket, doc, cb) {
+  socket.emit('voice_voicemail_messages:save', doc);
+}
+
+function onMessageRemove(socket, doc, cb) {
+  socket.emit('voice_voicemail_messages:remove', doc);
+}
diff --git a/server/api/voice_voicemail/voice_voicemail.spec.js b/server/api/voice_voicemail/voice_voicemail.spec.js
index 7861fea..5785825 100644
--- a/server/api/voice_voicemail/voice_voicemail.spec.js
+++ b/server/api/voice_voicemail/voice_voicemail.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/voice/voicemails', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/voice/voicemails')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/voice_voicemail_messages/index.js b/server/api/voice_voicemail_messages/index.js
index c3be621..b80b40a 100644
--- a/server/api/voice_voicemail_messages/index.js
+++ b/server/api/voice_voicemail_messages/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./voice_voicemail_messages.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/download', auth.isAuthenticated(), controller.download);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/voice_voicemail_messages/voice_voicemail_messages.controller.js b/server/api/voice_voicemail_messages/voice_voicemail_messages.controller.js
index b6d1bba..8268a61 100644
--- a/server/api/voice_voicemail_messages/voice_voicemail_messages.controller.js
+++ b/server/api/voice_voicemail_messages/voice_voicemail_messages.controller.js
@@ -1 +1,106 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var stream = require('stream');
+var Util = require('../../config/util');
+
+var VoiceVoicemailMessages = require('../../models').VoiceVoicemailMessages;
+
+// Get list of voicemail messages
+exports.index = function(req, res) {
+  return VoiceVoicemailMessages
+    .scope({
+      method: ['mailbox', req.query]
+    })
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(function(result) {
+      res.status(200).send(result);
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+// Get a single voicemail_message
+exports.show = function(req, res) {
+  return VoiceVoicemailMessages
+    .findById(req.params.id)
+    .then(function(voicemail_message) {
+      if (!voicemail_message) {
+        return res.sendStatus(404);
+      }
+      return res.send(voicemail_message);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a voicemail_message from the DB.
+exports.destroy = function(req, res) {
+  return VoiceVoicemailMessages
+    .findById(req.params.id)
+    .then(function(voicemail_message) {
+      if (!voicemail_message) {
+        return res.sendStatus(404);
+      }
+      voicemail_message.recording = null;
+      voicemail_message.destroy();
+    })
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes all voicemail_messages from the DB.
+exports.bulkDestroy = function(req, res) {
+  return VoiceVoicemailMessages
+    .findAll({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(function(voicemail_messages) {
+      var bulkDest = [];
+      voicemail_messages.forEach(function(message) {
+        voicemail_message.recording = null;
+        bulkDest.push(voicemail_message.destroy());
+      });
+      return bulkDest;
+    })
+    .all()
+    .then(function() {
+      return res.sendStatus(204);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.download = function(req, res) {
+  return VoiceVoicemailMessages
+    .findById(req.params.id)
+    .then(function(message) {
+      var base64String = new Buffer(message.recording, 'binary').toString('base64');
+      return base64String;
+    })
+    .then(function(base64String) {
+      return res.status(200).send({
+        downloadString: base64String
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/voice_voicemail_messages/voice_voicemail_messages.socket.js b/server/api/voice_voicemail_messages/voice_voicemail_messages.socket.js
index 3deb056..ee0fbef 100644
--- a/server/api/voice_voicemail_messages/voice_voicemail_messages.socket.js
+++ b/server/api/voice_voicemail_messages/voice_voicemail_messages.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var VoiceVoicemailMessages = require('../../models').VoiceVoicemailMessages;
+
+exports.register = function(socket) {
+  VoiceVoicemailMessages.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  VoiceVoicemailMessages.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('voice_voicemail_messages:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('voice_voicemail_messages:remove', doc);
+}
diff --git a/server/api/widget/index.js b/server/api/widget/index.js
index 4d09978..374e711 100644
--- a/server/api/widget/index.js
+++ b/server/api/widget/index.js
@@ -1 +1,19 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./widget.controller');
+var auth = require('../../auth/auth.service');
+
+var router = express.Router();
+
+// router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/counter', auth.isAuthenticated(), controller.counter);
+router.get('/table', auth.isAuthenticated(), controller.table);
+router.get('/piechart', auth.isAuthenticated(), controller.piechart);
+// router.get('/:id', auth.isAuthenticated(), controller.show);
+// router.post('/', auth.isAuthenticated(), controller.create);
+// router.put('/:id', auth.isAuthenticated(), controller.update);
+// router.patch('/:id', auth.isAuthenticated(), controller.update);
+// router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/widget/widget.controller.js b/server/api/widget/widget.controller.js
index fe5e247..731df92 100644
--- a/server/api/widget/widget.controller.js
+++ b/server/api/widget/widget.controller.js
@@ -1 +1,102 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var sequelize = require('../../models').sequelize;
+
+// var ReportAgent = require('../../models').ReportAgent;
+// var ReportCall = require('../../models').ReportCall;
+// var ReportDial = require('../../models').ReportDial;
+// var ReportMember = require('../../models').ReportMember;
+// var ReportQueue = require('../../models').ReportQueue;
+
+
+
+exports.counter = function(req, res) {
+  var query = util.format("SELECT %s FROM %s", req.query.select, req.query.table);
+  if (req.query.condition) {
+    query += util.format(" WHERE %s", decodeURIComponent(req.query.condition))
+  }
+  sequelize.query(query, {
+      type: sequelize.QueryTypes.SELECT
+    })
+    .then(function(result) {
+      return res.status(200).send({
+        result: result
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+      return handleError(res, err);
+    });
+};
+
+exports.table = function(req, res) {
+  var columns = [];
+  var elem;
+  if (req.query.fields) {
+    if (_.isArray(req.query.fields)) {
+      req.query.fields.forEach(function(element) {
+        elem = JSON.parse(element);
+        if (elem.column && elem.alias) {
+          columns.push(elem.column + ' AS "' + elem.alias + '"');
+        }
+      });
+    } else if (_.isString(req.query.fields)) {
+      elem = JSON.parse(req.query.fields);
+      if (elem.column && elem.alias) {
+        columns.push(elem.column + ' AS "' + elem.alias + '"');
+      }
+    }
+  }
+  var select = columns.length ? columns.join(',') : '*';
+  sequelize.query(util.format("SELECT %s FROM %s WHERE %s", select, 'report' + req.query.table, decodeURIComponent(req.query.condition)), {
+      type: sequelize.QueryTypes.SELECT
+    })
+    .then(function(result) {
+      return res.status(200).send(result);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.piechart = function(req, res) {
+  var columns = [];
+  var elem;
+  if (req.query.fields) {
+    if (_.isArray(req.query.fields)) {
+      req.query.fields.forEach(function(element) {
+        elem = JSON.parse(element);
+        if (elem.column && elem.alias) {
+          columns.push(elem.column + ' AS "' + elem.alias + '"');
+        }
+      });
+    } else if (_.isString(req.query.fields)) {
+      elem = JSON.parse(req.query.fields);
+      if (elem.column && elem.alias) {
+        columns.push(elem.column + ' AS "' + elem.alias + '"');
+      }
+    }
+  }
+  var select = columns.length ? columns.join(',') : '*';
+  var where = '';
+  if (req.query.condition !== 'undefined') {
+    where = util.format(' WHERE %s', decodeURIComponent(req.query.condition));
+  }
+  sequelize.query(util.format("SELECT %s FROM %s%s", select, req.query.table, where), {
+      type: sequelize.QueryTypes.SELECT
+    })
+    .then(function(result) {
+      return res.status(200).send({
+        result: result
+      });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/widget/widget.spec.js b/server/api/widget/widget.spec.js
index db91344..94ea420 100644
--- a/server/api/widget/widget.spec.js
+++ b/server/api/widget/widget.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/widgets', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/widgets')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/xchatty/index.js b/server/api/xchatty/index.js
index 32dd7f5..e03bc91 100644
--- a/server/api/xchatty/index.js
+++ b/server/api/xchatty/index.js
@@ -1 +1,33 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./xchatty.controller');
+
+var router = express.Router();
+
+router.get('/jquery', controller.getJqueryLibrary);
+router.get('/assets', controller.getAssets);
+
+router.get('/status', controller.getStatus);
+router.get('/signup', controller.signupCustomer);
+router.get('/read', controller.getMessages);
+router.get('/send', controller.sendMessage);
+router.get('/signout', controller.signoutCustomer);
+router.get('/submit', controller.submitEnquiry);
+router.get('/unserved', controller.unservedCustomer);
+router.get('/transcript', controller.getTranscript);
+router.get('/rating', controller.setRating);
+router.get('/abandon', controller.abandonCustomer);
+
+router.get('/:id', controller.getJsByWebsite);
+router.get('/:id/html', controller.getHtmlByWebsite);
+router.get('/:id/proactive', controller.getProactiveActionsByWebsite);
+router.get('/:id/styles', controller.getStyleByWebsite);
+
+// router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/xchatty/xchatty.controller.js b/server/api/xchatty/xchatty.controller.js
index c9cbc99..017d746 100644
--- a/server/api/xchatty/xchatty.controller.js
+++ b/server/api/xchatty/xchatty.controller.js
@@ -1 +1,829 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var jsmin = require('jsmin').jsmin;
+var Mustache = require('mustache');
+var querystring = require('querystring');
+var path = require('path');
+var md5 = require('md5');
+var fs = require('fs');
+var UAParser = require('ua-parser-js');
+var uaParser = new UAParser();
+var languageParser = require('accept-language-parser');
+var ipaddr = require('ipaddr.js');
+var geoip = require('geoip-lite');
+var csv = require('to-csv');
+var util = require('util');
+var moment = require('moment');
+
+var config = require('../../config/environment');
+var Xchatty = require('../../models').Xchatty;
+var xchatty_components = require('../../components/xchatty');
+var Util = require('../../config/util');
+
+var sequelize = require('../../models').sequelize;
+var User = require('../../models').User;
+var Agent = require('../../models').User;
+var ChatRoom = require('../../models').ChatRoom;
+var ChatEnquiry = require('../../models').ChatEnquiry;
+var ChatVisitor = require('../../models').ChatVisitor;
+var ChatMessage = require('../../models').ChatMessage;
+var ChatWebsite = require('../../models').ChatWebsite;
+var ChatWebsitesField = require('../../models').ChatWebsitesField;
+var ChatProactiveAction = require('../../models').ChatProactiveAction;
+var ReportChatSession = require('../../models').ReportChatSession;
+
+var id = 1;
+
+// Get list of xchattys
+exports.index = function(req, res, next) {
+  return Xchatty
+    .findAll()
+    .then(function(xchattys) {
+      return res.status(200).send(xchattys);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+exports.getJsByWebsite = function(req, res, next) {
+
+  // TO DO: Find the configuration of the website and replace configuration in the file
+  ChatWebsite
+    .findById(req.params.id)
+    .then(function(chatWebsite) {
+      res.set({
+        'Content-Type': 'application/javascript',
+        'x-timestamp': Date.now(),
+        'x-sent': true
+      });
+
+      var js = xchatty_components.js;
+      /*jshint multistr: true */
+      var configJS = {
+        websiteId: req.params.id,
+        // rootPath: path.join(chatWebsite.remote, 'api', 'xchatty/'),
+        rootPath: Util.stripTrailingSlash(chatWebsite.remote) + '/api/xchatty/',
+        signedUp: 'false',
+        windowFocused: 'false',
+        isNewPage: 'true',
+        processingSignUp: 'false',
+        processingEnquiry: 'false',
+        processingSend: 'false',
+        processingRating: 'false',
+        visitorFullname: '""',
+        visitorAvatar: '""',
+        timerReadMessages: 'null',
+        timerCheckStatus: 'null',
+        isOnline: 'false',
+        decayHistory: 0,
+        defaultTimeout: 5000,
+        isInitialStatusCheck: 'true',
+        muteNotificationSoundOnce: 'false',
+        assetBase: Util.stripTrailingSlash(chatWebsite.remote) + '/api/xchatty/assets',
+        hasSessionSaf: 'false',
+        restoreOpenState: 'false',
+        animateHover: chatWebsite.animation,
+        hideEmail: 'false',
+        hideOffline: 'false',
+        headerShape: chatWebsite.header_shape,
+        HeaderOnline: chatWebsite.header_online,
+        OnlineMessage: chatWebsite.online_message,
+        UsernamePlaceholder: chatWebsite.username_placeholder,
+        EmailPlaceholder: chatWebsite.email_placeholder,
+        StartChatButtonText: chatWebsite.start_chat_button,
+        OfflineMessage: chatWebsite.offline_message,
+        HeaderOffline: chatWebsite.header_offline,
+        EnquiryMessagePlaceholder: chatWebsite.enquiry_message_placeholder,
+        EnquiryButtonText: chatWebsite.enquiry_button,
+        downloadTranscript: chatWebsite.download_transcript,
+        RatingMessage: chatWebsite.rating_message,
+        RatingSend: chatWebsite.rating_send,
+        RatingSkip: chatWebsite.rating_skip
+      };
+
+      js = Mustache.render(js, configJS);
+      res.send(js);
+
+    })
+    .catch(function(err) {
+      next(err);
+    });
+
+};
+
+exports.getStyleByWebsite = function(req, res, next) {
+
+  // TO DO: Find the configuration of the website and replace configuration in the file
+  ChatWebsite
+    .findById(req.params.id)
+    .then(function(chatWebsite) {
+
+      res.set({
+        'Content-Type': 'text/css',
+        'x-timestamp': Date.now(),
+        'x-sent': true
+      });
+
+      var css = xchatty_components.css;
+
+      var configCSS = {
+        // assetBase: path.join(chatWebsite.remote, 'api', 'xchatty',
+        //   'assets'),
+        assetBase: Util.stripTrailingSlash(chatWebsite.remote) + '/api/xchatty/assets',
+        color: chatWebsite.color,
+        colorFocus: chatWebsite.color_focus,
+        colorButton: chatWebsite.color_button
+      };
+
+      css = Mustache.render(css, configCSS);
+      res.send(css);
+    })
+    .catch(function(err) {
+      next(err);
+    });
+};
+
+exports.getAssets = function(req, res, next) {
+  res.download(path.join(config.root, 'server', 'components', 'xchatty', 'assets', req.query.resource));
+};
+
+exports.getJqueryLibrary = function(req, res, next) {
+
+  // TODO: Find the configuration of the website and replace configuration in the file
+  res.set({
+    'Content-Type': 'application/javascript',
+    'x-timestamp': Date.now(),
+    'x-sent': true
+  });
+
+  var jquery = xchatty_components.jquery;
+  res.send(jquery);
+
+};
+
+exports.getStatus = function(req, res, next) {
+
+  if (!req.session.xchatty) {
+    req.session.xchatty = {};
+  }
+
+  var _jsonResponse = {};
+
+  return Agent
+    .findAll({
+      where: {
+        online: true,
+        role: 'agent'
+      }
+    })
+    .then(function(agents) {
+      if (req.query.data.open_state)
+        req.session.xchatty.open_state = req.query.data.open_state === 'true' ? true : false;
+      _jsonResponse = {
+        success: true,
+        has_session_saf: null,
+        signed_up: req.session.xchatty.room_id ? true : false,
+        fullname: req.session.xchatty.fullname ? req.session.xchatty.fullname : '',
+        email: req.session.xchatty.email ? req.session.xchatty.email : '',
+        avatar: req.session.xchatty.avatar ? req.session.xchatty.avatar : '',
+        open_state: req.session.xchatty.open_state || req.session.xchatty.room_id ? true : false,
+        online: agents.length ? true : false,
+        is_unserved: false,
+        room_id: 0
+      };
+      return;
+    })
+    .then(function() {
+      if (req.session.xchatty.visitor_id) {
+        return ChatRoom
+          .findOne({
+            where: {
+              ChatVisitorId: req.session.xchatty.visitor_id,
+              status: 'OPEN',
+              token: {
+                $ne: ''
+              }
+            }
+          });
+      }
+      return;
+    })
+    .then(function(chatRoom) {
+      if (chatRoom) {
+        req.session.xchatty.room_id = chatRoom.id;
+        _jsonResponse.room_id = chatRoom.id;
+        _jsonResponse.signed_up = true;
+      } else {
+        _jsonResponse.signed_up = false;
+      }
+      return;
+    })
+    .then(function() {
+      if (req.session.xchatty.visitor_id) {
+        return ChatVisitor
+          .findOne({
+            where: {
+              id: req.session.xchatty.visitor_id,
+              status: 'unserved'
+            }
+          })
+      }
+      return;
+    })
+    .then(function(visitor) {
+      if (visitor) {
+        _jsonResponse.is_unserved = true;
+      }
+      return res.send(req.query.callback + '(' + JSON.stringify(_jsonResponse) + ')');
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+exports.getMessages = function(req, res, next) {
+
+  /*jshint multistr: true */
+  var condition = {
+    where: {
+      ChatRoomId: req.session.xchatty.room_id
+    },
+    include: [{
+      model: User,
+      attributes: ['id', 'name', 'fullname', 'email']
+    }, {
+      model: ChatVisitor,
+      attributes: ['id', 'fullname', 'email']
+    }]
+  };
+
+  if (req.query.data.is_new_page === 'false') {
+    condition.where.id = {
+      $gt: req.query.data.last_id
+    };
+  }
+
+  ChatMessage
+    .findAll(condition)
+    .then(function(chatMessages) {
+      var json = {
+        success: true,
+        messages: chatMessages,
+        composing: false,
+        composing_fullname: null
+      };
+
+      res.send(req.query.callback + '(' + JSON.stringify(json) + ')');
+    })
+    .catch(function(err) {
+      next(err);
+    });
+};
+
+exports.sendMessage = function(req, res, next) {
+
+  /*jshint multistr: true */
+  var condition = {
+    where: {
+      ChatRoomId: req.session.xchatty.room_id
+    },
+    include: [{
+      model: User,
+      attributes: ['id', 'name', 'fullname', 'email']
+    }, {
+      model: ChatVisitor,
+      attributes: ['id', 'fullname', 'email']
+    }]
+  };
+
+  if (req.query.data.is_new_page === 'false') {
+    condition.where.id = {
+      $gt: req.query.data.last_id
+    };
+  }
+
+  ChatMessage
+    .create({
+      body: req.query.data.Message.message,
+      ChatRoomId: req.session.xchatty.room_id,
+      ChatVisitorId: req.session.xchatty.visitor_id
+    })
+    .then(function(chatMessage) {
+      ChatMessage
+        .findAll(condition)
+        .then(function(chatMessages) {
+          var json = {
+            errors: [],
+            success: true,
+            data: chatMessage,
+            messages: chatMessages
+          };
+
+          res.send(req.query.callback + '(' + JSON.stringify(json) + ')');
+        })
+        .catch(function(err) {
+          next(err);
+        });
+    })
+    .catch(function(err) {
+      next(err);
+    });
+
+};
+
+exports.signoutCustomer = function(req, res, next) {
+  return ChatRoom
+    .findById(req.session.xchatty.room_id)
+    .then(function(chatRoom) {
+      return chatRoom
+        .update({
+          status: 'CLOSED'
+        });
+    })
+    .then(function() {
+      var json = {
+        success: true,
+        errors: []
+      };
+      req.session.xchatty = {};
+      res.send(req.query.callback + '(' + JSON.stringify(json) + ')');
+    })
+    .catch(function(err) {
+      next(err);
+    });
+};
+
+exports.signupCustomer = function(req, res, next) {
+
+  var ua = uaParser.setUA(req.headers['user-agent']).getResult();
+  var languages = languageParser.parse(req.headers['accept-language']);
+  // var addr = ipaddr.parse(req.connection.remoteAddress);
+
+  var remoteAddress = req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] : req.connection.remoteAddress || req.ip;
+
+  // var ipv4Addr = "151.0.175.186";
+  // var geo = geoip.lookup(ipv4Addr);
+
+  var body = {
+    fullname: req.query.data.Discussion.fullname,
+    email: req.query.data.Discussion.email,
+    // remote_address: addr,
+    remote_address: remoteAddress,
+    user_agent: req.headers['user-agent'],
+    visitor_language: req.headers['accept-language'],
+    referer: req.headers.referer,
+    origin: 'webchat',
+    browser: ua.browser.name + ' ' + ua.browser.version,
+    engine: ua.engine.name + ' ' + ua.engine.version,
+    os: ua.os.name + ' ' + ua.os.version,
+    device: ua.device.model ? ua.device.model + ' ' + ua.device.vendor + ' ' + ua.device.type : null,
+    // country: geo.country,
+    // city: geo.city,
+    // region: geo.region,
+    // latitude: geo.ll.length ? geo.ll[0].toString() : null,
+    // longitude: geo.ll.length ? geo.ll[1].toString() : null,
+    ChatWebsiteId: req.query.chatWebsiteId
+  };
+
+  if (req.query.data.Discussion.data1 && req.query.data.Discussion.labelData1) {
+    body.data1 = req.query.data.Discussion.data1;
+    body.labelData1 = req.query.data.Discussion.labelData1;
+  }
+  if (req.query.data.Discussion.data2 && req.query.data.Discussion.labelData2) {
+    body.data2 = req.query.data.Discussion.data2;
+    body.labelData2 = req.query.data.Discussion.labelData2;
+  }
+  if (req.query.data.Discussion.data3 && req.query.data.Discussion.labelData3) {
+    body.data3 = req.query.data.Discussion.data3;
+    body.labelData3 = req.query.data.Discussion.labelData3;
+  }
+
+  return ChatVisitor
+    .create(body)
+    .then(function(chatVisitor) {
+
+      req.session.xchatty.fullname = chatVisitor.fullname;
+      req.session.xchatty.email = chatVisitor.email;
+      req.session.xchatty.avatar = md5(chatVisitor.email).toString('base64');
+      req.session.xchatty.visitor_id = chatVisitor.id;
+
+      var json = {
+        success: true,
+        errors: [],
+        chatVisitor: {
+          id: chatVisitor.id
+        }
+      };
+
+      /*jshint multistr: true */
+      res.send(req.query.callback + '(' + JSON.stringify(json) + ')');
+    })
+    .catch(function(err) {
+      next(err);
+    });
+};
+
+exports.unservedCustomer = function(req, res, next) {
+  return ChatVisitor
+    .findById(req.session.xchatty.visitor_id)
+    .then(function(chatVisitor) {
+      return chatVisitor
+        .update({
+          status: 'unserved'
+        });
+    })
+    .then(function() {
+      var json = {
+        success: true,
+        errors: []
+      };
+      req.session.xchatty = {};
+      res.send(req.query.callback + '(' + JSON.stringify(json) + ')');
+    })
+    .catch(function(err) {
+      next(err);
+    });
+};
+
+exports.submitEnquiry = function(req, res, next) {
+
+  return sequelize.transaction().then(function(t) {
+
+    var remoteAddress = req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] : req.connection.remoteAddress || req.ip;
+
+    var ua = uaParser.setUA(req.headers['user-agent']).getResult();
+    var languages = languageParser.parse(req.headers['accept-language']);
+    var body = {
+      fullname: req.query.data.Enquiry.fullname,
+      email: req.query.data.Enquiry.email,
+      remote_address: remoteAddress,
+      user_agent: req.headers['user-agent'],
+      visitor_language: req.headers['accept-language'],
+      browser: ua.browser.name + ' ' + ua.browser.version,
+      engine: ua.engine.name + ' ' + ua.engine.version,
+      os: ua.os.name + ' ' + ua.os.version,
+      device: ua.device.model ? ua.device.model + ' ' + ua.device.vendor + ' ' + ua.device.type : null,
+      referer: req.headers.referer,
+      origin: 'enquiry',
+      status: 'unserved',
+      ChatWebsiteId: parseInt(req.query.data.Enquiry.websiteId, 10),
+      ChatEnquiry: {
+        username: req.query.data.Enquiry.fullname,
+        email: req.query.data.Enquiry.email,
+        text: req.query.data.Enquiry.message,
+        ChatWebsiteId: parseInt(req.query.data.Enquiry.websiteId, 10)
+      }
+    };
+
+    if (req.query.data.Enquiry.offlineData1 && req.query.data.Enquiry.labelOfflineData1) {
+      body.offlineData1 = req.query.data.Enquiry.offlineData1;
+      body.labelOfflineData1 = req.query.data.Enquiry.labelOfflineData1;
+    }
+    if (req.query.data.Enquiry.offlineData2 && req.query.data.Enquiry.labelOfflineData2) {
+      body.offlineData2 = req.query.data.Enquiry.offlineData2;
+      body.labelOfflineData2 = req.query.data.Enquiry.labelOfflineData2;
+    }
+    if (req.query.data.Enquiry.offlineData3 && req.query.data.Enquiry.labelOfflineData3) {
+      body.offlineData3 = req.query.data.Enquiry.offlineData3;
+      body.labelOfflineData3 = req.query.data.Enquiry.labelOfflineData3;
+    }
+
+    return ChatVisitor
+      .create(body, {
+        transaction: t,
+        include: [{
+          model: ChatEnquiry
+        }]
+      })
+      .then(function() {
+        t.commit();
+        res.send(req.query.callback + '(' + JSON.stringify({
+          success: true,
+          errors: []
+        }) + ')');
+      })
+      .catch(function(err) {
+        t.rollback();
+        next(err);
+      });
+  });
+};
+
+exports.getTranscript = function(req, res, next) {
+  if (req.session.xchatty && req.session.xchatty.visitor_id && req.session.xchatty.room_id) {
+    ChatMessage
+      .findAll({
+        where: {
+          ChatRoomId: req.session.xchatty.room_id
+        },
+        include: [{
+          model: Agent,
+          attributes: ['id', 'fullname']
+        }, {
+          model: ChatVisitor,
+          attributes: ['id', 'fullname']
+        }]
+      })
+      .then(function(chatMessages) {
+        var chatMessageForCSV = [];
+        chatMessages.forEach(function(chatMessage) {
+          var chatMsg = {
+            name: chatMessage.UserId ? chatMessage.User.fullname : chatMessage.ChatVisitor.fullname,
+            text: chatMessage.body.replace(/(\r\n|\n|\r)/gm, " "),
+            date: chatMessage.createdAt
+          };
+          chatMessageForCSV.push(chatMsg);
+        });
+        var csv_transcript = csv(chatMessageForCSV);
+        res.type('text/csv').status(200).send(csv_transcript);
+      })
+      .catch(function(err) {
+        next(err);
+      });
+  }
+};
+
+// Get a single xchatty
+exports.show = function(req, res, next) {
+  return Xchatty
+    .findById(req.params.id)
+    .then(function(xchatty) {
+      if (!xchatty) {
+        return res.sendStatus(404);
+      }
+      return res.send(xchatty);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Creates a new xchatty in the DB.
+exports.create = function(req, res, next) {
+  return Xchatty
+    .create(req.body)
+    .then(function(xchatty) {
+      return res.status(201).send(xchatty);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Updates an existing xchatty in the DB.
+exports.update = function(req, res, next) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+
+  return Xchatty
+    .findById(req.params.id)
+    .then(function(xchatty) {
+      if (!xchatty) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(xchatty, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(xchatty);
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+// Deletes a xchatty from the DB.
+exports.destroy = function(req, res, next) {
+  return Xchatty
+    .findById(req.params.id)
+    .then(function(xchatty) {
+      if (!xchatty) {
+        return res.sendStatus(404);
+      }
+      xchatty.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+
+exports.getHtmlByWebsite = function(req, res, next) {
+  var template = xchatty_components.template;
+  return ChatWebsite
+    .findById(req.params.id, {
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(chatWebsite) {
+      var configWebsite = {
+        rootPath: Util.stripTrailingSlash(chatWebsite.remote) + '/api/xchatty/',
+        headerShape: chatWebsite.header_shape,
+        enquiryEnable: chatWebsite.enquiry_enable,
+        onlineFields: chatWebsite.Online,
+        renderedOnlineField: function() {
+          var renderedElement = '';
+          var element = this;
+          switch (this.component) {
+            case 'textInput':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label" for="%d">%s %s</label>', element.index, element.label, element.required ? '*' : '');
+              renderedElement += util.format('<input data-label="%s" type="text" id="%d" name="data[Discussion][data%d]" placeholder="%s" %s/>', element.label, element.index, element.index +
+                1,
+                element.placeholder, element.required ?
+                'required' :
+                '');
+              renderedElement += '</div>';
+              break;
+            case 'textArea':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label" for="%d">%s %s</label>', element.index, element.label, element.required ? '*' : '');
+              renderedElement += util.format('<textarea id="%d" data-label="%s" name="data[Discussion][data%d]" placeholder="%s" %s></textarea>', element.index, element.label, element.index +
+                1,
+                element.placeholder, element.required ?
+                'required' :
+                '');
+              renderedElement += '</div>';
+              break;
+            case 'checkbox':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label">%s</label>', element.label);
+              _.forEach(element.options, function(item) {
+                renderedElement += '<label style="display:block;">';
+                renderedElement += util.format('<input type="checkbox" name="data[Discussion][data%d]" value="%s"> %s<br>', element.index + 1, item, item);
+                renderedElement += '</label>';
+              });
+              renderedElement += '</div>';
+              break;
+            case 'radio':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label">%s</label>', element.label);
+              _.forEach(element.options, function(item) {
+                renderedElement += '<label style="display:block;">';
+                renderedElement += util.format('<input type="radio" name="data[Discussion][data%d]" value="%s"> %s<br>', element.index + 1, item, item);
+                renderedElement += '</label>';
+              });
+              renderedElement += '</div>';
+              break;
+            case 'select':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label" for="%d">%s</label>', element.index, element.label);
+              renderedElement += util.format('<select name="data[Discussion][data%d]">', element.index + 1);
+              _.forEach(element.options, function(item) {
+                renderedElement += util.format('<option value="%s">%s</option>', item, item);
+              });
+              renderedElement += '</select></div>';
+              break;
+          }
+          renderedElement += '<br>';
+          renderedElement += util.format('<input type="hidden" name="data[Discussion][labelData%d]" value="%s">', element.index + 1, element.label);
+          return renderedElement;
+        },
+        offlineFields: chatWebsite.Offline,
+        renderedOfflineField: function() {
+          var renderedElement = '';
+          var element = this;
+          switch (this.component) {
+            case 'textInput':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label" for="offline%d">%s %s</label>', element.index, element.label, element.required ? '*' : '');
+              renderedElement += util.format('<input data-label="%s" type="text" id="offline%d" name="data[Enquiry][offlineData%d]" placeholder="%s" %s/>', element.label, element.index,
+                element.index +
+                1,
+                element.placeholder, element.required ?
+                'required' :
+                '');
+              renderedElement += '</div>';
+              break;
+            case 'textArea':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label" for="offline%d">%s %s</label>', element.index, element.label, element.required ? '*' : '');
+              renderedElement += util.format('<textarea id="offline%d" data-label="%s" name="data[Enquiry][offlineData%d]" placeholder="%s" %s></textarea>', element.index, element.label,
+                element.index +
+                1,
+                element.placeholder, element.required ?
+                'required' :
+                '');
+              renderedElement += '</div>';
+              break;
+            case 'checkbox':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label">%s</label>', element.label);
+              _.forEach(element.options, function(item) {
+                renderedElement += '<label style="display:block;">';
+                renderedElement += util.format('<input type="checkbox" name="data[Enquiry][offlineData%d]" value="%s"> %s<br>', element.index + 1, item, item);
+                renderedElement += '</label>';
+              });
+              renderedElement += '</div>';
+              break;
+            case 'radio':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label">%s</label>', element.label);
+              _.forEach(element.options, function(item) {
+                renderedElement += '<label style="display:block;">';
+                renderedElement += util.format('<input type="radio" name="data[Enquiry][offlineData%d]" value="%s"> %s<br>', element.index + 1, item, item);
+                renderedElement += '</label>';
+              });
+              renderedElement += '</div>';
+              break;
+            case 'select':
+              renderedElement = '<div class="xc_div_group">';
+              renderedElement += util.format('<label class="xc_label">%s</label>', element.label);
+              renderedElement += util.format('<select name="data[Enquiry][offlineData%d]">', element.index + 1);
+              _.forEach(element.options, function(item) {
+                renderedElement += util.format('<option value="%s">%s</option>', item, item);
+              });
+              renderedElement += '</select></div>';
+              break;
+          }
+          renderedElement += '<br>';
+          renderedElement += util.format('<input type="hidden" name="data[Enquiry][labelOfflineData%d]" value="%s">', element.index + 1, element.label);
+          return renderedElement;
+        }
+      };
+      template = Mustache.render(template, configWebsite);
+      return res.status(200).jsonp(template);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+exports.getProactiveActionsByWebsite = function(req, res, next) {
+  return ChatWebsite
+    .findById(req.params.id, {
+      include: [{
+        model: ChatProactiveAction
+      }]
+    })
+    .then(function(chatWebsite) {
+      if (!chatWebsite) {
+        return res.status(400).jsonp({});
+      }
+      return res.status(200).jsonp(chatWebsite.ChatProactiveActions);
+    })
+    .catch(function(err) {
+      return next(err);
+    });
+};
+
+exports.setRating = function(req, res, next) {
+  return ChatRoom
+    .findById(req.query.data.Rating.room_id)
+    .then(function(chatRoom) {
+      if (!chatRoom) {
+        return res.status(400).jsonp({});
+      }
+      if (req.query.data.Rating.hasOwnProperty('rating') && req.query.data.Rating.hasOwnProperty('message')) {
+        return chatRoom
+          .update({
+            rating: req.query.data.Rating.rating,
+            ratingMessage: req.query.data.Rating.message
+          });
+      }
+      return;
+    })
+    .then(function() {
+      return res.status(200).jsonp({});
+    })
+    .catch(function(err) {
+      return res.status(400).jsonp(err);
+    });
+}
+
+exports.abandonCustomer = function(req, res, next) {
+  if (req.session.xchatty.hasOwnProperty('visitor_id') && req.session.xchatty.visitor_id > 0) {
+    return ReportChatSession
+      .find({
+        where: {
+          visitorid: req.session.xchatty.visitor_id
+        }
+      })
+      .then(function(reportChatSession) {
+        return reportChatSession
+          .update({
+            abandonAt: moment().format("YYYY-MM-DD HH:mm:ss")
+          });
+      })
+      .then(function() {
+        return res.status(200).jsonp({});
+      })
+      .catch(function(err) {
+        res.status(500).jsonp({});
+      });
+  }
+  return res.status(200).jsonp({});
+}
diff --git a/server/api/xchatty/xchatty.socket.js b/server/api/xchatty/xchatty.socket.js
index d62b659..b8ec557 100644
--- a/server/api/xchatty/xchatty.socket.js
+++ b/server/api/xchatty/xchatty.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var xchatty = require('../../models').Xchatty;
+
+exports.register = function(socket) {
+  // xchatty.afterCreate(function (doc) {
+  //   onSave(socket, doc);
+  // });
+  // xchatty.afterDestroy(function (doc) {
+  //   onRemove(socket, doc);
+  // });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('xchatty:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('xchatty:remove', doc);
+}
diff --git a/server/api/zendesk_account/index.js b/server/api/zendesk_account/index.js
index 68f7291..1d3a00a 100644
--- a/server/api/zendesk_account/index.js
+++ b/server/api/zendesk_account/index.js
@@ -1 +1,32 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var auth = require('../../auth/auth.service');
+var controller = require('./zendesk_account.controller');
+
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.get('/:id/check', auth.isAuthenticated(), controller.checkAccount);
+router.get('/:id/fields', auth.isAuthenticated(), controller.getFields);
+router.get('/:id/configurations', auth.isAuthenticated(), controller.getAccountConfigurations);
+router.get('/:id/configurations/:cid', auth.isAuthenticated(), controller.getConfiguration);
+router.post('/validate/:field', auth.isAuthenticated(), controller.zendeskValidation);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.post('/:id/configurations', auth.isAuthenticated(), controller.createConfiguration);
+router.post('/:id/configurations/:cid/:type', auth.isAuthenticated(), controller.createField);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+// router.put('/:id/configurations/:cid', auth.isAuthenticated(), controller.updateConfiguration);
+router.put('/:id/configurations/:cid/tags', auth.isAuthenticated(), controller.addConfigurationTags);
+router.put('/:id/configurations/:cid/fields/:fid', auth.isAuthenticated(), controller.updateField);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+// router.patch('/:id/configurations/:cid', auth.isAuthenticated(), controller.updateConfiguration);
+router.patch('/:id/configurations/:cid/fields/:fid', auth.isAuthenticated(), controller.updateField);
+router.delete('/', auth.isAuthenticated(), controller.bulkDestroy);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+router.delete('/:id/configurations', auth.isAuthenticated(), controller.bulkDestroyConfigurations);
+router.delete('/:id/configurations/:cid', auth.isAuthenticated(), controller.destroyConfiguration);
+router.delete('/:id/configurations/:cid/fields/:fid', auth.isAuthenticated(), controller.destroyField);
+
+module.exports = router;
diff --git a/server/api/zendesk_account/zendesk_account.controller.js b/server/api/zendesk_account/zendesk_account.controller.js
index 9436b5b..78030cd 100644
--- a/server/api/zendesk_account/zendesk_account.controller.js
+++ b/server/api/zendesk_account/zendesk_account.controller.js
@@ -1 +1,316 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Util = require('../../config/util');
+var zendesk = require('node-zendesk');
+
+var ZendeskAccount = require('../../models').ZendeskAccount;
+var ZendeskConfiguration = require('../../models').ZendeskConfiguration;
+var ZendeskField = require('../../models').ZendeskField;
+
+function handleError(res, statusCode) {
+  statusCode = statusCode || 500;
+  return function(err) {
+    console.error('Error:', err);
+    res.status(statusCode).send(err);
+  };
+}
+
+function responseWithResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function(entity) {
+    if (entity) {
+      res.status(statusCode).json(entity);
+    }
+  };
+}
+
+function responseWithoutResult(res, statusCode) {
+  statusCode = statusCode || 200;
+  return function() {
+    res.sendStatus(statusCode);
+  };
+}
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function saveUpdates(updates) {
+  return function(entity) {
+    return entity.updateAttributes(updates)
+      .then(function(updated) {
+        return updated;
+      });
+  };
+}
+
+function removeEntity(res) {
+  return function(entity) {
+    if (entity) {
+      return entity.destroy()
+        .then(function() {
+          res.status(204).end();
+        });
+    }
+  };
+}
+
+// Get list of zendesk_accounts
+exports.index = function(req, res, next) {
+  return ZendeskAccount
+    .scope('configurations')
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Get a single zendesk_account
+exports.show = function(req, res) {
+  return ZendeskAccount
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Creates a new zendesk_account in the DB.
+exports.create = function(req, res) {
+  return ZendeskAccount
+    .create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+// Updates an existing zendesk_account in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ZendeskAccount
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+// Deletes a zendesk_account from the DB.
+exports.destroy = function(req, res) {
+  return ZendeskAccount
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};
+
+exports.bulkDestroy = function(req, res) {
+  return ZendeskAccount
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(responseWithResult(res, 204))
+    .catch(handleError(res));
+};
+
+exports.zendeskValidation = function(req, res) {
+  var where = {};
+  where[req.params.field] = req.body.value;
+  return ZendeskAccount
+    .findAndCountAll({
+      where: where
+    })
+    .then(function(result) {
+      if (result.count) {
+        return res.status(200).send({
+          isValid: false,
+          value: req.body.value
+        });
+      }
+      return res.status(200).send({
+        isValid: true,
+        value: req.body.value
+      });
+    })
+    .catch(handleError(res));
+
+};
+
+exports.checkAccount = function(req, res) {
+  return ZendeskAccount
+    .findById(req.params.id)
+    .then(function(account) {
+      var accountZendesk = {
+        username: account.username,
+        remoteUri: account.remoteUri,
+        // debug: true
+      };
+      if (account.authType === 'password') {
+        accountZendesk.password = account.password;
+      } else if (account.authType === 'token') {
+        accountZendesk.token = account.token;
+        // accountZendesk.oauth = true;
+      }
+      // console.log(accountZendesk);
+      var client = zendesk.createClient(accountZendesk);
+      client.users.auth(function(err, res2, result) {
+        if (err) {
+          console.error(err);
+          return handleError(res, err);
+        }
+        // console.log(result);
+        if (!result.verified) {
+          return res.sendStatus(500);
+        }
+        return res.sendStatus(204);
+      });
+    })
+    .catch(handleError(res));
+};
+
+exports.getFields = function(req, res) {
+  return ZendeskAccount
+    .findById(req.params.id)
+    .then(function(account) {
+      var accountZendesk = {
+        username: account.username,
+        remoteUri: account.remoteUri,
+        // debug: true
+      };
+      if (account.authType === 'password') {
+        accountZendesk.password = account.password;
+      } else if (account.authType === 'token') {
+        accountZendesk.token = account.token;
+      }
+      var client = zendesk.createClient(accountZendesk);
+      client.ticketfields.list(function(err, res2, result) {
+        if (err) {
+          console.error(err);
+          return handleError(res, err);
+        }
+        return res.status(200).send(result);
+      });
+    })
+    .catch(handleError(res));
+}
+
+exports.getAccountConfigurations = function(req, res) {
+  return ZendeskConfiguration
+    .scope({
+      method: ['account', req.params.id]
+    })
+    .findAndCountAll(Util.getQuery(req.query))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+exports.getConfiguration = function(req, res) {
+
+  return ZendeskConfiguration
+    .scope({
+      method: ['account', req.params.id]
+    })
+    .findById(req.params.cid)
+    .then(handleEntityNotFound(res))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+}
+
+exports.destroyConfiguration = function(req, res) {
+  return ZendeskConfiguration
+    .scope({
+      method: ['account', req.params.id]
+    })
+    .findById(req.params.cid)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};
+
+exports.bulkDestroyConfigurations = function(req, res) {
+  return ZendeskConfiguration
+    .destroy({
+      where: {
+        id: req.query.ids
+      },
+      individualHooks: true
+    })
+    .then(responseWithResult(res, 204))
+    .catch(handleError(res));
+};
+
+exports.createConfiguration = function(req, res) {
+  req.body['AccountId'] = req.params.id;
+  return ZendeskConfiguration
+    .create(req.body)
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+exports.createField = function(req, res) {
+  return ZendeskConfiguration
+    .scope({
+      method: ['account', req.params.id]
+    })
+    .findById(req.params.cid)
+    .then(handleEntityNotFound(res))
+    .then(function(configuration) {
+      switch (req.params.type) {
+        case 'subject':
+          req.body['SubjectId'] = req.params.cid;
+          break;
+        case 'description':
+          req.body['DescriptionId'] = req.params.cid;
+          break;
+        case 'field':
+          req.body['FieldId'] = req.params.cid;
+          break;
+      }
+      return ZendeskField.create(req.body);
+    })
+    .then(responseWithResult(res, 201))
+    .catch(handleError(res));
+};
+
+exports.updateField = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return ZendeskField
+    .findById(req.params.fid)
+    .then(handleEntityNotFound(res))
+    .then(saveUpdates(req.body))
+    .then(responseWithResult(res))
+    .catch(handleError(res));
+};
+
+exports.destroyField = function(req, res) {
+  return ZendeskField
+    .findById(req.params.fid)
+    .then(handleEntityNotFound(res))
+    .then(removeEntity(res))
+    .catch(handleError(res));
+};
+
+exports.addConfigurationTags = function(req, res, next) {
+  return ZendeskConfiguration
+    .findById(req.params.cid)
+    .then(handleEntityNotFound(res))
+    .then(function(configuration) {
+      return configuration.setTags(req.body);
+    })
+    .then(responseWithoutResult(res, 200))
+    .catch(function(err) {
+      console.error(err);
+      return handleError(res, err);
+    });
+};
diff --git a/server/api/zendesk_account/zendesk_account.socket.js b/server/api/zendesk_account/zendesk_account.socket.js
index c9304df..96c8214 100644
--- a/server/api/zendesk_account/zendesk_account.socket.js
+++ b/server/api/zendesk_account/zendesk_account.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ZendeskAccount = require('../../models').ZendeskAccount;
+
+exports.register = function(socket) {
+  ZendeskAccount.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  ZendeskAccount.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('zendesk_account:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('zendesk_account:remove', doc);
+}
diff --git a/server/api/zendesk_account/zendesk_account.spec.js b/server/api/zendesk_account/zendesk_account.spec.js
index 1212bf8..68c8389 100644
--- a/server/api/zendesk_account/zendesk_account.spec.js
+++ b/server/api/zendesk_account/zendesk_account.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/zendesk/accounts', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/zendesk/accounts')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/zendesk_configuration/index.js b/server/api/zendesk_configuration/index.js
index 16572ef..e0bc33f 100644
--- a/server/api/zendesk_configuration/index.js
+++ b/server/api/zendesk_configuration/index.js
@@ -1 +1,17 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./zendesk_configuration.controller');
+var auth = require('../../auth/auth.service');
+var router = express.Router();
+
+router.get('/', auth.isAuthenticated(), controller.index);
+router.get('/:id', auth.isAuthenticated(), controller.show);
+router.post('/', auth.isAuthenticated(), controller.create);
+router.put('/:id', auth.isAuthenticated(), controller.update);
+router.post('/:id/tags', auth.isAuthenticated(), controller.addConfigurationTags);
+router.post('/:id/:type', auth.isAuthenticated(), controller.addConfigurationField);
+router.patch('/:id', auth.isAuthenticated(), controller.update);
+router.delete('/:id', auth.isAuthenticated(), controller.destroy);
+
+module.exports = router;
diff --git a/server/api/zendesk_configuration/zendesk_configuration.controller.js b/server/api/zendesk_configuration/zendesk_configuration.controller.js
index 4eac4e5..e299424 100644
--- a/server/api/zendesk_configuration/zendesk_configuration.controller.js
+++ b/server/api/zendesk_configuration/zendesk_configuration.controller.js
@@ -1 +1,181 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ZendeskConfiguration = require('../../models').ZendeskConfiguration;
+var ZendeskField = require('../../models').ZendeskField;
+
+// Get list of zendesk_configurations
+exports.index = function(req, res) {
+  ZendeskConfiguration
+    .findAll({
+      where: req.query
+    })
+    .then(function(zendesk_configurations) {
+      return res.status(200).send(zendesk_configurations);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single zendesk_configuration
+exports.show = function(req, res) {
+  ZendeskConfiguration
+    .find({
+      where: {
+        id: req.params.id
+      },
+      include: [{
+        all: true
+      }]
+    })
+    .then(function(zendesk_configuration) {
+      if (!zendesk_configuration) {
+        return res.sendStatus(404);
+      }
+      return res.send(zendesk_configuration);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new zendesk_configuration in the DB.
+exports.create = function(req, res) {
+  ZendeskConfiguration
+    .create(req.body)
+    .then(function(zendesk_configuration) {
+      return res.status(201).send(zendesk_configuration);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing zendesk_configuration in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  ZendeskConfiguration
+    .findById(req.params.id)
+    .then(function(zendesk_configuration) {
+      if (!zendesk_configuration) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(zendesk_configuration, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(zendesk_configuration);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a zendesk_configuration from the DB.
+exports.destroy = function(req, res) {
+  ZendeskConfiguration
+    .findById(req.params.id)
+    .then(function(zendesk_configuration) {
+      if (!zendesk_configuration) {
+        return res.sendStatus(404);
+      }
+      zendesk_configuration.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+exports.addConfigurationField = function(req, res, next) {
+  // console.log(req.params);
+  ZendeskConfiguration
+    .findById(req.params.id)
+    .then(function(configuration) {
+      if (!configuration) {
+        return res.sendStatus(500);
+      }
+      ZendeskField
+        .create(req.body)
+        .then(function(field) {
+          switch (req.params.type) {
+            case 'subject':
+              configuration.addSubject(field)
+                .then(function() {
+                  return res.status(200).send(field);
+                })
+                .catch(function(err) {
+                  return handleError(res, err);
+                });
+              break;
+            case 'description':
+              configuration.addDescription(field)
+                .then(function() {
+                  return res.status(200).send(field);
+                })
+                .catch(function(err) {
+                  return handleError(res, err);
+                });
+              break;
+            case 'field':
+              configuration.addField(field)
+                .then(function() {
+                  return res.status(200).send(field);
+                })
+                .catch(function(err) {
+                  return handleError(res, err);
+                });
+              break;
+          }
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+          // return next(err);
+        });
+    })
+    .catch(function(err) {
+      // return next(err);
+      return handleError(res, err);
+    });
+};
+
+exports.addConfigurationTags = function(req, res, next) {
+  ZendeskConfiguration
+    .findById(req.params.id)
+    .then(handleEntityNotFound(res))
+    .then(function(configuration) {
+      return configuration.setTags(req.body);
+    })
+    .then(function(data) {
+      return res.sendStatus(200);
+    })
+    .catch(function(err) {
+      console.error(err);
+      return handleError(res, err);
+    });
+};
+
+function handleEntityNotFound(res) {
+  return function(entity) {
+    if (!entity) {
+      res.status(404).end();
+      return null;
+    }
+    return entity;
+  };
+}
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/zendesk_configuration/zendesk_configuration.socket.js b/server/api/zendesk_configuration/zendesk_configuration.socket.js
index 0aa8993..d9a56f1 100644
--- a/server/api/zendesk_configuration/zendesk_configuration.socket.js
+++ b/server/api/zendesk_configuration/zendesk_configuration.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ZendeskConfiguration = require('../../models').ZendeskConfiguration;
+
+exports.register = function(socket) {
+  ZendeskConfiguration.afterCreate(function (doc) {
+    onSave(socket, doc);
+  });
+  ZendeskConfiguration.afterDestroy(function (doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('zendesk_configuration:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('zendesk_configuration:remove', doc);
+}
diff --git a/server/api/zendesk_configuration/zendesk_configuration.spec.js b/server/api/zendesk_configuration/zendesk_configuration.spec.js
index 1cb418e..3589f5d 100644
--- a/server/api/zendesk_configuration/zendesk_configuration.spec.js
+++ b/server/api/zendesk_configuration/zendesk_configuration.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/zendesk/configurations', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/zendesk/configurations')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
\ No newline at end of file
diff --git a/server/api/zendesk_field/index.js b/server/api/zendesk_field/index.js
index e27e759..76f5c48 100644
--- a/server/api/zendesk_field/index.js
+++ b/server/api/zendesk_field/index.js
@@ -1 +1,15 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var controller = require('./zendesk_field.controller');
+
+var router = express.Router();
+
+router.get('/', controller.index);
+router.get('/:id', controller.show);
+router.post('/', controller.create);
+router.put('/:id', controller.update);
+router.patch('/:id', controller.update);
+router.delete('/:id', controller.destroy);
+
+module.exports = router;
diff --git a/server/api/zendesk_field/zendesk_field.controller.js b/server/api/zendesk_field/zendesk_field.controller.js
index 7bcd9bf..48d0c38 100644
--- a/server/api/zendesk_field/zendesk_field.controller.js
+++ b/server/api/zendesk_field/zendesk_field.controller.js
@@ -1 +1,93 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var ZendeskField = require('../../models').ZendeskField;
+
+// Get list of zendesk_fields
+exports.index = function(req, res) {
+  ZendeskField
+    .findAll()
+    .then(function(zendesk_fields) {
+      return res.status(200).send(zendesk_fields);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Get a single zendesk_field
+exports.show = function(req, res) {
+  ZendeskField
+    .findById(req.params.id)
+    .then(function(zendesk_field) {
+      if (!zendesk_field) {
+        return res.sendStatus(404);
+      }
+      return res.send(zendesk_field);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Creates a new zendesk_field in the DB.
+exports.create = function(req, res) {
+  ZendeskField
+    .create(req.body)
+    .then(function(zendesk_field) {
+      return res.status(201).send(zendesk_field);
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Updates an existing zendesk_field in the DB.
+exports.update = function(req, res) {
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  ZendeskField
+    .findById(req.params.id)
+    .then(function(zendesk_field) {
+      if (!zendesk_field) {
+        return res.sendStatus(404);
+      }
+      var updated = _.merge(zendesk_field, req.body);
+      updated.save()
+        .then(function() {
+          return res.status(200).send(zendesk_field);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+// Deletes a zendesk_field from the DB.
+exports.destroy = function(req, res) {
+  ZendeskField
+    .findById(req.params.id)
+    .then(function(zendesk_field) {
+      if (!zendesk_field) {
+        return res.sendStatus(404);
+      }
+      zendesk_field.destroy()
+        .then(function() {
+          return res.sendStatus(204);
+        })
+        .catch(function(err) {
+          return handleError(res, err);
+        });
+    })
+    .catch(function(err) {
+      return handleError(res, err);
+    });
+};
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}
diff --git a/server/api/zendesk_field/zendesk_field.socket.js b/server/api/zendesk_field/zendesk_field.socket.js
index 784cc09..8a5419b 100644
--- a/server/api/zendesk_field/zendesk_field.socket.js
+++ b/server/api/zendesk_field/zendesk_field.socket.js
@@ -1 +1,24 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var ZendeskField = require('../../models').ZendeskField;
+
+exports.register = function(socket) {
+  ZendeskField.afterCreate(function(doc) {
+    onSave(socket, doc);
+  });
+  ZendeskField.afterDestroy(function(doc) {
+    onRemove(socket, doc);
+  });
+}
+
+function onSave(socket, doc, cb) {
+  socket.emit('zendesk_field:save', doc);
+}
+
+function onRemove(socket, doc, cb) {
+  socket.emit('zendesk_field:remove', doc);
+}
diff --git a/server/api/zendesk_field/zendesk_field.spec.js b/server/api/zendesk_field/zendesk_field.spec.js
index 45a05ae..1527fe8 100644
--- a/server/api/zendesk_field/zendesk_field.spec.js
+++ b/server/api/zendesk_field/zendesk_field.spec.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var should = require('should');
+var app = require('../../app');
+var request = require('supertest');
+
+describe('GET /api/zendesk/fields', function() {
+
+  it('should respond with JSON array', function(done) {
+    request(app)
+      .get('/api/zendesk/fields')
+      .expect(200)
+      .expect('Content-Type', /json/)
+      .end(function(err, res) {
+        if (err) return done(err);
+        res.body.should.be.instanceof(Array);
+        done();
+      });
+  });
+});
diff --git a/server/app.js b/server/app.js
index c9acc5c..5b16a4a 100644
--- a/server/app.js
+++ b/server/app.js
@@ -1 +1,112 @@
\ No newline at end of file
+/**
+ * Main application file
+ */
+
+'use strict';
+
+// Set default node environment to development
+process.env.NODE_ENV = process.env.NODE_ENV || 'development';
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
+
+process.on('uncaughtException', function(err) {
+  if (typeof err === 'object') {
+    if (err.message) {
+      console.log('\nMessage: ' + err.message)
+    }
+    if (err.stack) {
+      console.log('\nStacktrace:')
+      console.log('====================')
+      console.log(err.stack);
+    }
+  } else {
+    console.log('dumpError :: argument is not an object');
+  }
+});
+
+var os = require('os');
+var http = require('http');
+var util = require('util');
+var crypto = require('crypto');
+var cronjob = require('cron').CronJob;
+var shelljs = require('shelljs/global');
+
+var express = require('express');
+var models = require('./models');
+var config = require('./config/environment');
+
+var Settings = models.Settings;
+
+// Setup server
+var app = express();
+var server = require('http').createServer(app);
+var io = require('socket.io')(server, {
+  serveClient: (config.env === 'production') ? false : true,
+  path: '/socket.io-client'
+});
+
+var ami = require('asterisk-manager')(config.asterisk.port,
+  config.asterisk.host,
+  config.asterisk.username,
+  config.asterisk.password,
+  true);
+ami.keepConnected();
+
+// Initialization Kue
+var kue = require('kue');
+kue.app.listen(3001);
+
+var _setting;
+var _license;
+
+require('./config/express')(app);
+require('./routes')(app);
+
+models
+  .sequelize
+  .sync()
+  .then(function() {
+
+    require('./routes')(app);
+    // Populate DB Develop with sample data
+    if (config.seedDB) {
+      require('./config/seed');
+    }
+    // Populate DB Production with sample data
+    if (config.seedPDB) {
+      require('./config/seedp');
+    }
+
+    // Start Cally Square Projects
+    require('./config/socketio')(io);
+    require('./config/routing/mail')();
+    require('./config/routing/chat')();
+    require('./config/automations/mail')();
+    require('./config/automations/fax')();
+    require('./config/triggers')(ami);
+    require('./config/imap')();
+    require('./config/smtp')();
+    require('./config/tools')();
+    require('./config/ami')(ami);
+    require('./config/agi')();
+    require('./config/license')();
+
+    // Start server
+    server.listen(config.port, config.ip, function() {
+      console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
+    });
+  })
+  .then(function() {
+    // Connect to database history
+    return models
+      .sequelize_history
+      .sync();
+  })
+  .then(function() {
+    require('./config/history')(cronjob);
+  })
+  .catch(function(err) {
+    console.error(err);
+  });
+
+// Expose app
+exports = module.exports = app;
diff --git a/server/app.json b/server/app.json
index 28c7e36..f4e7fbe 100644
--- a/server/app.json
+++ b/server/app.json
@@ -8,6 +8,7 @@
     "cwd": "/var/opt/motion/server",
     "error_file": "/var/log/xcally/motion-err.log",
     "out_file": "/var/log/xcally/motion-out.log",
-    "log_date_format": "YYYY-MM-DD HH:mm Z"
+    "log_date_format": "YYYY-MM-DD HH:mm Z",
+    "watch": false
   }]
 }
diff --git a/server/auth/auth.service.js b/server/auth/auth.service.js
index 1db738d..a606070 100644
--- a/server/auth/auth.service.js
+++ b/server/auth/auth.service.js
@@ -1 +1,95 @@
\ No newline at end of file
+'use strict';
+
+//var mongoose = require('mongoose');
+var passport = require('passport');
+var config = require('../config/environment');
+var jwt = require('jsonwebtoken');
+var expressJwt = require('express-jwt');
+var compose = require('composable-middleware');
+// var User = require('../api/user/user.model');
+var User = require('../models').User;
+var Service = require('../models').Service;
+var validateJwt = expressJwt({
+  secret: config.session.secret
+});
+
+/**
+ * Attaches the user object to the request if authenticated
+ * Otherwise returns 403
+ */
+function isAuthenticated() {
+  return compose()
+    // Validate jwt
+    .use(function(req, res, next) {
+      // allow access_token to be passed through query parameter as well
+      if (req.query && req.query.hasOwnProperty('access_token')) {
+        req.headers.authorization = 'Bearer ' + req.query.access_token;
+      }
+      validateJwt(req, res, next);
+    })
+    // Attach user to request
+    .use(function(req, res, next) {
+      User
+        .findById(req.user.id, {
+          include: [{
+            model: Service
+          }]
+        })
+        .then(function(user) {
+          if (!user) return res.send(401);
+          req.user = user;
+          return next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the user role meets the minimum requirements of the route
+ */
+function hasRole(roleRequired) {
+  if (!roleRequired) throw new Error('Required role needs to be set');
+
+  return compose()
+    .use(isAuthenticated())
+    .use(function meetsRequirements(req, res, next) {
+      if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) {
+        return next();
+      } else {
+        return res.send(403);
+      }
+    });
+}
+
+/**
+ * Returns a jwt token signed by the app secret
+ */
+function signToken(id, role, remember) {
+  var expiresIn = 60 * 24 * 365 * 5; //If remember, it expires in 5 years
+  if (!remember)
+    expiresIn = "1d"; //Expire in one day
+  return jwt.sign({
+    id: id
+  }, config.session.secret, {
+    expiresIn: expiresIn
+  });
+}
+
+/**
+ * Set token cookie directly for oAuth strategies
+ */
+function setTokenCookie(req, res) {
+  if (!req.user) return res.json(404, {
+    message: 'Something went wrong, please try again.'
+  });
+  var token = signToken(req.user.id, req.user.role);
+  res.cookie('token', JSON.stringify(token));
+  res.redirect('/');
+}
+
+exports.isAuthenticated = isAuthenticated;
+exports.hasRole = hasRole;
+exports.signToken = signToken;
+exports.setTokenCookie = setTokenCookie;
diff --git a/server/auth/facebook/index.js b/server/auth/facebook/index.js
index cd9fbb7..4a6f878 100644
--- a/server/auth/facebook/index.js
+++ b/server/auth/facebook/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var auth = require('../auth.service');
+
+var router = express.Router();
+
+router
+  .get('/', passport.authenticate('facebook', {
+    scope: ['email', 'user_about_me'],
+    failureRedirect: '/signup',
+    session: false
+  }))
+
+  .get('/callback', passport.authenticate('facebook', {
+    failureRedirect: '/signup',
+    session: false
+  }), auth.setTokenCookie);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/auth/facebook/passport.js b/server/auth/facebook/passport.js
index 4708837..90ae489 100644
--- a/server/auth/facebook/passport.js
+++ b/server/auth/facebook/passport.js
@@ -1 +1,37 @@
\ No newline at end of file
+var passport = require('passport');
+var FacebookStrategy = require('passport-facebook').Strategy;
+
+exports.setup = function (User, config) {
+  passport.use(new FacebookStrategy({
+      clientID: config.facebook.clientID,
+      clientSecret: config.facebook.clientSecret,
+      callbackURL: config.facebook.callbackURL
+    },
+    function(accessToken, refreshToken, profile, done) {
+      User.findOne({
+        'facebook.id': profile.id
+      },
+      function(err, user) {
+        if (err) {
+          return done(err);
+        }
+        if (!user) {
+          user = new User({
+            name: profile.displayName,
+            email: profile.emails[0].value,
+            role: 'user',
+            username: profile.username,
+            provider: 'facebook',
+            facebook: profile._json
+          });
+          user.save(function(err) {
+            if (err) done(err);
+            return done(err, user);
+          });
+        } else {
+          return done(err, user);
+        }
+      })
+    }
+  ));
+};
\ No newline at end of file
diff --git a/server/auth/google/index.js b/server/auth/google/index.js
index 760f642..9b1ce39 100644
--- a/server/auth/google/index.js
+++ b/server/auth/google/index.js
@@ -1 +1,24 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var auth = require('../auth.service');
+
+var router = express.Router();
+
+router
+  .get('/', passport.authenticate('google', {
+    failureRedirect: '/signup',
+    scope: [
+      'https://www.googleapis.com/auth/userinfo.profile',
+      'https://www.googleapis.com/auth/userinfo.email'
+    ],
+    session: false
+  }))
+
+  .get('/callback', passport.authenticate('google', {
+    failureRedirect: '/signup',
+    session: false
+  }), auth.setTokenCookie);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/auth/google/passport.js b/server/auth/google/passport.js
index 0ec5987..d304e8a 100644
--- a/server/auth/google/passport.js
+++ b/server/auth/google/passport.js
@@ -1 +1,33 @@
\ No newline at end of file
+var passport = require('passport');
+var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
+
+exports.setup = function (User, config) {
+  passport.use(new GoogleStrategy({
+      clientID: config.google.clientID,
+      clientSecret: config.google.clientSecret,
+      callbackURL: config.google.callbackURL
+    },
+    function(accessToken, refreshToken, profile, done) {
+      User.findOne({
+        'google.id': profile.id
+      }, function(err, user) {
+        if (!user) {
+          user = new User({
+            name: profile.displayName,
+            email: profile.emails[0].value,
+            role: 'user',
+            username: profile.username,
+            provider: 'google',
+            google: profile._json
+          });
+          user.save(function(err) {
+            if (err) done(err);
+            return done(err, user);
+          });
+        } else {
+          return done(err, user);
+        }
+      });
+    }
+  ));
+};
diff --git a/server/auth/index.js b/server/auth/index.js
index 4a38c78..a034241 100644
--- a/server/auth/index.js
+++ b/server/auth/index.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+var express   = require('express');
+var passport  = require('passport');
+var config    = require('../config/environment');
+var User      = require('../models').User;
+
+
+// Passport Configuration
+require('./local/passport').setup(User, config);
+require('./facebook/passport').setup(User, config);
+require('./google/passport').setup(User, config);
+require('./twitter/passport').setup(User, config);
+
+var router = express.Router();
+
+router.use('/local', require('./local'));
+router.use('/facebook', require('./facebook'));
+router.use('/twitter', require('./twitter'));
+router.use('/google', require('./google'));
+
+module.exports = router;
diff --git a/server/auth/local/index.js b/server/auth/local/index.js
index 73c72f4..d60d5e0 100644
--- a/server/auth/local/index.js
+++ b/server/auth/local/index.js
@@ -1 +1,63 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var async = require('async');
+
+var auth = require('../auth.service');
+var User = require('../../models').User;
+var Action = require('../../models').Action;
+var ReportMember = require('../../models').ReportMember;
+var license = require('../../config/license/license.service');
+
+var channels = ['voice', 'chat', 'mail', 'fax'];
+var router = express.Router();
+
+router.post('/', function(req, res, next) {
+  passport.authenticate('local-login', function(err, user, info) {
+    var error = err || info;
+    if (error) return res.status(401).json(error);
+    if (!user) return res.status(404).json({
+      message: 'Something went wrong, please try again.'
+    });
+
+    return Action
+      .create({
+        name: 'login',
+        data1: user.id,
+        data2: user.name,
+        UserId: user.id
+      })
+      .then(function(action) {
+        return res.json({
+          role: user.role,
+          token: auth.signToken(user.id, user.role, req.body.remember || false),
+          userId: user.id
+        });
+      })
+      .catch(function(err) {
+        return next(err);
+      });
+
+  })(req, res, next)
+});
+
+router
+  .get('/logout', auth.isAuthenticated(), function(req, res, next) {
+    return Action
+      .create({
+        name: 'logout',
+        data1: req.user.id,
+        data2: req.user.name,
+        UserId: req.user.id
+      })
+      .then(function() {
+        req.logout();
+        res.redirect('/');
+      })
+      .catch(function(err) {
+        return next(err);
+      });
+  });
+
+module.exports = router;
diff --git a/server/auth/local/local.ami.js b/server/auth/local/local.ami.js
index 7ad3607..bfb533d 100644
--- a/server/auth/local/local.ami.js
+++ b/server/auth/local/local.ami.js
@@ -1 +1,51 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var util = require('util');
+var amiController = require('../../api/ami/ami.ami');
+
+exports.logout = function(userid, username, queuename, callback) {
+  var body = {
+    action: 'QueuePause',
+    queue: queuename,
+    interface: util.format('SIP/%s', username),
+    paused: false
+  };
+  body.data = JSON.stringify(body);
+  body.UserId = userid;
+
+  amiController.action(body, function(err, res) {
+    if (err) {
+      console.log(err);
+    }
+    var body = {
+      action: 'QueueRemove',
+      queue: queuename,
+      interface: util.format('SIP/%s', username)
+    };
+    body.data = JSON.stringify(body);
+    body.UserId = userid;
+
+    amiController.action(body, callback);
+  });
+}
+
+exports.login = function(userid, username, queuename, callback) {
+
+  var body = {
+    action: 'QueueAdd',
+    queue: queuename,
+    interface: util.format('SIP/%s', username),
+    penalty: 0,
+    paused: false,
+    membername: username
+  };
+
+  body.data = JSON.stringify(body);
+  body.UserId = userid;
+
+  amiController.action(body, callback);
+}
diff --git a/server/auth/local/passport.js b/server/auth/local/passport.js
index b74e42d..e602ae6 100644
--- a/server/auth/local/passport.js
+++ b/server/auth/local/passport.js
@@ -1 +1,93 @@
\ No newline at end of file
+var passport = require('passport');
+var LocalStrategy = require('passport-local').Strategy;
+var Settings = require('../../models').Settings;
+var license = require('../../config/license/license.service');
+
+exports.setup = function(User, config) {
+
+  passport.use('local-login', new LocalStrategy({
+      usernameField: 'name',
+      passwordField: 'password' // this is the virtual field on the model
+    },
+    function(name, password, done) {
+      var _user;
+      var _settings;
+      var _license;
+
+      return User.findOne({
+          where: {
+            name: name
+          }
+        })
+        .then(function(user) {
+          _user = user;
+
+          if (!_user || !user.authenticate(password)) {
+            throw new Error({
+              message: 'Wrong credentials.'
+            });
+          }
+
+          return _user;
+        })
+        .then(license.isValidAgent())
+        .then(function(valid) {
+          if (valid) {
+            return done(null, _user);
+          } else {
+            return done(null, false, {
+              message: 'Maximum login limit has been reached.'
+            });
+
+          }
+        })
+        .catch(function(err) {
+          return done(null, false, err);
+        });
+    }
+  ));
+
+  // passport.use('local-signup', new LocalStrategy({
+  //     usernameField: 'name',
+  //     passwordField: 'password',
+  //     passReqToCallback: true
+  //   },
+  //   function(req, name, password, done) {
+  //
+  //     if (!req.user) {
+  //       User
+  //         .findOne({
+  //           where: {
+  //             name: name
+  //           }
+  //         })
+  //         .then(function(user) {
+  //           if (user) {
+  //             return done(null, false, {
+  //               message: 'That email is already taken.'
+  //             });
+  //           } else {
+  //             var newUser = User.build(req.body);
+  //             newUser.provider = 'local';
+  //             newUser.online = true;
+  //             newUser
+  //               .save()
+  //               .then(function() {
+  //                 return done(null, newUser);
+  //               })
+  //               .catch(function(err) {
+  //                 return done(err);
+  //               });
+  //           }
+  //         })
+  //         .catch(function(err) {
+  //           return done(err);
+  //         });
+  //     } else {
+  //       // user is logged in and already has a local account. Ignore signup. (You should log out before trying to create a new account, user!)
+  //       return done(null, req.user);
+  //     }
+  //   }
+  // ));
+
+};
diff --git a/server/auth/twitter/index.js b/server/auth/twitter/index.js
index 5614ddf..8360247 100644
--- a/server/auth/twitter/index.js
+++ b/server/auth/twitter/index.js
@@ -1 +1,20 @@
\ No newline at end of file
+'use strict';
+
+var express = require('express');
+var passport = require('passport');
+var auth = require('../auth.service');
+
+var router = express.Router();
+
+router
+  .get('/', passport.authenticate('twitter', {
+    failureRedirect: '/signup',
+    session: false
+  }))
+
+  .get('/callback', passport.authenticate('twitter', {
+    failureRedirect: '/signup',
+    session: false
+  }), auth.setTokenCookie);
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/auth/twitter/passport.js b/server/auth/twitter/passport.js
index 2bb0425..a2eb4a5 100644
--- a/server/auth/twitter/passport.js
+++ b/server/auth/twitter/passport.js
@@ -1 +1,35 @@
\ No newline at end of file
+exports.setup = function (User, config) {
+  var passport = require('passport');
+  var TwitterStrategy = require('passport-twitter').Strategy;
+
+  passport.use(new TwitterStrategy({
+    consumerKey: config.twitter.clientID,
+    consumerSecret: config.twitter.clientSecret,
+    callbackURL: config.twitter.callbackURL
+  },
+  function(token, tokenSecret, profile, done) {
+    User.findOne({
+      'twitter.id_str': profile.id
+    }, function(err, user) {
+      if (err) {
+        return done(err);
+      }
+      if (!user) {
+        user = new User({
+          name: profile.displayName,
+          username: profile.username,
+          role: 'user',
+          provider: 'twitter',
+          twitter: profile._json
+        });
+        user.save(function(err) {
+          if (err) return done(err);
+          return done(err, user);
+        });
+      } else {
+        return done(err, user);
+      }
+    });
+    }
+  ));
+};
\ No newline at end of file
diff --git a/server/config/agi.js b/server/config/agi.js
index 0f4d55e..6ab451e 100644
--- a/server/config/agi.js
+++ b/server/config/agi.js
@@ -1 +1,1216 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+
+var agi = require('agi');
+var util = require('util');
+var moment = require('moment');
+var xml = require('xml2js');
+var wait = require('wait.for');
+var sr = require('simple-random');
+var path = require('path');
+var config = require('../config/environment');
+var sh = require('shelljs');
+
+var db = require("odbc")();
+
+var SquareProject = require('../models').SquareProject;
+var Variable = require('../models').Variable;
+var SquareOdbc = require('../models').SquareOdbc;
+var Settings = require('../models').Settings;
+var User = require('../models').User;
+var VoiceQueue = require('../models').VoiceQueue;
+var Trunk = require('../models').Trunk;
+var Interval = require('../models').Interval;
+var Sound = require('../models').Sound;
+var ReportSquare = require('../models').ReportSquare;
+var ReportSquareDetail = require('../models').ReportSquareDetail;
+// var TempTable = require('../models').TempTable;
+// var FakeTable = require('../models').FakeTable;
+var users, trunks, variables, intervals, projects, sounds, queues, dbConnections, generalUniqueId;
+var agiPort;
+var weekDaysCollection = {
+  mon: 1,
+  tue: 2,
+  wed: 3,
+  thu: 4,
+  fri: 5,
+  sat: 6,
+  sun: 7
+};
+var monthsCollection = {
+  jan: 1,
+  feb: 2,
+  mar: 3,
+  apr: 4,
+  may: 5,
+  jun: 6,
+  jul: 7,
+  aug: 8,
+  sep: 9,
+  oct: 10,
+  nov: 11,
+  dec: 12
+};
+var methods = {};
+
+methods.answer = function(context, vertex, callback) {
+  console.log('--ANSWER BLOCK--');
+  console.log('Answering the call...');
+  context.send('ANSWER\n', function(err, res) {
+    callback(err, res);
+  });
+};
+
+methods.custom_app = function(context, vertex, callback) {
+  console.log('--CUSTOM APP BLOCK--');
+  console.log('Executing custom app "' + vertex.application + '"...');
+  context.exec(vertex.application, vertex.options, function(err, res) {
+    if (callback) {
+      callback(err, res);
+    }
+  });
+};
+
+
+methods.dial = function(context, vertex, callback) {
+  console.log('--INTERNAL_DIAL BLOCK--');
+  if (!users) {
+    getUsers(false);
+  }
+  var sip = _.find(users, {
+    id: parseInt(vertex.sip_id)
+  });
+  console.log('Calling ' + util.format('SIP/%s', sip.name) + '...');
+  var parameters = [util.format('SIP/%s', sip.name), vertex.timeout, vertex.opts, vertex.url];
+  context.exec('DIAL', parameters.join(','),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.ext_dial = function(context, vertex, callback) {
+  console.log('--EXTERNAL_DIAL BLOCK--');
+  if (!trunks) {
+    getTrunks(false);
+  }
+  var trunk = _.find(trunks, {
+    id: parseInt(vertex.trunk_id)
+  });
+  console.log('Calling ' + util.format('SIP/%s', trunk.name) + '...');
+  var parameters = [util.format('SIP/%s@%s', vertex.phone, trunk.name), vertex.timeout, vertex.opts, vertex.url];
+  context.exec('DIAL', parameters.join(','),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.queue = function(context, vertex, callback) {
+  console.log('--QUEUE BLOCK--');
+  if (!queues) {
+    getQueues(false);
+  }
+  var queue = _.find(queues, {
+    name: vertex.queue_id
+  });
+  var announceOverride = getFilePath(vertex.file_id);
+  var parameters = [queue.name, vertex.opts, vertex.url, announceOverride, vertex.timeout, vertex.agi, vertex.macro, vertex.gosub, '', vertex.position];
+  console.log('Joining ' + queue.name + ' queue...');
+  context.exec('QUEUE', parameters.join(','), function(err, res) {
+    callback(err, res);
+  });
+};
+
+methods.voicemail = function(context, vertex, callback) {
+  console.log('--VOICEMAIL BLOCK--');
+  var parameters = [vertex.mailbox, vertex.opts];
+  var squareDetail = {
+    uniqueid: context.uniqueid,
+    node: vertex.label,
+    application: vertex.tag,
+    data: parameters.join(',') || null
+  }
+  ReportSquareDetail
+    .create(squareDetail)
+    .catch(function(err) {
+      console.log('Error saving ' + vertex.tag + ':' + vertex.label + ' block log', err);
+    });
+  console.log('Starting voicemail recording...');
+  context.exec('VOICEMAIL', parameters.join(','), function(err, res) {
+    callback(err, res);
+  });
+};
+
+// methods.callback = function(context, vertex, callback) {
+//   context.send('ANSWER\n', function(err, res) {
+//     callback(err, res);
+//   });
+// };
+
+methods.math = function(context, vertex, callback) {
+  console.log('--MATH BLOCK--');
+  console.log('Calculating expression...');
+  var result = eval(vertex.operation);
+  console.log('Saving result in variable...');
+  var varName = getVariableName(vertex.variable_id);
+  if (_.isArray(result)) {
+    result.forEach(function(elem, index) {
+      wait.for(setVariable, context, varName + '[' + index + ']', elem);
+    });
+  } else {
+    wait.for(setVariable, context, varName, result);
+  }
+}
+
+// methods.background = function(context, vertex, callback) {
+//   var parameters = [getFilePath(vertex.file_id), vertex.opts];
+//   context.exec('BACKGROUND', parameters.join('|'), function(err, res) {
+//     callback(err, res);
+//   });
+// };
+
+methods.playback = function(context, vertex, callback) {
+  console.log('--PLAYBACK BLOCK--');
+  var audiofile = getFilePath(vertex.file_id);
+  // var audiofile = '/var/www/html/files/sounds/d0269ff87187df665ece75538e4cddfd';
+  var parameters = [audiofile, vertex.opts];
+  console.log('Executing playback...');
+  context.exec('PLAYBACK', parameters.join(','), function(err, res) {
+    callback(err, res);
+  });
+};
+
+methods.menu = function(context, vertex, callback) {
+  console.log('--MENU BLOCK--');
+  var res = {};
+  console.log('There are ' + vertex.retry + ' retries');
+  if (vertex.retry > 0) {
+    vertex.retry--;
+    var announce = getFilePath(vertex.file_id);
+    // var announce = '/var/www/html/files/sounds/b19642d2f71e9cfffbd783fcc79c7415';
+    // console.log(announce);
+    console.log('Announcing and waiting for user entry...');
+    context.send(util.format('GET DATA %s %s %s\n', announce, parseInt(vertex.response) * 1000, vertex.digit),
+      function(err, res) {
+        var squareDetail = {
+          uniqueid: context.uniqueid,
+          node: vertex.label,
+          application: vertex.tag,
+          data: res.result || null
+        }
+        ReportSquareDetail
+          .create(squareDetail)
+          .catch(function(err) {
+            console.log('Error saving ' + vertex.tag + ':' + vertex.label + ' block log', err);
+          });
+        if (res.result !== ' (timeout)') {
+          if (vertex.variable_id) {
+            console.log('Saving user entry in a variable...');
+            var varName = getVariableName(vertex.variable_id);
+            setVariable(context, varName, res.result, function() {
+              res.data = res.result;
+              callback(null, res);
+            });
+            // setVariable(context, variableId, res.result);
+          }
+
+        } else {
+          console.log('Menu timeout!');
+          res = {
+            code: 200,
+            result: '0',
+            data: 't'
+          };
+          callback(null, res);
+        }
+      });
+
+  } else {
+    console.log('Run out of retries!');
+    res = {
+      code: 200,
+      result: '0',
+      data: '-'
+    };
+    callback(null, res);
+  }
+};
+
+methods.saynumber = function(context, vertex, callback) {
+  console.log('--SAYNUMBER BLOCK--');
+  console.log('Saying number...');
+  context.send(util.format('SAY NUMBER %s "%s"\n', vertex.number, vertex.escape_digits),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.sayphonetic = function(context, vertex, callback) {
+  console.log('--SAYPHONETIC BLOCK--');
+  console.log('Saying phonetic...');
+  context.send(util.format('SAY PHONETIC %s "%s"\n', vertex.text, vertex.escape_digits),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.tts = function(context, vertex, callback) {
+  console.log('--GOOGLE_TTS BLOCK--');
+  var parameters = [path.join(config.root, 'server/config/agi_scripts', 'googletts.agi'), encodeURIComponent(vertex.text), vertex.google_tts_language];
+  // var parameters = ['/var/www/html/agisquare/agiscripts/googletts.agi', encodeURIComponent(vertex.text), vertex.google_tts_language];
+  console.log('Calling Google TTS API...');
+  context.exec('AGI', parameters.join(','),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.ispeechtts = function(context, vertex, callback) {
+  console.log('--ISPEECH_TTS BLOCK--');
+  // var parameters = [path.join(config.root, 'server/config/agi_scripts', 'ispeech-tts.agi'), encodeURIComponent(vertex.text), vertex.ispeech_tts_language, '', '', vertex.key];
+  var parameters = ['/var/www/html/agisquare/agiscripts/ispeech-tts.agi', encodeURIComponent(vertex.text), vertex.ispeech_tts_language, '', '', vertex.key];
+  console.log('Calling iSpeech TTS API...');
+  context.exec('AGI', parameters.join(','),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.getdigits = function(context, vertex, callback) {
+  console.log('--GETDIGITS BLOCK--');
+  var res = {};
+  console.log('There are ' + vertex.retry + ' retries');
+  if (vertex.retry > 0) {
+    vertex.retry--;
+    var announce = getFilePath(vertex.file_id);
+    // var announce = '/var/www/html/files/sounds/b19642d2f71e9cfffbd783fcc79c7415';
+    console.log('Announcing and waiting for user entry...');
+    context.send(util.format('GET DATA %s %s %s\n', announce, parseInt(vertex.response) * 1000, vertex.maxdigit),
+      function(err, res) {
+        var squareDetail = {
+          uniqueid: context.uniqueid,
+          node: vertex.label,
+          application: vertex.tag,
+          data: res.result || null
+        }
+        ReportSquareDetail
+          .create(squareDetail)
+          .catch(function(err) {
+            console.log('Error saving ' + vertex.tag + ':' + vertex.label + ' block log', err);
+          });
+        if (res.result && res.result.length >= parseInt(vertex.mindigit)) {
+          if (res.result !== '-1') {
+            console.log('There is a result, is over the minimum length and not due to an hangup!');
+            if (vertex.variable_id) {
+              console.log('Saving user entry in a variable...');
+              var varName = getVariableName(vertex.variable_id);
+              setVariable(context, varName, res.result, function() {
+                res = {
+                  code: 200,
+                  result: '0',
+                  data: 'x'
+                };
+                callback(null, res);
+              });
+            }
+          } else {
+            console.log('Channel hangup!');
+            res = {
+              code: 500,
+              result: '-1'
+            };
+            callback(null, res);
+          }
+        } else {
+          console.log('No entry or not long enough!');
+          res = {
+            code: 200,
+            result: '0',
+            data: 'i'
+          };
+          callback(null, res);
+        }
+      });
+  } else {
+    console.log('Run out of retries!');
+    res = {
+      code: 500,
+      result: '-1'
+    };
+    callback(null, res);
+  }
+};
+
+methods.record = function(context, vertex, callback) {
+  console.log('--RECORD BLOCK--');
+  var saveName = sr();
+  // FakeTable
+  //   .create({
+  //     name: vertex.name,
+  //     filename: saveName
+  //   })
+  //   .then(function() {
+  console.log('Starting call recording...');
+  context.send(util.format('RECORD FILE %s wav "%s" %s %s %s %s\n', path.join(config.root, 'server/files/recordings', saveName), vertex.escape_digits, vertex.timeout, null, true, null), function(
+    err, res) {
+    if (res.code === 200 && res.result !== '-1') {
+      console.log('Saving recording filename in RECORDING_SAVENAME variable...');
+      context.send(util.format('SET VARIABLE %s %s\n', 'RECORDING_SAVENAME', saveName), function(err, res) {
+        callback(err, res);
+      });
+    } else {
+      callback(err, res);
+    }
+  });
+  // })
+  // .catch(function(err) {
+  //   console.log(err);
+  // });
+};
+
+methods.gotoiftime = function(context, vertex, callback) { //single or multiple intervals
+  console.log('--GOTOIFTIME BLOCK--');
+  var res;
+  var valid = 'false';
+  var gotoIntervals;
+  if (!intervals) {
+    getIntervals(false);
+  }
+  var interval = _.find(intervals, {
+    id: parseInt(vertex.interval_id)
+  });
+  if (!interval.IntervalId) {
+    gotoIntervals = _.filter(intervals, {
+      IntervalId: parseInt(interval.id)
+    });
+  }
+  if (gotoIntervals) {
+    console.log('Is a group of intervals!');
+    if (gotoIntervals.length) {
+      console.log('There are ' + gotoIntervals.length + ' intervals!');
+      var subInterval;
+      console.log('Checking if at least one interval is valid...');
+      gotoIntervals.forEach(function(elem, index) {
+        subInterval = splitInterval(elem.interval);
+        if (isIntervalValid(subInterval)) {
+          console.log('Interval ' + index + ' is valid!');
+          valid = 'true';
+        }
+      });
+    } else {
+      console.log('There are no sub intervals, so it will be always true!');
+      valid = 'true';
+    }
+  } else {
+    console.log('Is a single interval!');
+    var splittedInterval = splitInterval(interval.interval);
+    if (isIntervalValid(splittedInterval)) {
+      console.log('The interval is valid!');
+      valid = 'true';
+    }
+  }
+  console.log('The final result for the intervals is "' + valid + '"!');
+  res = {
+    code: 200,
+    result: '0',
+    data: valid
+  };
+  callback(null, res);
+};
+
+methods.vswitch = function(context, vertex, callback) {
+  console.log('--VARIABLE_SWITCH BLOCK--');
+  console.log('Getting the variable value and searching the right exit...');
+  var varName = getVariableName(vertex.variable_id);
+  getVariable(context, varName, function(err, res) {
+    if (!res.data) {
+      res.data = '-';
+    }
+    callback(null, res);
+  });
+
+};
+
+methods.goal = function(context, vertex, callback) {
+  var squareDetail = {
+    uniqueid: context.uniqueid,
+    node: vertex.label,
+    application: vertex.tag,
+    data: vertex.goalname || null
+  }
+  ReportSquareDetail
+    .create(squareDetail)
+    .then(function(res) {
+      res = {
+        code: 200,
+        result: '1'
+      };
+      callback(null, res);
+    })
+    .catch(function(err) {
+      console.log('Error saving ' + vertex.tag + ':' + vertex.label + ' block log', err);
+      var res = {
+        code: 200,
+        result: '1'
+      };
+      callback(err, res);
+    });
+};
+
+methods.system = function(context, vertex, callback) {
+  console.log('--SYSTEM BLOCK--');
+  var squareDetail = {
+    uniqueid: context.uniqueid,
+    node: vertex.label,
+    application: vertex.tag,
+    data: _.trim(vertex.command) || null
+  }
+  ReportSquareDetail
+    .create(squareDetail)
+    .catch(function(err) {
+      console.log('Error saving ' + vertex.tag + ':' + vertex.label + ' block log', err);
+    });
+  console.log('Executing the system command...');
+  sh.exec(_.trim(vertex.command), function(code, output) {
+    var formattedOutput = output.replace(/(\r\n|\n|\r)/gm, "");
+    console.log('Program output:', formattedOutput);
+    console.log('Saving the output in a variable...');
+    var varName = getVariableName(vertex.variable_id);
+    setVariable(context, varName, '"' + formattedOutput + '"', callback);
+  });
+};
+
+methods.agi = function(context, vertex, callback) {
+  console.log('--AGI BLOCK--');
+  console.log('Executing the AGI command...');
+  context.exec('AGI', vertex.command, vertex.args,
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.subproject = function(context, vertex, callback) {
+  console.log('--SUBPROJECT BLOCK--');
+  if (!projects) {
+    getProjects(false);
+  }
+  var project = _.find(projects, {
+    id: parseInt(vertex.project_id)
+  });
+  console.log('Moving to project ""' + project.name + '"...');
+  context.exec('AGI', util.format('agi://192.168.2.147/square,%s', project.name),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.ispeechasr = function(context, vertex, callback) {
+  console.log('--ISPEECH_ASR BLOCK--');
+  // var parameters = ['/var/www/html/agisquare/agiscripts/ispeech-asr.agi', vertex.ispeech_asr_language, '', (vertex.model === '0' ? '' : vertex.model), '1', '#', '', vertex.key];
+  var parameters = [path.join(config.root, 'server/config/agi_scripts', 'ispeech-asr.agi'), vertex.ispeech_asr_language, '', (vertex.model === '0' ? '' : vertex.model), '1', '#', '', vertex.key];
+  console.log('Calling Google ASR API...');
+  context.exec('AGI', parameters.join(','),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.database = function(context, vertex, callback) {
+  console.log('--DATABASE BLOCK--');
+  if (!dbConnections) {
+    getDbConnections(false);
+  }
+  var connection = _.find(dbConnections, {
+    id: parseInt(vertex.odbc_id)
+  });
+  console.log('Trying to get ODBC connection...');
+  var squareDetail = {
+    uniqueid: context.uniqueid,
+    node: vertex.label,
+    application: vertex.tag,
+    data: vertex.query || null
+  }
+  ReportSquareDetail
+    .create(squareDetail)
+    .catch(function(err) {
+      console.log('Error saving ' + vertex.tag + ':' + vertex.label + ' block log', err);
+    });
+  try {
+    db.openSync(connection.dsn);
+    var data = db.querySync(vertex.query);
+    db.closeSync();
+    setVariablesMatrix(context, vertex, callback, data);
+  } catch (e) {
+    console.log(e.message);
+    wait.for(setVariable, context, 'ERROR', 'true');
+    wait.for(setVariable, context, 'MESSAGE', e.message);
+  }
+};
+
+methods.gotoc = function(context, vertex, callback) {
+  console.log('--GOTO BLOCK--');
+  console.log('Going to extension ' + vertex.extension + ' ...');
+  context.exec('DIAL', util.format('Local/%s@%s', vertex.extension, vertex.context),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.gotoif = function(context, vertex, callback) {
+  console.log('--GOTOIF BLOCK--');
+  var res = {
+    code: 200,
+    result: '0'
+  };
+  console.log('Evaluating condition...');
+  res.data = String(eval(vertex.condition) ? 'true' : 'false');
+  console.log('The result is ' + res.data);
+  callback(null, res);
+};
+
+methods.hangup = function(context, vertex, callback) {
+  console.log('--HANGUP BLOCK--');
+  console.log('Hangin up the call...');
+  context.hangup();
+};
+
+methods.noop = function(context, vertex, callback) {
+  console.log('--NOOP BLOCK--');
+  var output = (vertex.output) ? '\"' + vertex.output + '\"' : '\"' +
+    vertex.label + '\"';
+  console.log('NOOP value is ' + output);
+  context.exec('NOOP', output,
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.saydigits = function(context, vertex, callback) {
+  console.log('--SAYDIGITS BLOCK--');
+  console.log('Saying digits...');
+  context.send(util.format('SAY DIGITS %s "%s"\n', vertex.digits, vertex.escape_digits),
+    function(err, res) {
+      callback(err, res);
+    });
+};
+
+methods.set = function(context, vertex, callback) {
+  console.log('--SET BLOCK--');
+  console.log('Saving value in a variable...');
+  var varName = getVariableName(vertex.variable_id);
+  wait.for(setVariable, context, varName, vertex.variable_value);
+};
+
+function getVertices(root) {
+  return _.reduce(root, function(result, edge, tag) {
+
+    if (tag === 'mxcell') {
+      return result;
+    }
+
+    if (_.isArray(edge)) {
+      _.forIn(edge, function(value, key) {
+        result[value.$.id] = value.$;
+        result[value.$.id].tag = tag;
+      });
+      return result;
+    }
+
+    result[edge.$.id] = edge.$;
+    result[edge.$.id].tag = tag;
+    return result;
+
+  }, {});
+}
+
+function getTargetBySource(root, source, value) {
+  return _.result(_.find(root.mxcell, function(edge) {
+    if (edge.$.edge && edge.$.source) {
+      if (edge.$.source === source) {
+        if (edge.$.value && edge.$.value !== value) {
+          return false;
+        }
+        return true;
+      }
+    }
+    return false;
+  }), '$.target');
+}
+
+function replaceAllVariables(context, vertex, callback) {
+  var obj = vertex;
+  for (var key in obj) {
+    var res = obj[key].match(/{+(.*?)}/gi);
+    if (res) {
+      console.log('res', res);
+      res.forEach(function(variable, index) {
+        console.log('variable', res[index].substring(1, res[index].length - 1));
+        var value = wait.for(getVariable, context, res[index].substring(1, res[index].length - 1));
+        console.log('value', value);
+        if (value.data) {
+          obj[key] = obj[key].replace(res[index], value.data);
+          // console.log('Replace', res[index], string);
+        }
+      })
+    }
+  }
+  obj.replaced = true;
+  return obj;
+}
+
+function xstart(context) {
+  var root = context.root;
+  var vertices = context.vertices;
+
+  if (root.start) {
+    if (!_.isArray(root.start)) {
+      var source = root.start.$.id;
+      var res = {};
+      do {
+        try {
+          var vertex = vertices[source].replaced ? vertices[source] : replaceAllVariables(context, vertices[source]);
+          if (vertex) {
+            if (_.isFunction(methods[vertex.tag])) {
+              res = wait.for(methods[vertex.tag], context, vertex);
+            } else {
+              res = wait.for(methods.noop, context, vertex);
+            }
+            if (res.code === 200 && res.result !== '-1') {
+              var target = getTargetBySource(root, source, res.data);
+              if (!target) {
+                if (vertex.tag === 'menu') {
+                  if (vertex.retry > 0) {
+                    target = getTargetBySource(root, source, 'i');
+                    if (!target) {
+                      target = source;
+                    }
+                  }
+                }
+                if (vertex.tag === 'getdigits' && vertex.retry > 0) {
+                  target = source;
+                }
+                if (vertex.tag === 'vswitch') {
+                  target = getTargetBySource(root, source, '-');
+                  if (!target) {
+                    target = source;
+                  }
+                }
+              }
+              source = target;
+            } else {
+              source = null;
+            }
+          } else {
+            source = null;
+          }
+        } catch (err) {
+          console.log(err);
+        }
+      }
+      while (source);
+      console.log('No target found, hangup!');
+      context.hangup();
+    }
+  }
+}
+
+
+function xfinally(context) {
+  var root = context.root;
+  var vertices = context.vertices;
+
+  if (root.finally) {
+    if (!_.isArray(root.finally)) {
+      var source = root.finally.$.id;
+      var res = {};
+      do {
+        try {
+          var vertex = replaceAllVariables(context, vertices[source]);
+          if (vertex) {
+            if (_.isFunction(methods[vertex.tag])) {
+              res = wait.for(methods[vertex.tag], context, vertex);
+            } else {
+              res = wait.for(methods.noop, context, vertex);
+            }
+            if (res.code === 200 && res.result !== '-1') {
+              var target = getTargetBySource(root, source, res.data);
+              if (!target) {
+                if (vertex.tag === 'menu') {
+                  if (vertex.retry > 0) {
+                    target = getTargetBySource(root, source, 'i');
+                    if (!target) {
+                      target = source;
+                    }
+                  }
+                }
+                if (vertex.tag === 'getdigits' && vertex.retry > 0) {
+                  target = source;
+                }
+              }
+              source = target;
+            } else {
+              source = null;
+            }
+          } else {
+            source = null;
+          }
+        } catch (err) {
+          console.log(err);
+        }
+      } while (source);
+      context.hangup();
+      console.log('No target found, stopping "Finally" branch!');
+    }
+  }
+  context.end();
+}
+
+
+function main(context) {
+
+  context.on('error', function(err) {
+    console.log('//Error:', err);
+  });
+
+  context.on('close', function() {
+    console.log('//Context close');
+  });
+
+  context.on('hangup', function() {
+    if (context.finally) {
+      context.finally = false;
+      console.log('//Starting "Finally" branch!');
+      wait.launchFiber(xfinally, context);
+    } else {
+      ReportSquare.update({
+          leaveAt: moment().format("YYYY-MM-DD HH:mm:ss")
+        }, {
+          where: {
+            uniqueid: context.uniqueid
+          }
+        })
+        .catch(function(err) {
+          console.log('Error saving exit time from IVR', err);
+        });
+      console.log('//Context Hangup');
+    }
+  });
+
+  context.on('response', function(res) {
+    //console.log('Response:', res);
+  });
+
+  context.on('variables', function(vars) {
+    console.log(vars);
+    console.log('Received new call from: ' + vars.agi_callerid +
+      ' with uniqueid: ' + vars.agi_uniqueid);
+    var squareLog = {
+      network: vars.agi_network,
+      network_script: vars.agi_network_script,
+      request: vars.agi_request,
+      channel: vars.agi_channel,
+      language: vars.agi_language,
+      type: vars.agi_type,
+      uniqueid: vars.agi_uniqueid,
+      version: vars.agi_version,
+      callerid: vars.agi_callerid,
+      calleridname: vars.agi_calleridname,
+      callingpres: vars.agi_callingpres,
+      callingani2: vars.agi_callingani2,
+      callington: vars.agi_callington,
+      callingtns: vars.agi_callingtns,
+      dnid: vars.agi_dnid,
+      rdnis: vars.agi_rdnis,
+      context: vars.agi_context,
+      extension: vars.agi_extension,
+      priority: vars.agi_priority,
+      enhanced: vars.agi_enhanced,
+      accountcode: vars.agi_accountcode,
+      threadid: vars.agi_threadid,
+      project_name: vars.agi_arg_1,
+      joinAt: moment().format("YYYY-MM-DD HH:mm:ss")
+    };
+
+    ReportSquare
+      .create(squareLog)
+      .catch(function(err) {
+        console.log('Error saving enter time for IVR', err);
+      });
+
+    if (vars.agi_arg_1) {
+      SquareProject
+        .findOne({
+          where: {
+            name: vars.agi_arg_1
+          },
+          attributes: ['id', 'production']
+        })
+        .then(function(project) {
+          if (project) {
+            if (project.production) {
+              xml.parseString(project.production, {
+                normalizeTags: true,
+                explicitArray: false
+              }, function(err, result) {
+                var root = result.mxgraphmodel.root;
+                if (root) {
+                  context.root = root;
+                  context.finally = true;
+                  context.uniqueid = vars.agi_uniqueid;
+                  context.vertices = getVertices(root);
+                  wait.launchFiber(xstart, context);
+                } else {
+                  console.log('No root in project!');
+                  context.end();
+                }
+              });
+            } else {
+              console.log('No project published!');
+              context.end();
+            }
+          } else {
+            console.log('No project found!');
+            context.end();
+          }
+        })
+        .catch(function(err) {
+          console.log('Error:', err);
+          context.end();
+        });
+    } else {
+      console.log('No project argument!');
+      context.end();
+    }
+  });
+}
+
+function splitInterval(interval) {
+  var finalInterval = {};
+  var splittedInterval = interval.split(',');
+  var intValues;
+  splittedInterval.forEach(function(element, index) {
+    switch (index) {
+      case 0:
+        if (element !== '*') {
+          intValues = element.split('-');
+          finalInterval.t_from = moment(intValues[0], 'HH:mm');
+          finalInterval.t_to = moment(intValues[1], 'HH:mm');
+        } else {
+          finalInterval.t_from = null;
+          finalInterval.t_to = null;
+        }
+        break;
+      case 1:
+        if (element !== '*') {
+          intValues = element.split('-');
+          finalInterval.wd_from = weekDaysCollection[intValues[0]];
+          finalInterval.wd_to = intValues[1] ? weekDaysCollection[intValues[1]] : null;
+        } else {
+          finalInterval.wd_from = null;
+          finalInterval.wd_to = null;
+        }
+        break;
+      case 2:
+        if (element !== '*') {
+          intValues = element.split('-');
+          finalInterval.md_from = intValues[0];
+          finalInterval.md_to = intValues[1] ? intValues[1] : null;
+        } else {
+          finalInterval.md_from = null;
+          finalInterval.md_to = null;
+        }
+        break;
+      case 3:
+        if (element !== '*') {
+          intValues = element.split('-');
+          finalInterval.m_from = monthsCollection[intValues[0]];
+          finalInterval.m_to = intValues[1] ? monthsCollection[intValues[1]] : null;
+        } else {
+          finalInterval.m_from = null;
+          finalInterval.m_to = null;
+        }
+        break;
+      default:
+
+    }
+
+
+  });
+  return finalInterval;
+}
+
+function isIntervalValid(interval) {
+  var hour = moment().format("HH:mm");
+  var day = moment().format("E");
+  var monthDay = moment().format("D");
+  var month = moment().format("M");
+  var tValid, wdValid, mdValid, mValid;
+  var monthsNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+  var weekDaysNumbers = [1, 2, 3, 4, 5, 6, 7];
+  var daysOfMonthsNumbers = [];
+  for (var i = 1; i <= 31; i++) {
+    daysOfMonthsNumbers.push(i);
+  }
+  tValid = (interval.t_from && interval.t_to) ? moment(hour, "HH:mm").isBetween(moment(interval.t_from, "HH:mm"), moment(interval.t_to, "HH:mm")) : true;
+  if (interval.wd_from) {
+    if (interval.wd_to) {
+      var validWeekdays = _.filter(weekDaysNumbers, function(elem) {
+        return (elem >= interval.wd_from) || (elem <= interval.wd_to);
+      });
+      wdValid = (validWeekdays.indexOf(parseInt(day)) !== -1) ? true : false;
+    } else {
+      wdValid = (parseInt(day) === interval.wd_from) ? true : false;
+    }
+  } else {
+    wdValid = true;
+  }
+  if (interval.md_from) {
+    if (interval.md_to) {
+      var validMonthsdays = _.filter(daysOfMonthsNumbers, function(elem) {
+        return (elem >= interval.md_from) || (elem <= interval.md_to);
+      });
+      mdValid = (validMonthsdays.indexOf(parseInt(monthDay)) !== -1) ? true : false;
+    } else {
+      mdValid = (parseInt(monthDay) === interval.md_from) ? true : false;
+    }
+  } else {
+    mdValid = true;
+  }
+  if (interval.m_from) {
+    if (interval.m_to) {
+      var validMonths = _.filter(monthsNumbers, function(elem) {
+        return (elem >= interval.m_from) || (elem <= interval.m_to);
+      });
+      mValid = (validMonths.indexOf(parseInt(month)) !== -1) ? true : false;
+    } else {
+      mValid = (parseInt(month) === interval.m_from) ? true : false;
+    }
+  } else {
+    mValid = true;
+  }
+
+  return tValid && wdValid && mdValid && mValid;
+
+}
+
+function setVariable(context, name, value, callback) {
+  console.log('Setting variable...');
+  console.log('Sending set variable,variable is ' + name + ', value is ', value);
+  context.send(util.format('SET VARIABLE %s %s\n', name, value), function(err, res) {
+    if (callback) {
+      callback(err, res);
+    }
+  });
+}
+
+function getVariableName(id) {
+  if (!variables) {
+    getVariables(false);
+  }
+  var variable = {
+    name: 'placeholder' //if for any reason i don't receive both the name and the id of the variable, the value will be saved in a placeholder variable
+  };
+  if (id) {
+    variable = _.find(variables, {
+      id: parseInt(id)
+    });
+  }
+  return variable.name;
+}
+
+function setVariablesMatrix(context, vertex, callback, data) {
+  var varName = getVariableName(vertex.variable_id);
+  _.forEach(data, function(row, index) {
+    _.forOwn(row, function(value, key) {
+      wait.for(setVariable, context, varName + '[' + index + '][' + key + ']', value);
+    });
+  });
+}
+
+function getVariable(context, name, callback) {
+  context.getVariable(name, function(err, res) {
+    if (res.code === 200 && res.result !== '0') {
+      res.data = res.result.substring(res.result.lastIndexOf("(") + 1,
+        res.result.lastIndexOf(")"));
+    } else {
+      res.data = '';
+    }
+    callback(null, res);
+  })
+}
+
+function getFilePath(id) {
+  if (!sounds) {
+    getSounds(false);
+  }
+  if (id > 0) {
+    var file = _.find(sounds, {
+      id: parseInt(id)
+    });
+    return util.format('%s/%s', config.convertedSounds, file.save_name);
+  }
+  return '';
+}
+
+function getUsers(synch) {
+  User
+    .findAll()
+    .then(function(result) {
+      users = _.clone(result);
+      if (synch) {
+        synchUpdates(User, users, 'id');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getQueues(synch) {
+  VoiceQueue
+    .findAll()
+    .then(function(result) {
+      queues = _.clone(result);
+      if (synch) {
+        synchUpdates(VoiceQueue, queues, 'name');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getTrunks(synch) {
+  Trunk
+    .findAll()
+    .then(function(result) {
+      trunks = _.clone(result);
+      if (synch) {
+        synchUpdates(Trunk, trunks, 'id');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getVariables(synch) {
+  Variable
+    .findAll()
+    .then(function(result) {
+      variables = _.clone(result);
+      if (synch) {
+        synchUpdates(Variable, variables, 'id');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getDbConnections(synch) {
+  SquareOdbc
+    .findAll()
+    .then(function(result) {
+      dbConnections = _.clone(result);
+      if (synch) {
+        synchUpdates(SquareOdbc, dbConnections, 'id');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getIntervals(synch) {
+  Interval
+    .findAll()
+    .then(function(result) {
+      intervals = _.clone(result);
+      if (synch) {
+        synchUpdates(Interval, intervals, 'id');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getProjects(synch) {
+  SquareProject
+    .findAll()
+    .then(function(result) {
+      projects = _.clone(result);
+      if (synch) {
+        synchUpdates(SquareProject, projects, 'id');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getSounds(synch) {
+  Sound
+    .findAll()
+    .then(function(result) {
+      sounds = _.clone(result);
+      if (synch) {
+        synchUpdates(Sound, sounds, 'id');
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function synchUpdates(Model, collection, key) {
+  var condition = {};
+  Model.afterCreate(function(doc) {
+    condition[key] = doc[key];
+    updateCollection(collection, condition, doc);
+  });
+  Model.afterUpdate(function(doc) {
+    condition[key] = doc[key];
+    updateCollection(collection, condition, doc);
+  });
+  Model.afterDestroy(function(doc) {
+    condition[key] = doc[key];
+    _.remove(collection, condition);
+  });
+}
+
+function updateCollection(collection, condition, doc) {
+  var oldItem = _.find(collection, condition);
+  var index = collection.indexOf(oldItem);
+  if (oldItem) {
+    _.merge(collection[index], doc);
+  } else {
+    collection.unshift(doc);
+  }
+}
+
+function getPort() {
+  Settings
+    .findOne()
+    .then(function(result) {
+      agiPort = result.agi_port;
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+module.exports = function() {
+  console.log('Starting Cally Square AGI...');
+  getPort();
+  var server = agi.createServer(main).listen(agiPort ? agiPort : 4573);
+  getUsers(true);
+  getQueues(true);
+  getTrunks(true);
+  getVariables(true);
+  getIntervals(true);
+  getProjects(true);
+  getSounds(true);
+  getDbConnections(true);
+};
diff --git a/server/config/ami/action.js b/server/config/ami/action.js
index bf3713b..99ebb83 100644
--- a/server/config/ami/action.js
+++ b/server/config/ami/action.js
@@ -1 +1,574 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+var moment = require('moment');
+var util = require('util');
+var Promise = require('bluebird');
+
+var Sequelize = require('../../models').sequelize;
+var ReportMember = require('../../models').ReportMember;
+var _Action = require('../../models').Action;
+var Agent = require('../../models').User;
+var UserHasVoiceQueue = require('../../models').UserHasVoiceQueue;
+var UserHasChatQueue = require('../../models').UserHasChatQueue;
+var UserHasMailQueue = require('../../models').UserHasMailQueue;
+var UserHasFaxQueue = require('../../models').UserHasFaxQueue;
+
+function Action(ami) {
+  console.log('Action Initialization...');
+  var channels = ['voice', 'chat', 'mail', 'fax'];
+
+  _Action.afterCreate(function(doc, options) {
+    return onSave(doc, options, ami);
+  });
+
+  ReportMember.beforeCreate(function(doc, options) {
+    return ReportMember
+      .findAll({
+        where: {
+          channel: doc.channel,
+          membername: doc.membername,
+          interface: doc.interface,
+          exitAt: null
+        }
+      })
+      .then(function(elms) {
+        var queries = [];
+        var exitAt = moment();
+        elms.forEach(function(elm) {
+          var enterAt = moment(elm.enterAt);
+          queries.push(elm.update({
+            exitAt: exitAt.format("YYYY-MM-DD HH:mm:ss"),
+            duration: exitAt.diff(enterAt, 'seconds')
+          }));
+        })
+        return queries;
+      })
+      .all();
+  });
+
+  // HANDLE PAUSE FUNCTION
+  function handlePause(options, action) {
+    return function(agent) {
+      return Promise
+        .promisify(ami.action)({
+          action: 'QueuePause',
+          interface: util.format('%s/%s', 'SIP', agent.name),
+          paused: true,
+          reason: options.body.type
+        })
+        .then(function(astData) {
+          return agent
+            .update({
+              voicePause: true,
+              chatPause: true,
+              mailPause: true,
+              faxPause: true,
+              pauseType: options.body.type,
+              lastPauseAt: moment().format("YYYY-MM-DD HH:mm:ss")
+            }, {
+              transaction: options.transaction || null
+            });
+        })
+        .then(function(agent) {
+          var reportMembers = [];
+          channels.forEach(function(channel) {
+            reportMembers.push({
+              channel: channel,
+              membername: agent.name,
+              interface: util.format('%s/%s', 'SIP', agent.name),
+              type: options.body.type,
+              enterAt: moment().format("YYYY-MM-DD HH:mm:ss"),
+              data1: action.data1,
+              data2: action.data2,
+              data3: action.data3,
+              data4: action.data4
+            });
+          })
+          return ReportMember
+            .bulkCreate(reportMembers, {
+              individualHooks: true,
+              transaction: options.transaction || null
+            });
+        });
+    }
+  }
+
+  // HANDLE UNPAUSE FUNCTION
+  function handleUnpause(options, action) {
+    return function(agent) {
+      if (!options.body) options.body = {};
+
+      switch (options.body.channel) {
+        case 'voice':
+
+          return Promise
+            .promisify(ami.action)({
+              action: 'QueuePause',
+              interface: util.format('%s/%s', 'SIP', agent.name),
+              paused: false,
+              reason: options.body.type
+            })
+            .then(function() {
+              return agent
+                .update({
+                  voicePause: false,
+                }, {
+                  transaction: options.transaction || null
+                })
+            })
+            .then(function(agent) {
+              var exitAt = moment();
+              return ReportMember
+                .findOne({
+                  where: {
+                    channel: options.body.channel,
+                    membername: agent.name,
+                    interface: util.format('%s/%s', 'SIP', agent.name),
+                    exitAt: null
+                  }
+                });
+            })
+            .then(handleEntityNotFound({
+              message: 'Already Pause Off'
+            }))
+            .then(function(reportMember) {
+              var exitAt = moment();
+              var enterAt = moment(reportMember.enterAt);
+
+              return reportMember
+                .update({
+                  exitAt: exitAt.format("YYYY-MM-DD HH:mm:ss"),
+                  duration: exitAt.diff(enterAt, 'seconds')
+                }, {
+                  individualHooks: true,
+                  transaction: options.transaction || null
+                });
+            })
+            .then(function() {
+              return agent;
+            });
+
+        case 'chat':
+        case 'mail':
+        case 'fax':
+
+          var data = {};
+          data[util.format('%s%s', options.body.channel, 'Pause')] = false;
+
+          return agent
+            .update(data, {
+              transaction: options.transaction
+            })
+            .then(function(agent) {
+              var exitAt = moment();
+
+              return ReportMember
+                .findOne({
+                  where: {
+                    channel: options.body.channel,
+                    membername: agent.name,
+                    interface: util.format('%s/%s', 'SIP', agent.name),
+                    exitAt: null
+                  }
+                });
+            })
+            .then(handleEntityNotFound({
+              message: 'Already Pause Off'
+            }))
+            .then(function(reportMember) {
+              var exitAt = moment();
+              var enterAt = moment(reportMember.enterAt);
+
+              return reportMember
+                .update({
+                  exitAt: exitAt.format("YYYY-MM-DD HH:mm:ss"),
+                  duration: exitAt.diff(enterAt, 'seconds')
+                }, {
+                  individualHooks: true,
+                  transaction: options.transaction
+                });
+            })
+            .then(function() {
+              return agent;
+            });
+
+        default:
+          var action = Promise.promisify(ami.action);
+          return action({
+              action: 'QueuePause',
+              interface: util.format('%s/%s', 'SIP', agent.name),
+              paused: false
+            })
+            .catch(function(err) {
+              console.error(err);
+            })
+            .then(function() {
+              return agent
+                .update({
+                  voicePause: false,
+                  mailPause: false,
+                  chatPause: false,
+                  faxPause: false
+                }, {
+                  transaction: options.transaction
+                });
+            })
+            .then(function(agent) {
+              var exitAt = moment();
+              return ReportMember
+                .update({
+                  exitAt: exitAt
+                }, {
+                  where: {
+                    membername: agent.name,
+                    interface: util.format('%s/%s', 'SIP', agent.name),
+                    exitAt: null
+                  },
+                  transaction: options.transaction
+                });
+            })
+            .then(function() {
+              return agent;
+            });
+      }
+    }
+  }
+
+  // HANDLE LOGIN FUNCTION
+  function handleLogin(options, action) {
+    return function(agent) {
+      return agent
+        .getVoiceQueues()
+        .then(function(voiceQueues) {
+          var actions = [];
+          voiceQueues.forEach(function(voiceQueue) {
+            actions.push(ami.action({
+              action: 'QueueAdd',
+              queue: voiceQueue.name,
+              interface: util.format('%s/%s', 'SIP', agent.name),
+              membername: agent.name
+            }));
+          });
+          return Promise.all(actions);
+        })
+        .then(function(astData) {
+          var reportMembers = [];
+          channels.forEach(function(channel) {
+            reportMembers.push({
+              channel: channel,
+              membername: agent.name,
+              interface: util.format('%s/%s', 'SIP', agent.name),
+              type: 'LOGIN',
+              enterAt: moment().format("YYYY-MM-DD HH:mm:ss")
+            });
+          });
+          return ReportMember
+            .bulkCreate(reportMembers, {
+              individualHooks: true
+            });
+        });
+    }
+  }
+
+  // HANDLE LOGOUT FUNCTION
+  function handleLogout(options, action) {
+    return function(agent) {
+      return agent
+        .getVoiceQueues()
+        .then(function(voiceQueues) {
+          var actions = [];
+          voiceQueues.forEach(function(voiceQueue) {
+            actions.push(ami.action({
+              action: 'QueueRemove',
+              queue: voiceQueue.name,
+              interface: util.format('%s/%s', 'SIP', agent.name)
+            }));
+          });
+          return Promise
+            .all(actions)
+            .catch(function(err) {
+              console.error(err);
+            });
+        })
+        .then(function(astData) {
+          return ReportMember
+            .update({
+              exitAt: moment().format("YYYY-MM-DD HH:mm:ss")
+            }, {
+              where: {
+                membername: agent.name,
+                interface: util.format('%s/%s', 'SIP', agent.name),
+                type: 'LOGIN',
+                exitAt: null
+              },
+              individualHooks: true,
+              transaction: options.transaction
+            });
+        });
+    }
+  }
+
+  // HANDLE QUEUEADD FUNCTION
+  function handleQueueAdd(options, action) {
+    return function(agent) {
+      var bulkQueues = [];
+      switch (options.body.channel) {
+        case 'voice':
+          options.body.queues.forEach(function(queue) {
+            bulkQueues.push(UserHasVoiceQueue
+              .findOrCreate({
+                where: {
+                  UserId: agent.id,
+                  queue: queue
+                },
+                defaults: {
+                  queue: queue,
+                  location: util.format('SIP/%s', agent.name),
+                  membername: agent.name,
+                  UserId: agent.id,
+                },
+                transaction: options.transaction
+              }));
+          });
+          return Promise
+            .all(bulkQueues)
+            .then(function() {
+              var actions = [];
+              options.body.queues.forEach(function(queue) {
+                actions.push(ami.action({
+                  action: 'QueueAdd',
+                  queue: queue,
+                  interface: util.format('%s/%s', 'SIP', agent.name),
+                  membername: agent.name
+                }));
+              });
+              return Promise
+                .all(actions);
+            })
+            .catch(function(err) {
+              console.error(err);
+            })
+        case 'chat':
+          options.body.queues.forEach(function(queue) {
+            bulkQueues.push(UserHasChatQueue
+              .findOrCreate({
+                where: {
+                  UserId: agent.id,
+                  ChatQueueId: queue.id
+                },
+                defaults: {
+                  ChatQueueId: queue.id,
+                  queue: queue.name,
+                  UserId: agent.id,
+                },
+                transaction: options.transaction
+              }));
+          });
+          return Promise
+            .all(bulkQueues)
+            .catch(function(err) {
+              console.log(err);
+            });
+        case 'mail':
+          options.body.queues.forEach(function(queue) {
+            bulkQueues.push(UserHasMailQueue
+              .findOrCreate({
+                where: {
+                  UserId: agent.id,
+                  MailQueueId: queue.id
+                },
+                defaults: {
+                  MailQueueId: queue.id,
+                  queue: queue.name,
+                  UserId: agent.id,
+                },
+                transaction: options.transaction
+              }));
+          });
+          return Promise
+            .all(bulkQueues)
+            .catch(function(err) {
+              console.log(err);
+            });
+        case 'fax':
+          options.body.queues.forEach(function(queue) {
+            bulkQueues.push(UserHasFaxQueue
+              .findOrCreate({
+                where: {
+                  UserId: agent.id,
+                  FaxQueueId: queue.id
+                },
+                defaults: {
+                  faxQueueId: queue.id,
+                  queue: queue.name,
+                  UserId: agent.id,
+                },
+                transaction: options.transaction
+              }));
+          });
+          return Promise
+            .all(bulkQueues)
+            .catch(function(err) {
+              console.log(err);
+            });
+        default:
+      }
+    }
+  }
+
+  // HANDLE QUEUEREMOVE FUNCTION
+  function handleQueueRemove(options, action) {
+    return function(agent) {
+      switch (options.body.channel) {
+        case 'voice':
+          return UserHasVoiceQueue
+            .destroy({
+              where: {
+                UserId: agent.id,
+                queue: options.body.queue
+              },
+              transaction: options.transaction,
+              individualHooks: true
+            })
+            .then(function() {
+              var newAction = Promise.promisify(ami.action);
+              return newAction({
+                action: 'QueueRemove',
+                queue: options.body.queue,
+                interface: util.format('%s/%s', 'SIP', agent.name),
+              });
+            })
+            .catch(function(err) {
+              console.error('queue association remove error :', err);
+            });
+        case 'chat':
+          return UserHasChatQueue
+            .destroy({
+              where: {
+                UserId: agent.id,
+                ChatQueueId: options.body.queueId
+              },
+              transaction: options.transaction,
+              individualHooks: true
+            })
+            .catch(function(err) {
+              console.error('queue association remove error :', err);
+            });
+        case 'mail':
+          return UserHasMailQueue
+            .destroy({
+              where: {
+                UserId: agent.id,
+                MailQueueId: options.body.queueId
+              },
+              transaction: options.transaction,
+              individualHooks: true
+            })
+            .catch(function(err) {
+              console.error('queue association remove error :', err);
+            });
+        case 'fax':
+          return UserHasFaxQueue
+            .destroy({
+              where: {
+                UserId: agent.id,
+                FaxQueueId: options.body.queueId
+              },
+              transaction: options.transaction,
+              individualHooks: true
+            })
+            .catch(function(err) {
+              console.error('queue association remove error :', err);
+            });
+        default:
+      }
+    }
+  }
+
+  // HANDLE ENTITY NOT FOUND
+  function handleEntityNotFound(error) {
+    return function(entity) {
+      if (!entity) {
+        throw error;
+      }
+      return entity;
+    };
+  }
+
+  function onSave(action, options, ami) {
+    // START ACTION STATEMENT
+    switch (action.name) {
+      case 'pause':
+        // START HANDLE PAUSE
+        return Agent
+          .findById(options.body.agent)
+          .then(handleEntityNotFound({
+            message: 'Unknown Agent'
+          }))
+          .then(handlePause(options, action));
+        // END HANDLE PAUSE
+      case 'unpause':
+        // START HANDLE UNPAUSE
+        return Agent
+          .findById(options.body.agent)
+          .then(handleEntityNotFound({
+            message: 'Unknown Agent'
+          }))
+          .then(handleUnpause(options, action));
+        // END HANDLE UNPAUSE
+
+      case 'login':
+        // START HANDLE LOGIN
+        return Agent
+          .findById(action.data1)
+          .then(handleEntityNotFound({
+            message: 'Unknown Agent'
+          }))
+          .then(handleLogin(options, action));
+        // END HANDLE LOGING
+
+      case 'logout':
+        // START HANDLE LOGOUT
+        return Agent
+          .findById(action.data1)
+          .then(handleEntityNotFound({
+            message: 'Unknown Agent'
+          }))
+          .then(handleUnpause(options, action))
+          .then(handleLogout(options, action));
+        // END HANDLE LOGOUT
+      case 'QueueAdd':
+        // START HANDLE QUEUEADD
+        return Agent
+          .findById(options.body.agent)
+          .then(handleEntityNotFound({
+            message: 'Unknown Agent'
+          }))
+          .then(handleQueueAdd(options, action));
+        // END HANDLE QUEUEADD
+      case 'QueueRemove':
+        // START HANDLE QUEUEREMOVE
+        return Agent
+          .findById(options.body.agent)
+          .then(handleEntityNotFound({
+            message: 'Unknown Agent'
+          }))
+          .then(handleQueueRemove(options, action));
+        // END HANDLE QUEUEREMOVE
+
+      default:
+        throw {
+          message: 'Unknown Action'
+        };
+    }
+    // END ACTION STATEMENT
+  }
+
+  return {};
+}
+
+module.exports = Action;
diff --git a/server/config/ami/acw.js b/server/config/ami/acw.js
index 10be274..d3f273e 100644
--- a/server/config/ami/acw.js
+++ b/server/config/ami/acw.js
@@ -1 +1,152 @@
\ No newline at end of file
+var moment = require('moment');
+var _ = require('lodash');
+var VoiceQueue = require('../../models').VoiceQueue;
+var Agent = require('../../models').User;
+var Action = require('../../models').Action;
+var ReportAgent = require('../../models').ReportAgent;
+var _voiceQueues;
+var _agents;
+var activeAcw = {};
+
+
+function ACW(ami) {
+  console.log('ACW Initialization...');
+  VoiceQueue
+    .findAll()
+    .then(function(voiceQueues) {
+      _voiceQueues = voiceQueues;
+      // console.log('_voiceQueues', _.map(_voiceQueues, 'dataValues'));
+      synchCollection(VoiceQueue, _voiceQueues, 'name');
+      return Agent
+        .scope('agent')
+        .findAll();
+    })
+    .then(function(agents) {
+      _agents = agents;
+      // console.log('_agents', _.map(_agents, 'dataValues'));
+      synchCollection(Agent, _agents, 'id');
+      Action.afterCreate(function(doc, options) {
+        return onSave(doc, options);
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function onSave(doc, options) {
+  if (doc.name === 'unpause' && activeAcw[options.body.agent]) {
+    var acwTime;
+    if (activeAcw[options.body.agent].unpause) {
+      acwTime = activeAcw[options.body.agent].acwTime;
+    } else {
+      clearTimeout(activeAcw[options.body.agent].timeout);
+      acwTime = moment.duration(moment() - activeAcw[options.body.agent].acwAt).asSeconds();
+    }
+    return ReportAgent
+      .update({
+        agentacw: true,
+        acwtime: acwTime
+      }, {
+        where: {
+          uniqueid: activeAcw[options.body.agent].uniqueid,
+          destuniqueid: activeAcw[options.body.agent].destuniqueid
+        },
+        transaction: options.transaction,
+        individualHooks: true
+      })
+      .then(function() {
+        delete activeAcw[options.body.agent];
+      })
+      .catch(function(err) {
+        console.log(err);
+      });
+  }
+}
+
+ACW.prototype.agentcomplete = function(evt) {
+  var queueIsAcw = _.find(_voiceQueues, {
+    name: evt.queue,
+    acw: true
+  });
+  if (queueIsAcw) {
+    var agent = _.find(_agents, {
+      name: evt.membername
+    });
+    Action
+      .create({
+        name: 'pause',
+        data1: evt.queue,
+        data2: queueIsAcw.acwTimeout,
+        data3: evt.uniqueid,
+        data4: 'ACW'
+      }, {
+        body: {
+          agent: agent.id,
+          type: 'ACW'
+        }
+      })
+      .then(function() {
+        console.log('PAUSED BY ACW');
+        activeAcw[agent.id] = {
+          uniqueid: evt.uniqueid,
+          destuniqueid: evt.destuniqueid,
+          acwTime: queueIsAcw.acwTimeout,
+          acwAt: moment(),
+          unpause: false
+        };
+
+        activeAcw[agent.id].timeout = setTimeout(function() {
+          console.log('UNPAUSED BY ACW');
+          activeAcw[agent.id].unpause = true;
+          return Action
+            .create({
+              name: 'unpause',
+              data1: evt.queue,
+              data2: queueIsAcw.acwTimeout,
+              data3: evt.uniqueid,
+              data4: 'ACW'
+            }, {
+              body: {
+                agent: agent.id,
+                type: 'ACW'
+              }
+            })
+            .catch(function(err) {
+              console.log(err);
+            });
+        }, queueIsAcw.acwTimeout * 1000);
+      })
+      .catch(function(err) {
+        console.log(err);
+      });
+  }
+}
+
+function synchCollection(model, array, uniqueProperty) {
+  var condition = {};
+  model.afterCreate(function(doc) {
+    condition[uniqueProperty] = doc[uniqueProperty]
+    updateCollection(array, doc, condition);
+  });
+  model.afterUpdate(function(doc) {
+    condition[uniqueProperty] = doc[uniqueProperty]
+    updateCollection(array, doc, condition);
+  });
+  model.afterDestroy(function(doc) {
+    condition[uniqueProperty] = doc[uniqueProperty]
+    _.remove(array, condition);
+  });
+}
+
+function updateCollection(collection, doc, condition) {
+  var oldItem = _.find(collection, condition);
+  var index = collection.indexOf(oldItem);
+  if (oldItem) {
+    _.merge(collection[index], doc);
+  } else {
+    collection.unshift(doc);
+  }
+}
+
+module.exports = ACW;
diff --git a/server/config/ami/agent.js b/server/config/ami/agent.js
index 5cc2e87..1aca579 100644
--- a/server/config/ami/agent.js
+++ b/server/config/ami/agent.js
@@ -1 +1,94 @@
\ No newline at end of file
+var moment = require('moment');
+var ReportAgent = require('../../models').ReportAgent;
+
+function Agent() {
+  console.log('Agent Initialization...');
+}
+
+Agent.prototype.called = function(evt) {
+  evt.lastevent = 'called';
+  evt.agentcalledAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return createAgent(evt);
+}
+
+Agent.prototype.connect = function(evt) {
+  evt.lastevent = 'connect';
+  evt.agentconnectAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  updateAgents(evt);
+  updateAgents({
+    agentringnoanswer: true,
+    agentringnoanswerAt: moment().format("YYYY-MM-DD HH:mm:ss"),
+    reason: 'answered_elsewhere',
+    lastevent: 'answered_elsewhere'
+  }, {
+    uniqueid: evt.uniqueid,
+    destuniqueid: {
+      $ne: evt.destuniqueid
+    },
+    reason: null
+  })
+}
+
+Agent.prototype.complete = function(evt) {
+  evt.lastevent = 'complete';
+  evt.agentcomplete = true;
+  evt.agentcompleteAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return updateAgents(evt);
+}
+
+Agent.prototype.dump = function(evt) {
+  evt.lastevent = 'dump';
+
+  evt.agentdump = true;
+  evt.agentdumpAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return updateAgents(evt);
+}
+
+Agent.prototype.ringnoanswer = function(evt) {
+  evt.lastevent = 'rejected';
+  evt.agentringnoanswer = true;
+  evt.agentringnoanswerAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  evt.reason = 'rejected';
+  return updateAgents(evt);
+}
+
+Agent.prototype.callerabandon = function(evt) {
+  evt.lastevent = 'abandoned';
+  evt.agentringnoanswer = true;
+  evt.agentringnoanswerAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  evt.reason = 'abandoned';
+  return updateAgents(evt, {
+    uniqueid: evt.uniqueid,
+    reason: null
+  });
+}
+
+function createAgent(evt) {
+  return ReportAgent
+    .create(evt)
+    .then(function(agent) {
+      console.log('[report] agent begin created');
+    })
+    .catch(function(err) {
+      console.error('[report] agent ' + err);
+    });
+}
+
+function updateAgents(evt, where) {
+  return ReportAgent
+    .update(evt, {
+      where: (where) ? where : {
+        uniqueid: evt.uniqueid,
+        destuniqueid: evt.destuniqueid
+      },
+      individualHooks: true
+    })
+    .then(function(agent) {
+      console.log('[agent] agent')
+    })
+    .catch(function(err) {
+      console.error('[agent] agent error: ' + err);
+    });
+}
+
+module.exports = Agent;
diff --git a/server/config/ami/call.js b/server/config/ami/call.js
index e012cb2..15e34b3 100644
--- a/server/config/ami/call.js
+++ b/server/config/ami/call.js
@@ -1 +1,26 @@
\ No newline at end of file
+function Call() {
+  console.log('Call Initialization...');
+}
+
+Call.prototype.add = function(evt) {
+  create(evt);
+}
+
+function create(evt) {
+  var call = require('../../models').ReportCall;
+  call
+    .findOrCreate({
+      where: {
+        uniqueid: evt.uniqueid
+      },
+      defaults: evt
+    })
+    .spread(function(call, created) {
+      console.log('[report] call created: ', created);
+    })
+    .catch(function(err) {
+      console.error('[report] call ' + err);
+    });
+}
+
+module.exports = Call;
diff --git a/server/config/ami/index.js b/server/config/ami/index.js
index 96d9888..3d7c92b 100644
--- a/server/config/ami/index.js
+++ b/server/config/ami/index.js
@@ -1 +1,217 @@
\ No newline at end of file
+'use strict';
+
+var Agent = require('./agent');
+var Action = require('./action');
+var Call = require('./call');
+var Member = require('./member');
+var QueueCaller = require('./queue_caller');
+var QueueMember = require('./queue_member');
+var Recording = require('./recording');
+var Trunk = require('./trunk');
+var Context = require('./voice_context');
+var ACW = require('./acw');
+var User = require('./user');
+var Transfer = require('./transfer');
+
+// When the ami disconnects.. perform this
+function onDisconnect(ami) {}
+
+// When the ami connects.. perform this
+function onConnect(ami) {}
+
+function getVariable(event) {
+  var keys = Object.keys(event.variable);
+  event.variable = keys[0];
+  return event;
+}
+
+module.exports = function(ami) {
+
+  var agent = new Agent();
+  var call = new Call();
+  var member = new Member();
+  var queuemember = new QueueMember();
+  var queuecaller = new QueueCaller();
+  var recording = new Recording();
+  var trunk = new Trunk(ami);
+  var context = new Context(ami);
+  var action = new Action(ami);
+  var acw = new ACW(ami);
+  var user = new User();
+  var tranfer = new Transfer();
+
+  // Asterisk Status Connect
+
+  // Raised when asterisk is connected
+  ami.on('connect', function() {
+    console.info('[%s:%s] AMI CONNECTED', ami.options.host, ami.options.port);
+    ami.options.connectedAt = new Date();
+    // Call onConnect.
+    onConnect(ami);
+  });
+
+  // Raised when asterisk is disconnected
+  ami.on('disconnect', function() {
+    // Call onDisconnect.
+    onDisconnect(ami);
+    console.info('[%s:%s] AMI DISCONNECTED', ami.options.host, ami.options.port);
+  });
+
+  // Raised when asterisk is closed
+  ami.on('close', function(flag) {
+    console.log('[%s:%s] AMI CLOSE: %s', ami.options.host, ami.options.port, flag);
+  });
+
+  // Raised when asterisk has an error
+  ami.on('error', function(err) {
+    console.log('[%s:%s] AMI ERROR: %s', ami.options.host, ami.options.port, err);
+  });
+
+  ami.on('managerevent', function(evt) {
+    // console.log(evt);
+  });
+
+  ami.on('devicestatechange', function(evt) {
+    // console.log(evt);
+    if (evt.device.indexOf('SIP/') === 0) {
+      user.stateUpdate(evt);
+    }
+  });
+
+  // Agent Status Events
+
+  // Raised when an Agent is notified of a member in the queue.
+  ami.on('agentcalled', function(evt) {
+    agent.called(evt);
+  });
+
+  // Raised when an agent has finished servicing a member in the queue.
+  ami.on('agentcomplete', function(evt) {
+    console.log('agentcomplete', evt);
+    acw.agentcomplete(evt);
+    agent.complete(evt);
+    queuecaller.complete(evt);
+  });
+
+  // Raised when an agent answers and is bridged to a member in the queue.
+  ami.on('agentconnect', function(evt) {
+    agent.connect(evt);
+  });
+
+  // Raised when an agent hangs up on a member in the queue.
+  ami.on('agentdump', function(evt) {
+    agent.dump(evt);
+  });
+
+  // Raised when an agent is notified of a member in the queue and fails to answer.
+  ami.on('agentringnoanswer', function(evt) {
+    agent.ringnoanswer(evt);
+  });
+
+  // Member Status Events in Queue
+
+  // Raised when a member is added to the queue.
+  ami.on('queuememberadded', function(evt) {
+    member.login(evt);
+    queuemember.added(evt);
+  });
+
+  // Raised when a member is paused/unpaused in the queue with a reason.
+  ami.on('queuememberpause', function(evt) {
+    member.pause(evt);
+    queuemember.pause(evt);
+  });
+
+  // Raised when a member's penalty is changed.
+  ami.on('queuememberpenalty', function(evt) {
+    queuemember.penalty(evt);
+  });
+
+  // Raised when a member is removed from the queue.
+  ami.on('queuememberremoved', function(evt) {
+    member.logout(evt);
+    queuemember.removed(evt);
+  });
+
+  // Raised when a member's ringinuse setting is changed.
+  ami.on('queuememberringinuse', function(evt) {
+    queuemember.ringinuse(evt);
+  });
+
+  // Raised when a Queue member's status has changed.
+  ami.on('queuememberstatus', function(evt) {
+    queuemember.status(evt);
+  });
+
+  // Queue Status Events
+
+  // Raised when a caller joins a Queue.
+  ami.on('queuecallerjoin', function(evt) {
+    queuecaller.join(evt);
+  });
+
+  // Raised when a caller abandons the queue.
+  ami.on('queuecallerabandon', function(evt) {
+    queuecaller.abandon(evt);
+    //queuemember.callerabandon(evt);
+    agent.callerabandon(evt);
+  });
+
+  // Raised when a caller leaves a Queue.
+  ami.on('queuecallerleave', function(evt) {
+    queuecaller.leave(evt);
+  });
+
+  // Cdr Status Events
+
+  // Raised when a CDR is generated.
+  ami.on('cdr', function(evt) {
+    call.add(evt);
+  });
+
+  // Hangup Status Events
+  ami.on('hangup', function(evt) {
+    //agent.hangup(evt);
+  });
+
+  // Recordings Events
+  ami.on('monitorstart', function(evt) {
+    // console.log('evt', evt);
+  });
+
+  ami.on('monitorstop', function(evt) {
+    // console.log('evt', evt);
+    recording.create(evt, 'outbound');
+  });
+
+  ami.on('blindtransfer', function(evt) {
+    console.log('blindtransfer', evt);
+    evt.type = 'blind';
+    tranfer.createBlind(evt);
+  });
+
+  ami.on('attendedtransfer', function(evt) {
+    console.log('attendedtransfer', evt);
+    evt.type = 'attended';
+    tranfer.createAttended(evt);
+  });
+
+  // Variable Events
+  ami.on('varset', function(evt) {
+    var event = getVariable(evt);
+    switch (event.variable) {
+      case 'mixmonitor_filename':
+        recording.create(event, 'inbound');
+        break;
+      case 'queuestatus':
+        queuecaller.exit(event);
+        break;
+      default:
+        // console.log('Variable not managed:', event.variable);
+        break;
+    }
+  });
+
+  require('./../fax').register(ami);
+  require('../../api/ami/ami.ami').register(ami);
+};
diff --git a/server/config/ami/member.js b/server/config/ami/member.js
index 014efd2..0c4d403 100644
--- a/server/config/ami/member.js
+++ b/server/config/ami/member.js
@@ -1 +1,111 @@
\ No newline at end of file
+'use strict';
+
+var moment = require('moment');
+
+var member = require('../../models').ReportMember;
+
+function Member() {
+  console.log('Member Initialization...');
+
+
+}
+
+Member.prototype.login = function (evt) {
+  evt.state = 0;
+  evt.statedesc = 'LOGIN';
+
+  evt.enterdate = moment().format("YYYY-MM-DD HH:mm:ss");
+
+  findOrCreate(evt);
+}
+
+Member.prototype.logout = function (evt) {
+  evt.state = 0;
+  evt.statedesc = 'LOGIN';
+
+  evt.exitdate = moment().format("YYYY-MM-DD HH:mm:ss");
+  findAndUpdate(evt);
+}
+
+Member.prototype.pause = function (evt) {
+  evt.state = 1;
+  evt.statedesc = 'PAUSE';
+
+  var paused = parseInt(evt.paused, 10);
+  if (paused) {
+    evt.enterdate = moment().format("YYYY-MM-DD HH:mm:ss");
+    findOrCreate(evt);
+  } else {
+    if (evt.reason) {
+      delete evt.reason;
+    }
+
+    evt.exitdate = moment().format("YYYY-MM-DD HH:mm:ss");
+    findAndUpdate(evt);
+  }
+}
+
+function findOrCreate(evt) {
+  // member.
+  // findOne({
+  //   where: {
+  //     queue: evt.queue,
+  //     membername: evt.membername,
+  //     state: evt.state,
+  //   }
+  // });
+
+  // member
+  //   .findOrCreate({
+  //     where: {
+  //       queue: evt.queue,
+  //       membername: evt.membername,
+  //       state: evt.state,
+  //       enterdate: {
+  //         $ne: null
+  //       },
+  //       exitdate: null
+  //     },
+  //     defaults: evt
+  //   })
+  //   .spread(function(mem, created) {
+  //     if (created) {
+  //       console.log('[member] member created');
+  //     } else {
+  //       console.log('[member] member exist');
+  //       mem
+  //         .updateAttributes({
+  //           exitdate: moment().format("YYYY-MM-DD HH:mm:ss")
+  //         })
+  //         .then(function(res) {
+  //           console.log('[member] member updated');
+  //           findOrCreate(evt);
+  //         });
+  //     }
+  //   });
+}
+
+function findAndUpdate(evt) {
+  // var member = require('../../models').ReportMember;
+  // member
+  //   .update(evt, {
+  //     where: {
+  //       queue: evt.queue,
+  //       membername: evt.membername,
+  //       state: evt.state,
+  //       enterdate: {
+  //         $ne: null
+  //       },
+  //       exitdate: null,
+  //       reason: null
+  //     }
+  //   })
+  //   .then(function(member) {
+  //     console.log('[member] member updated');
+  //   })
+  //   .catch(function(err) {
+  //     console.error('[member] member error: ' + err);
+  //   });
+}
+
+module.exports = Member;
diff --git a/server/config/ami/queue_caller.js b/server/config/ami/queue_caller.js
index dc2e90d..d5ebc2a 100644
--- a/server/config/ami/queue_caller.js
+++ b/server/config/ami/queue_caller.js
@@ -1 +1,69 @@
\ No newline at end of file
+var moment = require('moment');
+
+var ReportQueue = require('../../models').ReportQueue;
+
+function QueueCaller() {
+  console.log('QueueCaller Initialization...');
+}
+
+QueueCaller.prototype.join = function(evt) {
+  evt.queuecallerjoinAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return create(evt);
+}
+
+QueueCaller.prototype.leave = function(evt) {
+  evt.queuecallerleaveAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return findAndUpdate(evt);
+}
+
+QueueCaller.prototype.abandon = function(evt) {
+  evt.queuecallerabandon = true;
+  evt.queuecallerabandonAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return findAndUpdate(evt);
+}
+
+QueueCaller.prototype.complete = function(evt) {
+  evt.queuecallercomplete = true;
+  evt.queuecallercompleteAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return findAndUpdate(evt);
+}
+
+QueueCaller.prototype.exit = function(evt) {
+  evt.queuecallerabandon = false;
+  evt.queuecallerabandonAt = null;
+
+  evt.queuecallerexit = true;
+  evt.queuecallerexitAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  evt.queuecallerexitreason = evt.value.toLowerCase();
+
+  return findAndUpdate(evt);
+}
+
+function create(evt) {
+  return ReportQueue
+    .create(evt)
+    .then(function(queue) {
+      console.log('[report] queue created');
+    })
+    .catch(function(err) {
+      console.error('[report] queue ' + err);
+    });
+}
+
+function findAndUpdate(evt) {
+  return ReportQueue
+    .update(evt, {
+      where: {
+        uniqueid: evt.uniqueid
+      },
+      individualHooks: true
+    })
+    .then(function(queue) {
+      console.info('[queue_caller] queue_caller updated');
+    })
+    .catch(function(err) {
+      console.error('[queue_caller] queue_caller error: ' + err);
+    });
+}
+
+module.exports = QueueCaller;
diff --git a/server/config/ami/queue_member.js b/server/config/ami/queue_member.js
index f4ac262..f7417d4 100644
--- a/server/config/ami/queue_member.js
+++ b/server/config/ami/queue_member.js
@@ -1 +1,54 @@
\ No newline at end of file
+var moment = require('moment');
+var UserHasVoiceQueue = require('../../models').UserHasVoiceQueue;
+
+function QueueMember() {
+  console.log('QueueMember Initialization...');
+}
+
+QueueMember.prototype.added = function(evt) {
+  evt.logged = true;
+  evt.loggedAt = moment().format("YYYY-MM-DD HH:mm:ss");
+  return findAndUpdate(evt);
+}
+
+QueueMember.prototype.pause = function(evt) {
+  evt.pausedAt = (parseInt(evt.paused, 10)) ? moment().format("YYYY-MM-DD HH:mm:ss") : null;
+  return findAndUpdate(evt);
+}
+
+QueueMember.prototype.penalty = function(evt) {
+  return findAndUpdate(evt);
+}
+
+QueueMember.prototype.removed = function(evt) {
+  evt.logged = false;
+  evt.loggedAt = null;
+  return findAndUpdate(evt);
+}
+
+QueueMember.prototype.ringinuse = function(evt) {
+  return findAndUpdate(evt);
+}
+
+QueueMember.prototype.status = function(evt) {
+  return findAndUpdate(evt);
+}
+
+function findAndUpdate(evt, options) {
+  return UserHasVoiceQueue
+    .update(evt, {
+      where: {
+        queue: evt.queue,
+        membername: evt.membername
+      },
+      individualHooks: true
+    })
+    .then(function(member) {
+      console.log('[queuemember] update member');
+    })
+    .catch(function(err) {
+      console.error('[queuemember] update member error: ' + err);
+    });
+}
+
+module.exports = QueueMember;
diff --git a/server/config/ami/recording.js b/server/config/ami/recording.js
index 98c9e87..1d22902 100644
--- a/server/config/ami/recording.js
+++ b/server/config/ami/recording.js
@@ -1 +1,22 @@
\ No newline at end of file
+var VoiceRecording = require('../../models').VoiceRecording;
+
+function Recording() {
+  console.log('Recording Initialization...');
+}
+
+Recording.prototype.create = function(evt, type) {
+  create(evt, type);
+}
+
+function create(evt, type) {
+  evt.type = type;
+
+  return VoiceRecording
+    .create(evt)
+    .then(function() {})
+    .catch(function(err) {
+      console.error('rcording ' + err);
+    });
+}
+
+module.exports = Recording;
diff --git a/server/config/ami/socket.js b/server/config/ami/socket.js
index 3d755a1..0a82a2b 100644
--- a/server/config/ami/socket.js
+++ b/server/config/ami/socket.js
@@ -1 +1,19 @@
\ No newline at end of file
+/**
+ * Broadcast updates to client when the model changes
+ */
+
+'use strict';
+
+function Socket(socket, ami) {
+  console.log('New socket connection');
+
+  var ReportAgent = require('../../models').ReportAgent;
+
+  return {
+    test: function () {
+      console.log('test');
+    }
+  }
+}
+
+module.exports = Socket;
diff --git a/server/config/ami/transfer.js b/server/config/ami/transfer.js
index 6359572..9c903b6 100644
--- a/server/config/ami/transfer.js
+++ b/server/config/ami/transfer.js
@@ -1 +1,43 @@
\ No newline at end of file
+var moment = require('moment');
+var ReportTransfer = require('../../models').ReportTransfer;
+
+function Transfer() {
+  console.log('Transfer Initialization...');
+}
+
+Transfer.prototype.createBlind = function(evt) {
+  return ReportTransfer
+    .create(evt)
+    .then(function(member) {
+      console.log('[tranfer] created');
+    })
+    .catch(function(err) {
+      console.error('[tranfer] created error: ' + err);
+    });
+}
+
+Transfer.prototype.createAttended = function(evt) {
+  return ReportTransfer
+    .create({
+      type: 'attended',
+      transferercalleridnum: evt.origtransferercalleridnum,
+      transferercalleridname: evt.origtransferercalleridname,
+      transfererconnectedlinenum: evt.secondtransfererconnectedlinenum,
+      transfererconnectedlinename: evt.secondtransfererconnectedlinename,
+      transferercontext: evt.origtransferercontext,
+      transfererexten: evt.origtransfererexten,
+      transfereecontext: evt.secondtransferercontext,
+      transfereeexten: evt.secondtransfererexten,
+      transfereruniqueid: evt.secondtransfereruniqueid,
+      transfererlinkedid: evt.secondtransfererlinkedid,
+      extension: evt.origtransfererexten,
+    })
+    .then(function(member) {
+      console.log('[tranfer] created');
+    })
+    .catch(function(err) {
+      console.error('[tranfer] created error: ' + err);
+    });
+}
+
+module.exports = Transfer;
diff --git a/server/config/ami/trunk.js b/server/config/ami/trunk.js
index 053c88c..638620f 100644
--- a/server/config/ami/trunk.js
+++ b/server/config/ami/trunk.js
@@ -1 +1,124 @@
\ No newline at end of file
+var TrunkModel = require('../../models').Trunk;
+var fs = require('fs');
+var util = require('util');
+var path = require('path');
+var config = require('../../config/environment');
+var _ = require('lodash');
+
+function Trunk(ami) {
+  console.log('Trunk Initialization...');
+  TrunkModel.afterCreate(function(doc) {
+    trunksRewrite(doc, ami, false);
+  })
+  TrunkModel.afterUpdate(function(doc) {
+    trunksRewrite(doc, ami, false);
+  })
+  TrunkModel.afterDelete(function(doc) {
+    trunksRewrite(doc, ami, true);
+  })
+}
+
+function trunksRewrite(doc, ami, deleteTrunk) {
+  var trunksFile = '';
+  var trunksRegistersFile = '';
+  var registryString = 'register => %s\n';
+  if (!deleteTrunk) {
+    var newTrunk = _.clone(doc.dataValues);
+    delete newTrunk.createdAt;
+    delete newTrunk.updatedAt;
+    trunksFile += util.format('[%s]\n', newTrunk.name);
+    if (newTrunk.otherFields) {
+      trunksFile += util.format('%s\n', newTrunk.otherFields);
+      delete newTrunk.otherFields;
+    }
+    for (var key in newTrunk) {
+      if (newTrunk[key]) {
+        switch (key) {
+          case 'allow':
+            var codecs = newTrunk[key].split(';');
+            codecs.forEach(function(elem) {
+              trunksFile += util.format('%s=%s\n', key, elem);
+            })
+            break;
+          default:
+            trunksFile += util.format('%s=%s\n', key, newTrunk[key]);
+        }
+      }
+    }
+    if (newTrunk.registry) {
+      trunksRegistersFile += util.format(registryString, newTrunk.registry);
+    }
+  }
+  return TrunkModel
+    .findAll({
+      where: {
+        name: {
+          $ne: doc.name
+        }
+      }
+    })
+    .then(function(trunks) {
+      var oldTrunks = _.map(trunks, 'dataValues');
+      oldTrunks.forEach(function(element) {
+        delete element.createdAt;
+        delete element.updatedAt;
+        trunksFile += util.format('[%s]\n', element.name);
+        if (element.otherFields) {
+          trunksFile += util.format('%s\n', element.otherFields);
+          delete element.otherFields;
+        }
+        for (var key in element) {
+          if (element[key]) {
+            switch (key) {
+              case 'allow':
+                var codecs = element[key].split(';');
+                codecs.forEach(function(elem) {
+                  trunksFile += util.format('%s=%s\n', key, elem);
+                })
+                break;
+              default:
+                trunksFile += util.format('%s=%s\n', key, element[key]);
+            }
+          }
+        }
+        if (element.registry) {
+          trunksRegistersFile += util.format(registryString, element.registry);
+        }
+      });
+      fs.writeFile(path.join(config.asteriskConf, 'sip_xcally_trunks.conf'), trunksFile, {
+        flags: 'w',
+        mode: parseInt('0777', 8)
+      }, function(err) {
+        if (err) throw err;
+        console.log("Trunks file saved!");
+        fs.writeFile(path.join(config.asteriskConf, 'sip_xcally_registers.conf'), trunksRegistersFile, {
+          flags: 'w',
+          mode: parseInt('0777', 8)
+        }, function(err) {
+          if (err) throw err;
+          reload(ami);
+          console.log("Trunks register file saved!");
+        });
+      });
+
+
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function reload(ami) {
+  ami.action({
+    Action: 'Reload'
+  }, function(err, res) {
+    if (err) {
+      console.error(err);
+    } else {
+      console.log(res);
+    }
+  });
+}
+
+
+module.exports = Trunk;
diff --git a/server/config/ami/user.js b/server/config/ami/user.js
index 626958e..45d1db1 100644
--- a/server/config/ami/user.js
+++ b/server/config/ami/user.js
@@ -1 +1,33 @@
\ No newline at end of file
+var moment = require('moment');
+var UserModel = require('../../models').User;
+
+function User() {
+  console.log('QueueMember Initialization...');
+}
+
+User.prototype.stateUpdate = function(evt) {
+  var obj = {
+    status: evt.state,
+    statusAt: moment().format("YYYY-MM-DD HH:mm:ss")
+  }
+  return findAndUpdate(obj, evt.device.replace('SIP/', ''));
+}
+
+
+function findAndUpdate(obj, agentName) {
+  return UserModel
+    .update(obj, {
+      where: {
+        name: agentName
+      },
+      individualHooks: true
+    })
+    .then(function(member) {
+      console.log('[user] status update');
+    })
+    .catch(function(err) {
+      console.error('[user] status update error: ' + err);
+    });
+}
+
+module.exports = User;
diff --git a/server/config/ami/voice_context.js b/server/config/ami/voice_context.js
index 759307e..4b4a8ef 100644
--- a/server/config/ami/voice_context.js
+++ b/server/config/ami/voice_context.js
@@ -1 +1,92 @@
\ No newline at end of file
+var VoiceContext = require('../../models').VoiceContext;
+var VoiceExtension = require('../../models').VoiceExtension;
+var fs = require('fs');
+var util = require('util');
+var path = require('path');
+var config = require('../../config/environment');
+
+function Context(ami) {
+  VoiceContext.beforeUpdate(function(updatedContext) {
+    if (updatedContext.changed('name')) {
+      throw new Error("You can't modify a context name");
+    }
+    if (updatedContext.defaultEntry) {
+      throw new Error("You can't modify a default context");
+    }
+  });
+
+  VoiceContext.beforeDelete(function(context) {
+    if (context.defaultEntry) {
+      throw new Error("You can't delete a default context");
+    }
+  });
+  VoiceContext.afterCreate(function(doc) {
+    rewriteContexts(doc, ami, false);
+    createHangup(doc);
+  })
+
+  VoiceContext.afterDelete(function(doc) {
+    rewriteContexts(doc, ami, true);
+  })
+}
+
+function rewriteContexts(doc, ami, deleteContext) {
+  var contextValues = '';
+  if (!deleteContext) {
+    contextValues = util.format('[%s]\nswitch => Realtime\n', doc.name);
+  }
+  VoiceContext
+    .findAll({
+      where: {
+        id: {
+          $ne: doc.id
+        }
+      }
+    })
+    .then(function(contexts) {
+      contexts.forEach(function(element) {
+        contextValues += util.format('[%s]\nswitch => Realtime\n', element.name);
+      });
+      fs.writeFile(path.join(config.asteriskConf, 'extensions_xcally_contexts.conf'), contextValues, {
+        flags: 'w',
+        mode: parseInt('0777', 8)
+      }, function(err) {
+        if (err) throw err;
+        console.log("Contexts file saved!");
+        ami.action({
+          Action: 'Reload',
+        }, function(err, res) {
+          if (err) {
+            console.error(err);
+          } else {
+            console.log(res);
+          }
+        });
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function createHangup(doc) {
+  var data = {
+    context: doc.name,
+    exten: 'h',
+    priority: 'n',
+    VoiceContextId: doc.id,
+    type: 'any',
+    app: 'Hangup'
+  };
+
+  VoiceExtension
+    .create(data)
+    .then(function(res) {
+      console.log('hangup extension created');
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+module.exports = Context;
diff --git a/server/config/analytics.js b/server/config/analytics.js
index 4ab5cf9..e69de29 100644
--- a/server/config/analytics.js
+++ b/server/config/analytics.js
@@ -1 +0,0 @@
-var _0x27ec=[]
\ No newline at end of file
diff --git a/server/config/autodialer/index.js b/server/config/autodialer/index.js
index 6a4f7c2..a47295f 100644
--- a/server/config/autodialer/index.js
+++ b/server/config/autodialer/index.js
@@ -1 +1,125 @@
\ No newline at end of file
+'use strict';
+var util = require('util');
+var Process = require('./process');
+
+module.exports = function(ami) {
+
+  console.log('Autodialer Starting...');
+  var proc = new Process();
+
+  proc.getProcesses(function(res, err) {
+    if(err)
+    {
+      console.log(err);
+      return;
+    }
+    // console.log(res.Contacts);
+    // if (util.isArray(res.Contacts))
+    // {
+    //   var contact = res.Contacts;
+    //   console.log(contact);
+    // }
+    // else
+    // {
+    //   var contacts = res.Contacts;
+    //   if (contacts.length)
+    //   {
+    //     contacts.forEach(function(contact) {
+    //       console.log(contact);
+    //     })
+    //   }
+    // }
+
+
+
+  });
+
+}
+
+// var _ = require('lodash');
+//
+// var agents = ['SIP/massimiliano.bungaro', 'SIP/daniele.cinti'];
+// var channels = [];
+// var peers = [];
+// var contacts = ['600', '600', '600'];
+//
+// var ami;
+//
+// function coreshow() {
+//   channels = [];
+//   if (contacts.length > 0) {
+//     ami.action({
+//       action: 'CoreShowChannels'
+//     }, function(err, res) {
+//       if (err) {
+//         console.log(err);
+//       }
+//       console.log(res);
+//     });
+//   }
+//
+// }
+//
+// function peerstatus() {
+//   peers = [];
+//   if (contacts.length > 0) {
+//     ami.action({
+//       action: 'SIPpeerstatus'
+//     }, function(err, res) {
+//       if (err) {
+//         console.log(err);
+//       }
+//       console.log(res);
+//     })
+//   }
+// }
+//
+// module.exports = function(asterisk_manager) {
+//
+//   ami = asterisk_manager;
+//
+//   peerstatus();
+//
+//   ami.on('peerstatus', function(evt) {
+//     if (evt.peerstatus == 'Reachable') {
+//       peers.push(evt.peer);
+//     }
+//   });
+//
+//   ami.on('coreshowchannel', function(evt) {
+//     channels.push(evt.channel.split('-')[0]);
+//   });
+//
+//   ami.on('sippeerstatuscomplete', function(evt) {
+//     coreshow();
+//   });
+//
+//   ami.on('coreshowchannelscomplete', function(evt) {
+//     console.log('Agents registered ', peers);
+//     console.log('Agents in call ', channels);
+//
+//
+//     var agent = _.find(agents, function(agent) {
+//       return !_.includes(channels, agent);
+//     });
+//
+//     if (agent) {
+//       if (_.includes(peers, agent)) {
+//         ami.action({
+//           action: 'originate',
+//           channel: agent,
+//           exten: contacts.pop(),
+//           context: 'demo',
+//           priority: 1,
+//           async: true
+//         }, function(err, res) {
+//           if (err) {
+//             console.log(err);
+//           }
+//           console.log(res);
+//         });
+//       }
+//     }
+//     peerstatus();
+//   });
+// };
diff --git a/server/config/autodialer/process.js b/server/config/autodialer/process.js
index 8291f96..7974168 100644
--- a/server/config/autodialer/process.js
+++ b/server/config/autodialer/process.js
@@ -1 +1,26 @@
\ No newline at end of file
+var AutodialerProcess = require('../../models').AutodialerProcess;
+var Contact = require('../../models').Contact;
+
+function Process() {
+  console.log('Process Initialization...');
+}
+
+Process.prototype.getProcesses = function(callback) {
+
+  AutodialerProcess
+  .findAll({
+    where: {
+      enable: true
+    },
+    include: [Contact]
+  })
+  .then(function(res) {
+    callback(res);
+  })
+  .catch(function(err) {
+    callback(null, err);
+  });
+
+}
+
+module.exports = Process;
diff --git a/server/config/automations/fax.js b/server/config/automations/fax.js
index c57ce3f..d31e0c8 100644
--- a/server/config/automations/fax.js
+++ b/server/config/automations/fax.js
@@ -1 +1,155 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var config = require('../environment');
+var moment = require('moment');
+// var stringifyObject = require('stringify-object');
+
+var sequelize = require('../../models').sequelize;
+var Automation = require('../../models').Automation;
+var FaxRoom = require('../../models').FaxRoom;
+var Settings = require('../../models').Settings;
+var automationTimeout;
+var activeAutomations = {};
+
+function executeAutomation(faxAutomation) {
+  var query = {
+    where: {
+      $and: [],
+      $or: []
+    }
+  };
+  var actions = {};
+  faxAutomation.All.forEach(function(condition) {
+    query.where.$and.push(getCondition(condition.dataValues));
+  });
+  faxAutomation.Any.forEach(function(condition) {
+    query.where.$or.push(getCondition(condition.dataValues));
+  });
+
+  return FaxRoom
+    .findAll(query)
+    .then(function(faxes) {
+      faxes.forEach(function(fax) {
+        faxAutomation.Actions.forEach(function(action) {
+          switch (action.action) {
+            case 'status':
+              setStatus(fax.id, action.data1);
+              break;
+            default:
+          }
+        });
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function setStatus(ticketId, newStatus) {
+  return FaxRoom.update({
+      status: newStatus,
+    }, {
+      where: {
+        id: ticketId
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getCondition(condition) {
+  var result = {};
+  switch (condition.field) {
+    case 'status':
+      switch (condition.operator) {
+        case 'equals':
+          result[condition.field] = condition.value;
+          break;
+        case 'not_equals':
+          result[condition.field] = {
+            $ne: condition.value
+          };
+          break;
+        default:
+      }
+      break;
+    case 'createdAt':
+      var compareDate = moment().subtract(parseInt(condition.value, 10), 'hours').format("YYYY-MM-DD HH:mm:ss");
+      switch (condition.operator) {
+        case 'greater':
+          result[condition.field] = {
+            lt: compareDate
+          };
+          break;
+        case 'greater_or_equal':
+          result[condition.field] = {
+            $lte: compareDate
+          };
+          break;
+        case 'less':
+          result[condition.field] = {
+            $gt: compareDate
+          };
+          break;
+        case 'less_or_equal':
+          result[condition.field] = {
+            $gte: compareDate
+          };
+          break;
+        default:
+      }
+      break;
+    default:
+  }
+  return result;
+}
+
+function createInterval(faxAutomation) {
+  activeAutomations['interval' + faxAutomation.id] = setInterval(function() {
+    executeAutomation(faxAutomation);
+  }, faxAutomation.timeout * 1000);
+}
+
+module.exports = function() {
+
+  Automation.afterCreate(function(doc, options) {
+    if (doc.status && doc.channel === 'fax') {
+      createInterval(doc);
+    }
+  });
+
+  Automation.afterUpdate(function(doc, options) {
+    if (activeAutomations['interval' + doc.id]) {
+      clearInterval(activeAutomations['interval' + doc.id]);
+    }
+    if (doc.status && doc.channel === 'fax') {
+      createInterval(doc);
+    }
+  });
+
+  Automation.afterDelete(function(doc, options) {
+    if (activeAutomations['interval' + doc.id]) {
+      clearInterval(activeAutomations['interval' + doc.id]);
+    }
+  });
+
+  Automation
+    .scope('all')
+    .findAll({
+      where: {
+        status: true,
+        channel: 'fax'
+      }
+    })
+    .then(function(faxAutomations) {
+
+      faxAutomations.forEach(function(faxAutomation) {
+        createInterval(faxAutomation);
+      });
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+};
diff --git a/server/config/automations/mail.js b/server/config/automations/mail.js
index 8038600..dfd4a14 100644
--- a/server/config/automations/mail.js
+++ b/server/config/automations/mail.js
@@ -1 +1,154 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var config = require('../environment');
+var moment = require('moment');
+// var stringifyObject = require('stringify-object');
+
+var sequelize = require('../../models').sequelize;
+var Automation = require('../../models').Automation;
+var MailRoom = require('../../models').MailRoom;
+var Settings = require('../../models').Settings;
+var activeAutomations = {};
+
+function executeAutomation(mailAutomation) {
+  var query = {
+    where: {
+      $and: [],
+      $or: []
+    }
+  };
+  var actions = {};
+  mailAutomation.All.forEach(function(condition) {
+    query.where.$and.push(getCondition(condition.dataValues));
+  });
+  mailAutomation.Any.forEach(function(condition) {
+    query.where.$or.push(getCondition(condition.dataValues));
+  });
+
+  return MailRoom
+    .findAll(query)
+    .then(function(mails) {
+      mails.forEach(function(mail) {
+        mailAutomation.Actions.forEach(function(action) {
+          switch (action.action) {
+            case 'status':
+              setStatus(mail.id, action.data1);
+              break;
+            default:
+          }
+        });
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function setStatus(ticketId, newStatus) {
+  return MailRoom.update({
+      status: newStatus,
+    }, {
+      where: {
+        id: ticketId
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function getCondition(condition) {
+  var result = {};
+  switch (condition.field) {
+    case 'status':
+      switch (condition.operator) {
+        case 'equals':
+          result[condition.field] = condition.value;
+          break;
+        case 'not_equals':
+          result[condition.field] = {
+            $ne: condition.value
+          };
+          break;
+        default:
+      }
+      break;
+    case 'createdAt':
+      var compareDate = moment().subtract(parseInt(condition.value, 10), 'hours').format("YYYY-MM-DD HH:mm:ss");
+      switch (condition.operator) {
+        case 'greater':
+          result[condition.field] = {
+            lt: compareDate
+          };
+          break;
+        case 'greater_or_equal':
+          result[condition.field] = {
+            $lte: compareDate
+          };
+          break;
+        case 'less':
+          result[condition.field] = {
+            $gt: compareDate
+          };
+          break;
+        case 'less_or_equal':
+          result[condition.field] = {
+            $gte: compareDate
+          };
+          break;
+        default:
+      }
+      break;
+    default:
+  }
+  return result;
+}
+
+function createInterval(mailAutomation) {
+  activeAutomations['interval' + mailAutomation.id] = setInterval(function() {
+    executeAutomation(mailAutomation);
+  }, mailAutomation.timeout * 1000);
+}
+
+module.exports = function() {
+
+  Automation.afterCreate(function(doc, options) {
+    if (doc.status && doc.channel === 'mail') {
+      createInterval(doc);
+    }
+  });
+
+  Automation.afterUpdate(function(doc, options) {
+    if (activeAutomations['interval' + doc.id]) {
+      clearInterval(activeAutomations['interval' + doc.id]);
+    }
+    if (doc.status && doc.channel === 'mail') {
+      createInterval(doc);
+    }
+  });
+
+  Automation.afterDelete(function(doc, options) {
+    if (activeAutomations['interval' + doc.id]) {
+      clearInterval(activeAutomations['interval' + doc.id]);
+    }
+  });
+
+  Automation
+    .scope('all')
+    .findAll({
+      where: {
+        status: true,
+        channel: 'mail'
+      }
+    })
+    .then(function(mailAutomations) {
+
+      mailAutomations.forEach(function(mailAutomation) {
+        createInterval(mailAutomation);
+      });
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+};
diff --git a/server/config/express.js b/server/config/express.js
index a0a463c..1b2d7a3 100644
--- a/server/config/express.js
+++ b/server/config/express.js
@@ -1 +1,74 @@
\ No newline at end of file
+/**
+ * Express configuration
+ */
+
+'use strict';
+
+var express = require('express');
+var favicon = require('serve-favicon');
+var morgan = require('morgan');
+var compression = require('compression');
+var bodyParser = require('body-parser');
+var queryParser = require('express-query-int');
+var methodOverride = require('method-override');
+var cookieParser = require('cookie-parser');
+var errorHandler = require('errorhandler');
+var path = require('path');
+var passport = require('passport');
+var session = require('express-session');
+var flash = require('connect-flash');
+var SessionStore = require('express-mysql-session');
+var session = require('express-session');
+var config = require('./environment');
+
+module.exports = function (app) {
+  var env = app.get('env');
+
+  app.set('views', config.root + '/server/views');
+  app.engine('html', require('ejs').renderFile);
+  app.set('view engine', 'html');
+  app.use(compression());
+  app.use(bodyParser.urlencoded({
+    extended: false
+  }));
+  app.use(bodyParser.json());
+  // app.use(queryParser());
+  app.use(methodOverride());
+  app.use(cookieParser());
+  app.use(passport.initialize());
+
+  // Express/MySQL session storage
+  app.use(session({
+    secret: config.session.secret,
+    store: new SessionStore({
+      host: config.db.host,
+      port: config.db.port,
+      user: config.db.username,
+      password: config.db.password,
+      database: config.db.database
+    }),
+    cookie: config.session.cookie,
+    name: config.session.name,
+    resave: true,
+    saveUninitialized: true
+  }));
+
+  if ('production' === env) {
+    app.use(favicon(path.join(config.root, 'public', 'favicon.ico')));
+    app.use(express.static(path.join(config.root, 'public')));
+    app.set('appPath', config.root + '/public');
+    app.use(morgan('dev'));
+  }
+
+  if ('development' === env || 'test' === env) {
+    app.use(require('connect-livereload')());
+    app.use(express.static(path.join(config.root, '.tmp')));
+    app.use(express.static(path.join(config.root, 'client')));
+    app.set('appPath', 'client');
+    app.use(morgan('dev'));
+    app.use(errorHandler()); // Error handler - has to be last
+  }
+
+  // Connect flash for flash messages
+  app.use(flash());
+};
diff --git a/server/config/fax.js b/server/config/fax.js
index 3a34622..c64343b 100644
--- a/server/config/fax.js
+++ b/server/config/fax.js
@@ -1 +1,240 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var uuidLib = require('node-uuid');
+var ConvertTiff = require('tiff-to-png');
+var exec = require('child_process').exec;
+var fs = require('fs');
+var path = require('path');
+
+var FaxMessage = require('../models').FaxMessage;
+var FaxRoom = require('../models').FaxRoom;
+var Contact = require('../models').Contact;
+var Event = require('../models').Event;
+var config = require('./environment');
+
+exports.register = function(ami) {
+
+  ami.on('receivefax', function(evt) {
+
+    FaxRoom
+      .create({
+        status: 'NEW',
+        from: evt.remotestationid,
+        FaxAccountId: 1, //TODO Handle account ID
+      })
+      .then(function(faxRoom) {
+
+        evt.status = 'RECEIVED';
+        evt.FaxRoomId = faxRoom.id;
+
+        FaxMessage
+          .create(evt)
+          .then(function(faxMessage) {
+
+            Event
+              .create({
+                name: 'INCOMING',
+                channel: 'FAX',
+                FaxRoomId: faxRoom.id,
+                FaxAccountId: 1 //TODO Handle account ID
+              })
+              .catch(function(err) {
+                console.error(err);
+              });
+
+            Contact
+              .findOrCreate({
+                where: {
+                  phone: evt.remotestationid
+                },
+                defaults: {
+                  phone: evt.remotestationid,
+                  fullname: evt.remotestationid
+                }
+              })
+              .spread(function(contact, created) {
+                faxMessage.setFrom(contact);
+              });
+
+            Contact
+              .findOrCreate({
+                where: {
+                  phone: evt.localstationid
+                },
+                defaults: {
+                  phone: evt.localstationid,
+                  fullname: evt.localstationid
+                }
+              })
+              .spread(function(contact, created) {
+                faxMessage.setTo(contact);
+              });
+
+            //Convert tiff to png
+            var uuid = uuidLib.v4();
+            var inputPath = evt.filename; //Restore for normal usage
+            // var inputPath = '/tmp/sample.tif' //Used for testing
+            var resultPath = path.join(config.root, 'server', 'files', 'fax', 'inbound', uuid + '.pdf');
+            var command = 'convert ' + inputPath + ' ' + resultPath;
+            fs.exists(inputPath, function(exists) {
+              if (exists) {
+                console.error('The file ' + inputPath + ' exits');
+                //Install ImageMagick for doing that!
+                exec(command, function(err) {
+                  if (err) {
+                    console.error(err);
+                  } else {
+                    faxMessage.updateAttributes({
+                        filenamePDF: uuid + '.pdf'
+                      })
+                      .then(function() {
+                        console.error('The file ' + inputPath + ' was converted in PDF');
+                      })
+                      .catch(function(err) {
+                        console.error(err);
+                      });
+                  }
+                });
+              } else {
+                console.error('The file ' + inputPath + ' does not exits');
+              }
+            });
+          })
+          .catch(function(err) {
+            console.error(err);
+          });
+
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  });
+
+  ami.on('sendfax', function(evt) {
+    console.log('***SendFAX***');
+
+    FaxMessage
+      .findOne({
+        where: {
+          uniqueid: evt.uniqueid
+        }
+      })
+      .then(function(faxMessage) {
+        if (faxMessage) {
+          faxMessage.updateAttributes(evt);
+        }
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+
+  });
+
+  ami.on('varset', function(evt) {
+    if (evt.variable.hasOwnProperty('faxstatus')) {
+      FaxMessage
+        .findOne({
+          where: {
+            uniqueid: evt.uniqueid
+          }
+        })
+        .then(function(faxMessage) {
+          if (faxMessage) {
+            console.log('VarSet: ', 'status', evt.value);
+            faxMessage.updateAttributes({
+              status: evt.value == 'FAILED' ? 'FAILED' : 'SENT'
+            });
+          }
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    } else if (evt.variable.hasOwnProperty('faxerror')) {
+      FaxMessage
+        .findOne({
+          where: {
+            uniqueid: evt.uniqueid
+          }
+        })
+        .then(function(faxMessage) {
+          if (faxMessage) {
+            console.log('VarSet: ', 'error', evt.value);
+            faxMessage.updateAttributes({
+              error: evt.value
+            });
+          }
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    } else if (evt.variable.hasOwnProperty('faxstatusstring')) {
+      FaxMessage
+        .findOne({
+          where: {
+            uniqueid: evt.uniqueid
+          }
+        })
+        .then(function(faxMessage) {
+          if (faxMessage) {
+            console.log('VarSet: ', 'laststatus', evt.value);
+            faxMessage.updateAttributes({
+              laststatus: evt.value
+            });
+          }
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    }
+
+  });
+
+  ami.on('userevent', function(evt) {
+    if (evt.userevent === 'Fax') {
+      FaxMessage
+        .findOne({
+          where: {
+            uuid: evt.uuid
+          }
+        })
+        .then(function(faxMessage) {
+          if (faxMessage) {
+            evt.status = 'SENDING';
+            faxMessage.updateAttributes(evt);
+          }
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    }
+
+  });
+
+  ami.on('faxstatus', function(evt) {
+    FaxMessage
+      .findOne({
+        where: {
+          uniqueid: evt.uniqueid
+        }
+      })
+      .then(function(faxMessage) {
+        if (faxMessage) {
+
+          console.log('FAXStatus: ', evt.uniqueid, evt.status);
+
+          faxMessage.updateAttributes({
+            operation: evt.operation,
+            laststatus: evt.status
+          });
+        }
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+
+  })
+
+  require('../api/fax_message/fax_message.ami').register(ami);
+
+};
diff --git a/server/config/history.js b/server/config/history.js
index eeaa290..160778b 100644
--- a/server/config/history.js
+++ b/server/config/history.js
@@ -1 +1,328 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var moment = require('moment');
+
+var sequelize = require('../models').sequelize;
+
+var ReportAgent = require('../models').ReportAgent;
+var ReportCall = require('../models').ReportCall;
+var ReportDial = require('../models').ReportDial;
+var ReportMember = require('../models').ReportMember;
+var ReportQueue = require('../models').ReportQueue;
+var ReportIntegration = require('../models').ReportIntegration;
+var ReportMailSession = require('../models').ReportMailSession;
+
+var ReportAgentHistory = require('../models').history.ReportAgentHistory;
+var ReportCallHistory = require('../models').history.ReportCallHistory;
+var ReportDialHistory = require('../models').history.ReportDialHistory;
+var ReportMemberHistory = require('../models').history.ReportMemberHistory;
+var ReportQueueHistory = require('../models').history.ReportQueueHistory;
+var ReportIntegrationHistory = require('../models').history.ReportIntegrationHistory;
+var ReportMailSessionHistory = require('../models').history.ReportMailSessionHistory;
+
+function ReportAgentInReportAgentHistory() {
+  ReportAgent
+    .findAll({
+      where: {
+        createdAt: {
+          $lte: moment().format("YYYY-MM-DD HH:mm:ss")
+        },
+        reason: {
+          $ne: null
+        }
+      },
+      raw: true
+    })
+    .then(function(rows) {
+      console.log('[report_agent] found: ' + rows.length);
+      rows.forEach(function(row, key) {
+        ReportAgentHistory
+          .create(row)
+          .then(function() {
+            console.log('[report_agent] ' + row.id + ' row copied');
+            ReportAgent.destroy({
+              where: {
+                id: row.id
+              },
+              individualHooks: true
+            }).then(function(res) {
+              console.log('[report_agent] ' + row.id + ' row deleted');
+            }).catch(function(err) {
+              console.log('[report_agent] error: ' + err)
+            });
+          })
+          .catch(function(err) {
+            console.log('[report_agent] error: ' + err);
+          });
+      });
+    })
+    .catch(function(err) {
+      console.error('[report_agent] error: ' + err);
+    });
+}
+
+function ReportCallInReportCallHistory() {
+  ReportCall
+    .findAll({
+      where: {
+        createdAt: {
+          $lte: moment().format("YYYY-MM-DD HH:mm:ss")
+        }
+      },
+      raw: true
+    })
+    .then(function(rows) {
+      rows.forEach(function(row, key) {
+        console.log('[report_call] found: ' + rows.length);
+        ReportCallHistory
+          .create(row)
+          .then(function() {
+            console.log('[c] ' + row.id + ' row copied');
+            ReportCall.destroy({
+              where: {
+                id: row.id
+              },
+              individualHooks: true
+            }).then(function(res) {
+              console.log('[report_call] ' + row.id + ' row deleted');
+            }).catch(function(err) {
+              console.log('[report_call] error: ' + err)
+            });
+          })
+          .catch(function(err) {
+            console.log('[report_call] error: ' + err);
+          });
+      });
+    })
+    .catch(function(err) {
+      console.error('[report_call] error: ' + err);
+    });
+}
+
+function ReportDialInReportDialHistory() {
+  ReportDial
+    .findAll({
+      where: {
+        createdAt: {
+          $lte: moment().format("YYYY-MM-DD HH:mm:ss")
+        },
+        dialstatus: {
+          $ne: null
+        }
+      },
+      raw: true
+    })
+    .then(function(rows) {
+      console.log('[report_dial] found: ' + rows.length);
+      rows.forEach(function(row, key) {
+        ReportDialHistory
+          .create(row)
+          .then(function() {
+            console.log('[report_dial] ' + row.id + ' row copied');
+            ReportDial.destroy({
+              where: {
+                id: row.id
+              },
+              individualHooks: true
+            }).then(function(res) {
+              console.log('[report_dial] ' + row.id + ' row deleted');
+            }).catch(function(err) {
+              console.log('[report_dial] error: ' + err)
+            });
+          })
+          .catch(function(err) {
+            console.log('[report_dial] error: ' + err);
+          });
+      });
+    })
+    .catch(function(err) {
+      console.error('[report_dial] error: ' + err);
+    });
+}
+
+function ReportMemberInReportMemberHistory() {
+  ReportMember
+    .findAll({
+      where: {
+        createdAt: {
+          $lte: moment().format("YYYY-MM-DD HH:mm:ss")
+        },
+        exitdate: {
+          $ne: null
+        }
+      },
+      raw: true
+    })
+    .then(function(rows) {
+      console.log('[report_member] found: ' + rows.length);
+      rows.forEach(function(row, key) {
+        ReportMemberHistory
+          .create(row)
+          .then(function() {
+            console.log('[report_member] ' + row.id + ' row copied');
+            ReportMember.destroy({
+              where: {
+                id: row.id
+              },
+              individualHooks: true
+            }).then(function(res) {
+              console.log('[report_member] ' + row.id + ' row deleted');
+            }).catch(function(err) {
+              console.log('[report_member] error: ' + err)
+            });
+          })
+          .catch(function(err) {
+            console.log('[report_member] error: ' + err);
+          });
+      });
+    })
+    .catch(function(err) {
+      console.error('[report_member] error: ' + err);
+    });
+}
+
+function ReportQueueInReportQueueHistory() {
+  ReportQueue
+    .findAll({
+      where: {
+        createdAt: {
+          $lte: moment().format("YYYY-MM-DD HH:mm:ss")
+        },
+        queuecallerleaveAt: {
+          $ne: null
+        }
+      },
+      raw: true
+    })
+    .then(function(rows) {
+      console.log('[report_queue] found: ' + rows.length);
+      rows.forEach(function(row, key) {
+        ReportQueueHistory
+          .create(row)
+          .then(function() {
+            console.log('[report_queue] ' + row.id + ' row copied');
+            ReportQueue.destroy({
+              where: {
+                id: row.id
+              },
+              individualHooks: true
+            }).then(function(res) {
+              console.log('[report_queue] ' + row.id + ' row deleted');
+            }).catch(function(err) {
+              console.log('[report_queue] error: ' + err)
+            });
+          })
+          .catch(function(err) {
+            console.log('[report_queue] error: ' + err);
+          });
+      });
+    })
+    .catch(function(err) {
+      console.error('[report_queue] error: ' + err);
+    });
+}
+
+function ReportIntegrationInReportIntegrationHistory() {
+  ReportIntegration
+    .findAll({
+      where: {
+        createdAt: {
+          $lte: moment().format("YYYY-MM-DD HH:mm:ss")
+        }
+      },
+      raw: true
+    })
+    .then(function(rows) {
+      rows.forEach(function(row, key) {
+        console.log('[report_integration] found: ' + rows.length);
+        ReportIntegrationHistory
+          .create(row)
+          .then(function() {
+            console.log('[c] ' + row.id + ' row copied');
+            ReportIntegration.destroy({
+              where: {
+                id: row.id
+              },
+              individualHooks: true
+            }).then(function(res) {
+              console.log('[report_integration] ' + row.id + ' row deleted');
+            }).catch(function(err) {
+              console.log('[report_integration] error: ' + err)
+            });
+          })
+          .catch(function(err) {
+            console.log('[report_integration] error: ' + err);
+          });
+      });
+    })
+    .catch(function(err) {
+      console.error('[report_integration] error: ' + err);
+    });
+}
+
+function ReportMailSessionInReportMailSessionHistory() {
+  return sequelize.transaction(function(t) {
+    var _reportMailSessions;
+    return ReportMailSession
+      .findAll({
+        where: {
+          createdAt: {
+            $lte: moment().format("YYYY-MM-DD HH:mm:ss")
+          },
+          leaveAt: {
+            $ne: null
+          }
+        },
+        raw: true
+      })
+      .then(function(reportMailSessions) {
+        console.log('[report_mail_session] found: ' + reportMailSessions.length);
+        _reportMailSessions = reportMailSessions;
+        console.log('_reportMailSessions', _reportMailSessions);
+        return ReportMailSessionHistory
+          .bulkCreate(_reportMailSessions, {
+            individualHooks: true,
+            transaction: t
+          });
+      })
+      .then(function() {
+        return ReportMailSession
+          .destroy({
+            where: {
+              id: _.map(_reportMailSessions, 'id')
+            },
+            individualHooks: true,
+            transaction: t
+          });
+      })
+
+  }).then(function(result) {
+    // Transaction has been committed
+    // result is whatever the result of the promise chain returned to the transaction callback
+  }).catch(function(err) {
+    // Transaction has been rolled back
+    // err is whatever rejected the promise chain returned to the transaction callback
+  });
+}
+
+module.exports = function(CronJob) {
+
+  // Every 1 Minute
+  var job = new CronJob({
+    cronTime: '00 59 23 * * *',
+    // cronTime: '* * * * *',
+    onTick: function() {
+      console.log('cronTime onTick');
+      new ReportAgentInReportAgentHistory();
+      new ReportCallInReportCallHistory();
+      new ReportDialInReportDialHistory();
+      new ReportMemberInReportMemberHistory();
+      new ReportQueueInReportQueueHistory();
+      new ReportIntegrationInReportIntegrationHistory();
+      new ReportMailSessionInReportMailSessionHistory();
+    }
+  });
+
+  job.start();
+}
diff --git a/server/config/imap/imap.js b/server/config/imap/imap.js
index fcca415..e2c946d 100644
--- a/server/config/imap/imap.js
+++ b/server/config/imap/imap.js
@@ -1 +1,219 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var path = require('path');
+
+var config = require('../environment');
+
+var ImapListener = require("mail-listener2");
+var MailServerIn = require('../../models').MailServerIn;
+var MailMessage = require('../../models').MailMessage;
+var MailAttachment = require('../../models').MailAttachment;
+var MailRoom = require('../../models').MailRoom;
+var Contact = require('../../models').Contact;
+var ContactEmail = require('../../models').ContactEmail;
+var sequelize = require('../../models').sequelize;
+
+// Public
+
+function Imap(doc) {
+  var imap;
+
+  function onUpdate(doc) {
+    if (!doc.changed('state') && !doc.changed('source') && doc.username === imap.imap._config.user) {
+      console.log("MAIL - Account " + doc.username + " IMAP UPDATED");
+      imap.stop();
+      onSave(doc);
+    }
+  }
+
+  function onRemove(doc) {
+    if (doc.username === imap.imap._config.user) {
+      console.log("MAIL - Account " + doc.username + " IMAP DESTROY");
+      imap.stop();
+      imap = null;
+    }
+  }
+
+  function onSave(doc) {
+    if (imap) {
+      imap.stop(doc);
+      imap = null;
+    }
+
+    imap = new ImapListener({
+      username: doc.username,
+      password: doc.password,
+      host: doc.host,
+      port: doc.port,
+      tls: doc.ssl,
+      tlsOptions: {
+        rejectUnauthorized: false
+      },
+      connTimeout: 30000,
+      mailbox: doc.mailbox, // mailbox to monitor
+      searchFilter: [doc.filter], // the search filter being used after an IDLE notification has been retrieved
+      markSeen: true, // all fetched email willbe marked as seen and not fetched next time
+      fetchUnreadOnStart: true, // use it only if you want to get all unread email on lib start. Default is `false`,,
+      mailParserOptions: {
+        streamAttachments: false
+      }, // options to be passed to mailParser lib.
+      attachments: true, // download attachments as they are encountered to the project directory
+      attachmentOptions: {
+        directory: path.join(config.root, 'server', 'files', 'attachments', '/')
+      } // specify a download directory for attachments
+    });
+
+    imap.start();
+
+    imap.on("server:connected", function() {
+      console.log("MAIL - Account " + doc.username + " IMAP CONNECTED");
+      // SETUP CONNECTED STATUS
+      return MailServerIn
+        .findById(doc.id)
+        .then(function(msi) {
+          return msi
+            .update({
+              state: 'CONNECTED'
+            });
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    });
+
+    imap.on("server:disconnected", function() {
+      console.log("MAIL - Account " + doc.MailAccountId + " IMAP DISCONNECTED");
+      return MailServerIn
+        .findById(doc.id)
+        .then(function(msi) {
+          if (msi) {
+            return msi
+              .update({
+                state: 'DISCONNECTED'
+              });
+          }
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    });
+
+    imap.on("error", function(err) {
+      console.log("MAIL - Account " + doc.MailAccountId + " IMAP ERROR", err);
+      return MailServerIn
+        .findById(doc.id)
+        .then(function(msi) {
+          if (msi) {
+            return msi
+              .update({
+                state: 'ERROR',
+                source: err.source
+              });
+          }
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    });
+
+    imap.on("mail", function(msg, seqno, attributes) {
+      var _mRoom;
+      var _mMessage;
+      var _mFrom, _mTo, _mCc, _mBcc;
+
+      return sequelize.transaction(function(t) {
+        if (msg.inReplyTo) {
+          return MailMessage
+            .findOne({
+              where: {
+                messageId: msg.inReplyTo[0]
+              },
+              include: [{
+                all: true
+              }]
+            })
+            .then(function(mailMessageParent) {
+              return mailMessageParent
+                .getMailRoom();
+            })
+            .then(function(mailRoom) {
+              _mRoom = mailRoom;
+              console.log('msg', msg);
+
+              return MailMessage
+                .create({
+                  messageId: msg.messageId,
+                  status: 'RECEIVED',
+                  subject: msg.subject,
+                  from: _.map(msg.from, 'address'),
+                  to: msg.to ? _.map(msg.to, 'address') : [],
+                  cc: msg.cc ? _.map(msg.cc, 'address') : [],
+                  bcc: msg.bcc ? _.map(msg.bcc, 'address') : [],
+                  html: msg.html || null,
+                  text: msg.text || null,
+                  MailAttachments: msg.attachments || [],
+                  MailRoomId: mailRoom.id,
+                }, {
+                  include: [{
+                    model: MailAttachment
+                  }],
+                  transaction: t
+                });
+            });
+
+        } else {
+          return MailRoom
+            .create({
+              roomId: msg.messageId,
+              subject: msg.subject,
+              from: _.map(msg.from, 'address').join(';'),
+              MailMessages: [{
+                messageId: msg.messageId,
+                subject: msg.subject,
+                from: _.map(msg.from, 'address'),
+                to: msg.to ? _.map(msg.to, 'address') : [],
+                cc: msg.cc ? _.map(msg.cc, 'address') : [],
+                bcc: msg.bcc ? _.map(msg.bcc, 'address') : [],
+                status: 'RECEIVED',
+                html: msg.html || null,
+                text: msg.text || null,
+                MailAttachments: msg.attachments || []
+              }],
+              MailAccountId: doc.MailAccountId
+            }, {
+              transaction: t,
+              include: [{
+                model: MailMessage,
+                include: [{
+                  model: MailAttachment
+                }]
+              }]
+            });
+        }
+      }).catch(function(err) {
+        console.error(err);
+      });
+    });
+
+    imap.on("attachment", function(attachment) {
+      console.log(attachment.path);
+    });
+  }
+
+  onSave(doc);
+
+  MailServerIn.afterCreate(function(doc) {
+    onSave(doc);
+  });
+  MailServerIn.afterUpdate(function(doc) {
+    onUpdate(doc);
+  });
+  MailServerIn.afterDestroy(function(doc) {
+    onRemove(doc);
+  });
+
+  return {};
+}
+
+module.exports = Imap;
diff --git a/server/config/imap/index.js b/server/config/imap/index.js
index f22dd42..89d4f28 100644
--- a/server/config/imap/index.js
+++ b/server/config/imap/index.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+var Imap = require('./imap');
+var MailServerIn = require('../../models').MailServerIn;
+
+module.exports = function() {
+  MailServerIn.afterCreate(function(doc) {
+    new Imap(doc);
+  });
+
+  // Get all Mail Accounts
+  return MailServerIn
+    .findAll()
+    .then(function(servers) {
+      servers.forEach(function(server) {
+        new Imap(server);
+      });
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+}
diff --git a/server/config/integration.js b/server/config/integration.js
index c321b7b..8b87bc5 100644
--- a/server/config/integration.js
+++ b/server/config/integration.js
@@ -1 +1,24 @@
\ No newline at end of file
+'use strict';
+
+var path = require('path');
+var fs = require('fs');
+
+var Integration = require('../models').Integration;
+
+module.exports = function(io, ami) {
+
+  Integration
+    .findAll()
+    .then(function(integrations) {
+      integrations.forEach(function(integration) {
+        var info = path.parse(integration.path);
+        var file = path.join(info.dir, info.name, integration.main);
+        if (fs.existsSync(file)) {
+          require(file)(io, ami);
+        }
+      });
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+};
diff --git a/server/config/integrations/index.js b/server/config/integrations/index.js
index 2fafeb2..46f19fb 100644
--- a/server/config/integrations/index.js
+++ b/server/config/integrations/index.js
@@ -1 +1,120 @@
\ No newline at end of file
+'use strict';
+
+// Library
+var _ = require('lodash');
+// Models
+var IntegrationModel = require('../../models').Integration;
+var Variable = require('../../models').Variable;
+// Integration service
+var Zendesk = require('./zendesk');
+
+function Integration(ami) {
+  console.log('Trigger Integration Initialization...');
+
+  var zendesk = new Zendesk();
+
+  var activeIntegrations = [];
+  var activeVariables = [];
+  var channelVariables = {};
+
+  ami.on('varset', function(evt) {
+    if (!channelVariables.hasOwnProperty(evt.uniqueid)) {
+      channelVariables[evt.uniqueid] = [];
+    }
+    if (evt.hasOwnProperty('variable') && evt.hasOwnProperty('value')) {
+      var index = _.findIndex(activeVariables, {
+        'name': _.head(_.keys(evt.variable))
+      });
+      if (index >= 0) {
+        var variable = {
+          name: _.head(_.keys(evt.variable)),
+          value: evt.value
+        };
+        channelVariables[evt.uniqueid].push(variable);
+      }
+    }
+  });
+
+  Variable
+    .findAll()
+    .then(function(variables) {
+      activeVariables = variables;
+      synchVariables(activeVariables);
+    })
+
+  IntegrationModel
+    .findAll({
+      where: {
+        active: 1
+      }
+    })
+    .then(function(integrations) {
+      activeIntegrations = integrations;
+      synchIntegrations(activeIntegrations);
+    });
+
+  this.manageEvent = function(rule, doc, channel) {
+    console.log('Integration: manageEvent');
+    if (integrationIsActive(rule.data1, activeIntegrations)) {
+      switch (rule.data1) {
+        case 'zendesk':
+          zendesk.handleEvent(rule.data2, rule.data3, channel, doc, _.result(channelVariables, doc.uniqueid));
+          break;
+        default:
+          console.log('Integration not recognized: ', rule.data1);
+      }
+    }
+  }
+}
+
+function integrationIsActive(integration, active) {
+  var integratioNames = _.map(active, 'state');
+  if (_.indexOf(integratioNames, integration) >= 0) {
+    return true;
+  }
+  return false;
+}
+
+function synchIntegrations(active) {
+  IntegrationModel.afterUpdate(function(doc) {
+    if (doc.active) {
+      updateCollection(active, doc);
+    } else {
+      _.remove(active, {
+        id: doc.id
+      });
+    }
+    console.log('After update, integrations are ', active.length);
+  });
+}
+
+function synchVariables(active) {
+  Variable.afterCreate(function(doc) {
+    updateCollection(active, doc);
+    console.log('After create, Variables are ', active.length);
+  });
+  Variable.afterUpdate(function(doc) {
+    updateCollection(active, doc);
+    console.log('After update, Variables are ', active.length);
+  });
+  Variable.afterDestroy(function(doc) {
+    _.remove(active, {
+      id: doc.id
+    });
+    console.log('After destroy, Variables are ', active.length);
+  });
+}
+
+function updateCollection(collection, doc) {
+  var oldItem = _.find(collection, {
+    id: doc.id
+  });
+  var index = collection.indexOf(oldItem);
+  if (oldItem) {
+    _.merge(collection[index], doc);
+  } else {
+    collection.unshift(doc);
+  }
+}
+
+module.exports = Integration;
diff --git a/server/config/integrations/zendesk.js b/server/config/integrations/zendesk.js
index ccbbc41..5abebe0 100644
--- a/server/config/integrations/zendesk.js
+++ b/server/config/integrations/zendesk.js
@@ -1 +1,530 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var rp = require('request-promise');
+
+var ZendeskIntegration = require('../../models').ZendeskIntegration;
+var ZendeskConfiguration = require('../../models').ZendeskConfiguration;
+var ReportIntegration = require('../../models').ReportIntegration;
+
+function Zendesk() {
+  console.log('Zendesk integration initialization...');
+}
+
+Zendesk.prototype.handleEvent = function(integrationId, configurationId, channel, data, variables) {
+
+  var zendeskData = {
+    uri: undefined,
+    headers: undefined,
+    agent: undefined,
+    enduser: undefined,
+    ticket: undefined,
+    configuration: undefined
+  };
+
+  if (!integrationId || !configurationId) {
+    console.error('Passed parameters are not enough to create a new ticket: miss IntegrationId or ConfigurationId');
+    return;
+  }
+
+  ZendeskConfiguration
+    .find({
+      where: {
+        id: configurationId,
+        AccountId: integrationId
+      },
+      include: [{
+        all: true,
+        include: [{
+          all: true
+        }]
+      }]
+    })
+    .then(handleEntityNotFound())
+    .then(handleConnection(zendeskData))
+    .then(searchAgent(zendeskData, channel, data))
+    .then(handleAgent(zendeskData, channel, data))
+    .then(searchAdmin(zendeskData))
+    .then(handleAdmin(zendeskData))
+    .then(searchEndUser(zendeskData, channel, data))
+    .then(handleEndUser(zendeskData, channel, data))
+    .then(endUserCreation(zendeskData, channel, data))
+    .then(handleEndUserCreation(zendeskData, channel, data))
+    .then(ticketCreation(zendeskData, data, variables))
+    .then(handleTicketDisplay(zendeskData))
+    .then(handleReportIntegration(zendeskData, channel, data, integrationId))
+    .then(handleEnd(zendeskData))
+    .catch(handleError(zendeskData, channel, data, integrationId));
+
+};
+
+function handleEntityNotFound() {
+  return function(entity) {
+    if (!entity) {
+      return null;
+    }
+    return entity;
+  };
+}
+
+function handleConnection(zendeskData) {
+  return function(configuration) {
+    var accConfiguration = getAccountConfiguration(configuration.ZendeskAccount);
+    zendeskData.configuration = configuration;
+    zendeskData.uri = accConfiguration.uri;
+    zendeskData.headers = accConfiguration.headers;
+  }
+}
+
+function getAccountConfiguration(account) {
+  var authJson = {
+    uri: stripTrailingSlash(account.remoteUri),
+    headers: {
+      'Authorization': undefined,
+      'Content-Type': 'application/json',
+      'Accept': 'application/json'
+    }
+  };
+  var auth = account.username;
+  if (account.authType === 'password') {
+    auth = auth + ':' + account.password;
+  } else if (account.authType === 'token') {
+    auth = auth + '/token:' + account.token;
+  }
+  authJson.headers.Authorization = 'Basic ' + new Buffer(auth).toString('base64');
+  return authJson;
+}
+
+function searchAgent(zendeskData, channel, data) {
+  return function() {
+    console.log('[Zendesk] Agent to search:', getValueFromPropertyName(data, getAgentSearchPropertyFromChannel(channel)));
+    return rp({
+      uri: zendeskData.uri + '/search.json',
+      qs: {
+        // 'query': "type:user role:agent name:Andrea Bianco"
+        'query': util.format("type:user role:agent name:%s", getValueFromPropertyName(data, getAgentSearchPropertyFromChannel(channel)))
+      },
+      headers: zendeskData.headers,
+      json: true
+    });
+  }
+}
+
+function getAgentSearchPropertyFromChannel(channel) {
+  var property = '';
+  if (channel) {
+    switch (channel) {
+      case 'voice':
+        property = 'destcalleridname';
+        break;
+      default:
+        property = 'destcalleridname';
+        console.error('Channel not recognized:', channel);
+    }
+  }
+  return property;
+}
+
+function getValueFromPropertyName(data, property) {
+  return _.result(data, property);
+}
+
+function handleAgent(zendeskData, channel, event) {
+  return function(data) {
+    if (data.results.length > 0 && data.count > 0) {
+      console.log('[Zendesk] Agent found:', getValueFromPropertyName(event, getAgentSearchPropertyFromChannel(channel)));
+      zendeskData.agent = getMatchObject(data.results, 'name', getValueFromPropertyName(event, getAgentSearchPropertyFromChannel(channel)));
+      return zendeskData.agent;
+    }
+    return;
+  }
+}
+
+function getMatchObject(collection, property, valueToSearch) {
+  var valueFound = _.find(collection, {
+    property: valueToSearch
+  });
+  if (valueFound) {
+    return valueFound;
+  }
+  return _.first(collection);
+}
+
+function searchAdmin(zendeskData) {
+  return function(data) {
+    if (data) {
+      return;
+    }
+    console.log('[Zendesk] Agent not found. Use the admin account');
+    return rp({
+      uri: zendeskData.uri + '/users/me.json',
+      headers: zendeskData.headers,
+      json: true
+    });
+  }
+}
+
+function handleAdmin(zendeskData) {
+  return function(data) {
+    if (!data) {
+      return;
+    }
+    if (data.user) {
+      console.log('[Zendesk] Admin selected');
+      zendeskData.agent = data.user;
+    }
+    return;
+  }
+}
+
+function searchEndUser(zendeskData, channel, data) {
+  return function() {
+    console.log('[Zendesk] End User to search:', getValueFromPropertyName(data, getEndUserSearchPropertyFromChannel(channel)));
+    return rp({
+      uri: zendeskData.uri + '/search.json',
+      qs: {
+        'query': util.format("type:user role:end-user %s:%s", getEndUserSearchQueryFromChannel(channel), getValueFromPropertyName(data, getEndUserSearchPropertyFromChannel(channel)))
+      },
+      headers: zendeskData.headers,
+      json: true
+    });
+  }
+}
+
+function getEndUserSearchQueryFromChannel(channel) {
+  var property = '';
+  if (channel) {
+    switch (channel) {
+      case 'voice':
+        property = 'phone';
+        break;
+      default:
+        property = 'phone';
+        console.error('Channel not recognized:', channel);
+    }
+  }
+  return property;
+}
+
+function getEndUserSearchPropertyFromChannel(channel) {
+  var property = '';
+  if (channel) {
+    switch (channel) {
+      case 'voice':
+        // TODO: Change with rigth field name: destconnectedlinenum
+        property = 'destconnectedlinenum';
+        break;
+      default:
+        property = 'destconnectedlinenum';
+        console.error('Channel not recognized:', channel);
+    }
+  }
+  return property;
+}
+
+function getEndUserNamePropertyFromChannel(channel) {
+  var property = '';
+  if (channel) {
+    switch (channel) {
+      case 'voice':
+        property = 'destconnectedlinename';
+        break;
+      default:
+        property = 'destconnectedlinename';
+        console.error('Channel not recognized:', channel);
+    }
+  }
+  return property;
+}
+
+function handleEndUser(zendeskData, channel, event) {
+  return function(data) {
+    if (data.results.length > 0 && data.count > 0) {
+      console.log('[Zendesk] End User found:', getValueFromPropertyName(event, getEndUserSearchPropertyFromChannel(channel)));
+      zendeskData.enduser = data.results[0];
+      return zendeskData.enduser;
+    }
+    return;
+  }
+}
+
+function endUserCreation(zendeskData, channel, event) {
+  return function(data) {
+    if (data) {
+      return;
+    }
+    console.log('[Zendesk] End User not found:', getValueFromPropertyName(event, getEndUserSearchPropertyFromChannel(channel)));
+    console.log(getJsonForEndUserCreation(channel, event));
+    return rp({
+      uri: zendeskData.uri + '/users.json',
+      method: 'POST',
+      body: {
+        user: getJsonForEndUserCreation(channel, event)
+      },
+      headers: zendeskData.headers,
+      json: true
+    });
+  }
+}
+
+function getJsonForEndUserCreation(channel, data) {
+  var user = {
+    role: 'end-user'
+  };
+
+  switch (channel) {
+    case 'voice':
+      if (data.destconnectedlinename) {
+        user.name = getValueFromPropertyName(data, getEndUserNamePropertyFromChannel(channel));
+      } else {
+        user.name = util.format('Caller - %s', getValueFromPropertyName(data, getAgentSearchPropertyFromChannel(channel)))
+      }
+      user.phone = getValueFromPropertyName(data, getEndUserSearchPropertyFromChannel(channel));
+      break;
+    default:
+      console.error('Channel not recognized:', channel);
+  }
+
+  return user;
+}
+
+function handleEndUserCreation(zendeskData, channel, event) {
+  return function(data) {
+    if (!data) {
+      return;
+    }
+    if (data.user) {
+      console.log('[Zendesk] End User created: ', getValueFromPropertyName(event, getEndUserSearchPropertyFromChannel(channel)));
+      zendeskData.enduser = data.user;
+    }
+    return;
+  }
+}
+
+function ticketCreation(zendeskData, event, variables) {
+  return function() {
+    var ticketBody = {
+      //Standard Field
+      requester_id: zendeskData.agent.id,
+      submitter_id: zendeskData.enduser.id,
+      assignee_id: zendeskData.enduser.id,
+      //Tag
+      tags: createTagsFromConfiguration(zendeskData),
+      //Configurated Field
+      subject: createSubjectFromConfiguration(zendeskData, event, variables),
+      description: createDescriptionFromConfiguration(zendeskData, event, variables),
+      custom_fields: createCustomFieldsFromConfiguration(zendeskData, event, variables)
+    };
+    var fields = createFieldsFromConfiguration(zendeskData);
+    if (fields.length > 0) {
+      _.forEach(fields, function(item) {
+        ticketBody[item.id] = item.value;
+      });
+    }
+    console.log('[Zendesk] Ticket body:', ticketBody);
+    return rp({
+      uri: zendeskData.uri + '/tickets.json',
+      method: 'POST',
+      body: {
+        ticket: ticketBody
+      },
+      headers: zendeskData.headers,
+      json: true
+    });
+  }
+}
+
+function createTagsFromConfiguration(zendeskData) {
+  var tags = [];
+  if (zendeskData.configuration.Tags.length > 0) {
+    tags = _.map(zendeskData.configuration.Tags, 'name');
+  }
+  return tags;
+}
+
+function createSubjectFromConfiguration(zendeskData, event, variables) {
+  var subject = '';
+  var stringsArray = [];
+  if (zendeskData.configuration.Subject.length > 0) {
+    var subjectConfiguration = _.sortBy(zendeskData.configuration.Subject, ['id']);
+    _.forEach(subjectConfiguration, function(value) {
+      if (value.type === 'string') {
+        stringsArray.push(value.content);
+      } else if (value.type === 'variable' && value.variableName) {
+        var prop = getValueFromPropertyName(event, value.variableName);
+        if (prop) {
+          stringsArray.push(prop.toString());
+        }
+      } else if (value.type === 'customVariable') {
+        var varFound = _.find(variables, {
+          'name': value.Variable.name
+        });
+        if (varFound) {
+          stringsArray.push(varFound.value.toString());
+        }
+      }
+    });
+    subject = stringsArray.join(' ');
+  }
+  return subject;
+}
+
+function createDescriptionFromConfiguration(zendeskData, event, variables) {
+  var description = '';
+  var stringsArray = [];
+  if (zendeskData.configuration.Description.length > 0) {
+    var descriptionConfiguration = _.sortBy(zendeskData.configuration.Description, ['id']);
+    _.forEach(descriptionConfiguration, function(value) {
+      if (value.type === 'string') {
+        stringsArray.push(value.content);
+      } else if (value.type === 'keyValue') {
+        if (value.keyType === 'string') {
+          stringsArray.push(util.format('%s: %s', value.key, value.keyContent));
+        } else if (value.keyType === 'variable' && value.variableName) {
+          var prop = getValueFromPropertyName(event, value.variableName);
+          if (prop) {
+            stringsArray.push(util.format('%s: %s', value.key, prop.toString()));
+          }
+        } else if (value.keyType === 'customVariable') {
+          var varFound = _.find(variables, {
+            'name': value.Variable.name
+          });
+          if (varFound) {
+            stringsArray.push(util.format('%s: %s', value.key, varFound.value.toString()));
+          }
+        }
+      }
+    });
+    description = stringsArray.join('\n');
+  }
+  return description;
+}
+
+function createCustomFieldsFromConfiguration(zendeskData, event, variables) {
+  var customFields = [];
+  var fieldArray = [];
+  if (zendeskData.configuration.Field.length > 0) {
+    customFields = _.filter(zendeskData.configuration.Field, {
+      'customField': true
+    });
+    _.forEach(customFields, function(item) {
+      if (item.type === 'string') {
+        fieldArray.push({
+          id: item.idField,
+          value: item.content
+        });
+      } else if (item.type === 'variable') {
+        var prop = getValueFromPropertyName(event, item.variableName);
+        if (prop) {
+          fieldArray.push({
+            id: item.idField,
+            value: prop.toString()
+          });
+        }
+      } else if (item.type === 'customVariable') {
+        var varFound = _.find(variables, {
+          'name': item.Variable.name
+        });
+        if (varFound) {
+          fieldArray.push({
+            id: item.idField,
+            value: varFound.value.toString()
+          });
+        }
+      }
+    });
+  }
+  return fieldArray;
+}
+
+function createFieldsFromConfiguration(zendeskData) {
+  var fields = [];
+  var fieldArray = [];
+  if (zendeskData.configuration.Field.length > 0) {
+    fields = _.filter(zendeskData.configuration.Field, {
+      'customField': false
+    });
+    _.forEach(fields, function(item) {
+      if (item.type === 'string') {
+        fieldArray.push({
+          id: item.nameField,
+          value: item.content
+        });
+      }
+    });
+  }
+  return fieldArray;
+}
+
+function handleTicketDisplay(zendeskData) {
+  return function(data) {
+    if (data.ticket) {
+      console.log('[Zendesk] Ticket created');
+      zendeskData.ticket = data.ticket;
+      var urlDisplay = util.format('/channels/voice/agents/%d/tickets/%d/display.json', zendeskData.agent.id, zendeskData.ticket.id);
+      return rp({
+        uri: zendeskData.uri + urlDisplay,
+        method: 'POST',
+        headers: zendeskData.headers,
+        json: true
+      });
+    }
+    return;
+  }
+}
+
+function handleReportIntegration(zendeskData, channel, data, integrationId) {
+  return function() {
+    data = data.dataValues;
+    delete data.id;
+    data.integration = 'zendesk';
+    data.eventChannel = channel;
+    data.exitStatus = 'success';
+    data.ticketId = zendeskData.ticket.id;
+    data.integrationId = integrationId;
+    return ReportIntegration.create(data);
+  }
+}
+
+function handleEnd(zendeskData, data, channel) {
+  return function() {
+    console.log(util.format('[Zendesk] Ticket %d displayed to agent %d', zendeskData.ticket.id, zendeskData.agent.id));
+    zendeskData = undefined;
+    return;
+  }
+}
+
+function handleError(zendeskData, channel, data, integrationId) {
+  return function(err) {
+    console.error('[Zendesk] Error in ticket creation');
+    console.error('[Zendesk] Message:', err.message);
+    console.error('[Zendesk] Stack:', err.stack);
+    zendeskData = undefined;
+
+    delete data.id;
+    data = data.dataValues;
+    data.integration = 'zendesk';
+    data.eventChannel = channel;
+    data.exitStatus = 'failed';
+    data.integrationId = integrationId;
+    ReportIntegration
+      .create(data)
+      .then(function() {
+        return;
+      })
+      .catch(function(err) {
+        console.error('[Zendesk] Error in Report integration create');
+        return;
+      });
+  }
+}
+
+function stripTrailingSlash(str) {
+  if (str.substr(-1) === '/') {
+    return str.substr(0, str.length - 1);
+  }
+  return str;
+}
+
+module.exports = Zendesk;
diff --git a/server/config/license/index.js b/server/config/license/index.js
index c708e99..fb29ba1 100644
--- a/server/config/license/index.js
+++ b/server/config/license/index.js
@@ -1 +1,61 @@
\ No newline at end of file
+'use strict';
+
+var config = require('../environment');
+var rp = require('request-promise');
+var util = require('util');
+var url = require('url');
+var CronJob = require('cron').CronJob;
+var Settings = require('../../models').Settings;
+var license = require('./license.service');
+
+var provisioning = config.provisioning + '/servers/' + license.getUuid() + '/check';
+console.log(provisioning);
+
+
+// Define the factory
+function licenseManager() {
+  // Store the settings in a closure
+  var job = new CronJob({
+    cronTime: '* * * * *',
+    onTick: function() {
+      // console.log('Check License Start');
+      var _data;
+
+      return rp({
+          uri: provisioning,
+          json: true // Automatically stringifies the body to JSON
+        })
+        .then(function(data) {
+          _data = data;
+
+          return Settings
+            .findOrCreate({
+              where: {
+                id: 1
+              }
+            });
+        })
+        .spread(function(settings) {
+          return settings
+            .update({
+              license: _data.license
+            });
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    },
+    start: false
+  });
+
+  job.start();
+  return {
+
+    // Define a sync function
+    // getLicence: function getLicence() {
+    //   return settings;
+    // },
+  };
+}
+
+module.exports = licenseManager;
diff --git a/server/config/license/license.service.js b/server/config/license/license.service.js
index a75bece..05c372a 100644
--- a/server/config/license/license.service.js
+++ b/server/config/license/license.service.js
@@ -1 +1,361 @@
\ No newline at end of file
+'use strict';
+
+var compose = require('composable-middleware');
+var moment = require('moment');
+var crypto = require('crypto');
+var util = require('util');
+var os = require('os');
+
+var config = require('../environment');
+
+
+var User = require('../../models').User;
+var Telephone = require('../../models').Telephone;
+var Settings = require('../../models').Settings;
+
+var demoLicense = {
+  users: 1,
+  agents: 1,
+  telephones: 0,
+  chat: false,
+  mail: false,
+  fax: false,
+  description: 'DEMO LICENSE'
+};
+
+function getUuid() {
+  if (os.platform() !== 'linux') {
+    var out = exec('dmidecode --string system-uuid', {
+      silent: true
+    }).output;
+    var result = out.split('\n').slice(0, -1);
+    return result[0];
+  } else {
+    var out = exec('blkid -g && blkid', {
+      silent: true
+    }).output;
+    var result = out.split('\n').slice(0, -1).map(function(line) {
+      var part, regex, result;
+      regex = new RegExp(/(\w+)\=\"([^\"]+)/g);
+      part = {
+        partition: line.match(/^(\S+)\:\ /)[1]
+      };
+      while (result = regex.exec(line)) {
+        part[result[1].toLowerCase()] = result[2];
+      }
+      return part;
+    });
+    return result[0].uuid;
+  }
+}
+
+/**
+ * Checks if the chat meets the requirements
+ */
+function hasChat() {
+
+  return compose()
+    .use(function(req, res, next) {
+      return Settings
+        .findOrCreate({
+          where: {
+            id: 1
+          }
+        })
+        .spread(function(settings) {
+          var license = null,
+            chat = false;
+
+          if (settings.license) {
+            license = decrypt(settings.license);
+            license = JSON.parse(license);
+          } else {
+            license = demoLicense;
+          }
+
+          chat = license.chat;
+
+          if (!chat) return res.sendStatus(403);
+          return next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the mail meets the requirements
+ */
+function hasMail() {
+
+  return compose()
+    .use(function(req, res, next) {
+      return Settings
+        .findOrCreate({
+          where: {
+            id: 1
+          }
+        })
+        .spread(function(settings) {
+          var license = null,
+            mail = false;
+
+          if (settings.license) {
+            license = decrypt(settings.license);
+            license = JSON.parse(license);
+          } else {
+            license = demoLicense;
+          }
+
+          mail = license.mail;
+
+          if (!mail) return res.sendStatus(403);
+          return next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the fax meets the requirements
+ */
+function hasFax() {
+
+  return compose()
+    .use(function(req, res, next) {
+      return Settings
+        .findOrCreate({
+          where: {
+            id: 1
+          }
+        })
+        .spread(function(settings) {
+          var license = null,
+            fax = false;
+
+          if (settings.license) {
+            license = decrypt(settings.license);
+            license = JSON.parse(license);
+          } else {
+            license = demoLicense;
+          }
+
+          fax = license.fax;
+
+          if (!fax) return res.sendStatus(403);
+          return next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the date meets the requirements
+ */
+function isValidDate() {
+
+  return compose()
+    .use(function(req, res, next) {
+      return Settings
+        .findOrCreate({
+          where: {
+            id: 1
+          }
+        })
+        .spread(function(settings) {
+          var license = null,
+            date = false;
+
+          if (settings.license) {
+            license = decrypt(settings.license);
+            license = JSON.parse(license);
+
+            if (moment().isSameOrBefore(license.expiration)) {
+              date = true;
+            }
+          }
+
+          if (!date) return res.sendStatus(403);
+          return next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the users meets the requirements
+ */
+function isUsersLimit() {
+
+  return compose()
+    .use(function(req, res, next) {
+      var _settings;
+
+      return Settings
+        .findOrCreate({
+          where: {
+            id: 1
+          }
+        })
+        .spread(function(settings) {
+          _settings = settings;
+
+          return User
+            .count({
+              where: {
+                $or: [{
+                  role: 'admin'
+                }, {
+                  role: 'user'
+                }]
+              }
+            });
+        })
+        .then(function(users) {
+          var license = null,
+            valid = false;
+
+          if (_settings.license) {
+            license = decrypt(_settings.license);
+            license = JSON.parse(license);
+          } else {
+            license = demoLicense;
+          }
+
+          if (license.users > users) valid = true;
+
+          if (!valid) return res.status(403).send({
+            message: 'Maximum users limit have been reached'
+          });
+          return next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the telephone meets the requirements
+ */
+function isTelephonesLimit() {
+
+  return compose()
+    .use(function(req, res, next) {
+      var _settings;
+
+      return Settings
+        .findOrCreate({
+          where: {
+            id: 1
+          }
+        })
+        .spread(function(settings) {
+          _settings = settings;
+
+          return User
+            .count({
+              where: {
+                role: 'telephone'
+              }
+            });
+        })
+        .then(function(telephones) {
+          var license = null,
+            valid = false;
+
+          if (_settings.license) {
+            license = decrypt(_settings.license);
+            license = JSON.parse(license);
+          } else {
+            license = demoLicense;
+          }
+
+          if (license.telephones > telephones) valid = true;
+
+          if (!valid) return res.status(403).send({
+            message: 'Maximum telephones limit have been reached'
+          });
+          return next();
+        })
+        .catch(function(err) {
+          return next(err);
+        });
+    });
+}
+
+/**
+ * Checks if the telephone meets the requirements
+ */
+function isValidAgent() {
+  return function(user) {
+    var _settings;
+    var _valid = false;
+
+    if (user.role === 'admin' || user.role === 'user' || (user.role === 'agent' && user.online)) {
+      _valid = true;
+    }
+
+    return Settings
+      .findOrCreate({
+        where: {
+          id: 1
+        }
+      })
+      .spread(function(settings) {
+        _settings = settings;
+
+        return User
+          .count({
+            where: {
+              role: 'agent',
+              online: true
+            }
+          });
+      })
+      .then(function(agents) {
+        var license = null;
+
+        if (_settings.license) {
+          license = decrypt(_settings.license);
+          license = JSON.parse(license);
+        } else {
+          license = demoLicense;
+        }
+
+        if (license.agents > agents) _valid = true;
+
+        return _valid;
+      });
+  }
+}
+
+function decrypt(cypher) {
+  var uuid = getUuid();
+  var prefix = uuid.split('-')[4];
+  var suffix = uuid.split('-')[0];
+
+  var decipher = crypto.createDecipher('aes-256-ctr', util.format('%s:%s:%s', prefix, uuid, suffix));
+  var license = decipher.update(cypher, 'base64', 'utf8')
+  license += decipher.final('utf8');
+
+  return license;
+}
+
+exports.getUuid = getUuid;
+exports.isValidDate = isValidDate;
+exports.isUsersLimit = isUsersLimit;
+exports.isTelephonesLimit = isTelephonesLimit;
+exports.hasChat = hasChat;
+exports.hasMail = hasMail;
+exports.hasFax = hasFax;
+exports.isValidAgent = isValidAgent;
+exports.demoLicense = demoLicense;
+exports.decrypt = decrypt;
diff --git a/server/config/local.env.sample.js b/server/config/local.env.sample.js
index 7f8f5f8..58ae668 100644
--- a/server/config/local.env.sample.js
+++ b/server/config/local.env.sample.js
@@ -1 +1,23 @@
\ No newline at end of file
+'use strict';
+
+// Use local.env.js for environment variables that grunt will set when the server starts locally.
+// Use for your api keys, secrets, etc. This file should not be tracked by git.
+//
+// You will need to set these on the server you deploy to.
+
+module.exports = {
+  DOMAIN:           'http://localhost:9000',
+  SESSION_SECRET:   'xcallyshuttle-secret',
+
+  FACEBOOK_ID:      'app-id',
+  FACEBOOK_SECRET:  'secret',
+
+  TWITTER_ID:       'app-id',
+  TWITTER_SECRET:   'secret',
+
+  GOOGLE_ID:        'app-id',
+  GOOGLE_SECRET:    'secret',
+
+  // Control debug level for modules using visionmedia/debug
+  DEBUG: ''
+};
diff --git a/server/config/logger.js b/server/config/logger.js
index 844c4ea..1542f7e 100644
--- a/server/config/logger.js
+++ b/server/config/logger.js
@@ -1 +1,120 @@
\ No newline at end of file
+var winston = require('winston');
+var path = require('path');
+var config = require('./environment');
+
+winston.emitErrs = true;
+
+var debug = new winston.Logger({
+  transports: [
+    new winston.transports.Console({
+      level: 'debug',
+      handleExceptions: true,
+      json: false,
+      colorize: true
+    })
+  ]
+});
+
+if (config.logging.debugToFile) {
+  debug.add(
+    winston.transports.File, {
+      name: 'debug-file',
+      level: 'debug',
+      filename: config.logging.debug,
+      handleExceptions: true,
+      json: true,
+      maxsize: 5242880, //5MB
+      maxFiles: 5,
+      colorize: false
+    }
+  );
+}
+
+var info = new winston.Logger({
+  transports: [
+    new winston.transports.File({
+      name: 'info-file',
+      level: 'info',
+      filename: config.logging.info,
+      handleExceptions: true,
+      json: true,
+      maxsize: 5242880, //5MB
+      maxFiles: 5,
+      colorize: false
+    }),
+    new winston.transports.Console({
+      level: 'info',
+      handleExceptions: true,
+      json: false,
+      colorize: true
+    })
+  ]
+});
+
+var warn = new winston.Logger({
+  transports: [
+    new winston.transports.File({
+      name: 'warn-file',
+      level: 'warn',
+      filename: config.logging.warn,
+      handleExceptions: true,
+      json: true,
+      maxsize: 5242880, //5MB
+      maxFiles: 5,
+      colorize: false
+    }),
+    new winston.transports.Console({
+      level: 'warn',
+      handleExceptions: true,
+      json: false,
+      colorize: true
+    })
+  ]
+});
+
+var error = new winston.Logger({
+  transports: [
+    new winston.transports.File({
+      name: 'error-file',
+      level: 'error',
+      filename: config.logging.error,
+      handleExceptions: true,
+      json: true,
+      maxsize: 5242880, //5MB
+      maxFiles: 5,
+      colorize: false
+    }),
+    new winston.transports.Console({
+      level: 'error',
+      handleExceptions: true,
+      json: false,
+      colorize: true
+    })
+  ]
+});
+
+var loggers = {
+  debug: function(msg, callback) {
+    debug.debug(msg, callback);
+  },
+  info: function(msg, callback) {
+    info.info(msg, callback);
+  },
+  warn: function(msg, callback) {
+    warn.warn(msg, callback);
+  },
+  error: function(msg, callback) {
+    error.error(msg, callback);
+  },
+  log: function(level, msg, callback) {
+    var lvl = exports[level];
+    lvl(msg, callback);
+  }
+};
+
+module.exports = loggers;
+module.exports.stream = {
+  write: function(message, encoding) {
+    loggers.info(message);
+  }
+};
diff --git a/server/config/routing/chat/index.js b/server/config/routing/chat/index.js
index 5091214..dc9a8ad 100644
--- a/server/config/routing/chat/index.js
+++ b/server/config/routing/chat/index.js
@@ -1 +1,235 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var moment = require('moment');
+var util = require('util');
+var async = require('async');
+
+var Util = require('../../util');
+
+var ChatApplication = require('../../../models').ChatApplication;
+var ChatVisitor = require('../../../models').ChatVisitor;
+var ChatWebsite = require('../../../models').ChatWebsite;
+var ChatQueue = require('../../../models').ChatQueue;
+var ChatRoom = require('../../../models').ChatRoom;
+
+var ReportChat = require('../../../models').ReportChat;
+var ReportChatSession = require('../../../models').ReportChatSession;
+var Interval = require('../../../models').Interval;
+var User = require('../../../models').User;
+
+var Session = require('./session');
+
+module.exports = function() {
+  console.log('Chat Routing Initialization..');
+
+  ChatVisitor.afterCreate(function(visitor) {
+    if (visitor.origin === 'webchat') {
+      handleVisitor(visitor);
+    }
+  });
+}
+
+function handleEntityNotFound(message) {
+  return function(entity) {
+    if (!entity) {
+      throw {
+        message: message
+      };
+    }
+    return entity;
+  };
+}
+
+function handleChatWebsite(_report) {
+  return function(website) {
+    _report.websiteid = website.id;
+    _report.websitename = website.name;
+    _report.websiteaddress = website.address;
+
+    return website
+      .getChatApplications({
+        order: 'priority ASC',
+        include: [{
+          model: ChatQueue,
+          attributes: ['id', 'name', 'timeout', 'strategy'],
+          include: [{
+            model: User,
+            attributes: ['id', 'name', 'online', 'chatPause']
+          }]
+        }, {
+          model: User,
+          attributes: ['id', 'name', 'online', 'chatPause']
+        }, {
+          model: Interval,
+          include: [{
+            all: true
+          }]
+        }]
+      });
+  }
+}
+
+function handleChatRoom(_report, _apps) {
+  return function(apps) {
+    apps.forEach(function(app) {
+      _apps.push(app);
+    });
+
+    _apps = apps;
+    // START CREATE/ASSIGN CHAT ROOM
+    return ChatRoom
+      .create({
+        type: 'external',
+        ChatVisitorId: _report.visitorid,
+        ChatWebsiteId: _report.websiteid
+      }, {
+        include: [{
+          all: true
+        }]
+      });
+  }
+}
+
+function handleApps(_report, _apps) {
+  return function(room) {
+    var waterfall = [];
+
+    _report.roomid = room.id;
+    _report.uniqueid = util.format('%s.%s', moment().unix(), room.id);
+
+    ReportChatSession
+      .create(_.cloneDeep(_report))
+      .catch(function(err) {
+        console.error(err);
+      });
+
+    _apps.forEach(function(app, index) {
+      var data = _.cloneDeep(_report);
+
+      data.apptimeout = app.timeout;
+      data.application = app.app;
+
+      switch (app.app) {
+        case 'queue':
+          data.queueid = app.ChatQueue.id;
+          data.queuename = app.ChatQueue.name;
+          data.strategy = app.ChatQueue.strategy;
+
+          data.queuetimeout = app.ChatQueue.timeout;
+          data.agents = _.map(app.ChatQueue.Users, function(agent) {
+            return {
+              id: agent.id,
+              name: agent.name
+            }
+          });
+
+          break;
+
+        case 'agent':
+          data.queuetimeout = app.timeout;
+          data.strategy = 'beepall';
+
+          data.agents = [{
+            id: app.User.id,
+            name: app.User.name
+          }];
+
+          break;
+
+        default:
+      }
+
+      if (waterfall.length) {
+        waterfall.push(function(exit, callback) {
+          if (exit) {
+            return callback(null, exit);
+          } else {
+            new Session(data, callback);
+          }
+        });
+      } else {
+        // PRIMA CALLBACK NESSUN DATO
+        waterfall.push(function(callback) {
+          new Session(data, callback);
+        });
+      }
+    });
+
+    async.waterfall(waterfall, function(err, managed, abandon) {
+      if (err) {
+        console.error(err);
+      }
+
+      console.log('End chat routing..');
+
+      if (!managed) {
+        return ReportChatSession
+          .update({
+            unmanagedAt: moment().format("YYYY-MM-DD HH:mm:ss")
+          }, {
+            where: {
+              uniqueid: _report.uniqueid
+            },
+            individualHooks: true
+          })
+          .then(handleUnmanaged(_report));
+      }
+
+      if (abandon) {
+        return handleAbandon(_report);
+      }
+
+      return;
+    });
+  }
+}
+
+function handleAbandon(_report) {
+  //Chat session abandon
+  return ChatVisitor
+    .update({
+      status: 'abandon'
+    }, {
+      where: {
+        id: _report.visitorid
+      }
+    });
+}
+
+function handleUnmanaged(_report) {
+  return function(reportChatSession) {
+    //Chat session not managed
+    return ChatVisitor
+      .update({
+        status: 'unserved'
+      }, {
+        where: {
+          id: _report.visitorid
+        }
+      });
+  }
+}
+
+function handleVisitor(visitor) {
+  var _apps = [];
+  var _report = {
+    visitorid: visitor.id,
+    visitorname: visitor.fullname,
+    visitoremail: visitor.email,
+    websiteid: visitor.ChatWebsiteId,
+    joinAt: moment().format("YYYY-MM-DD HH:mm:ss")
+  };
+
+  return ChatWebsite
+    .findById(visitor.ChatWebsiteId)
+    .then(handleEntityNotFound('Unknown ChatWebsite..'))
+    .then(handleChatWebsite(_report))
+    .then(handleEntityNotFound('Unknown ChatWebsite Applications..'))
+    .then(handleChatRoom(_report, _apps))
+    .then(handleEntityNotFound('Unknown ChatRoom..'))
+    .then(handleApps(_report, _apps))
+    .catch(function(err) {
+      console.error('err', err);
+    });
+}
diff --git a/server/config/routing/chat/session.js b/server/config/routing/chat/session.js
index 630a367..17560eb 100644
--- a/server/config/routing/chat/session.js
+++ b/server/config/routing/chat/session.js
@@ -1 +1,280 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var moment = require('moment');
+
+var Agent = require('../../../models').User;
+var ChatRoom = require('../../../models').ChatRoom;
+var ChatApplication = require('../../../models').ChatApplication;
+var ReportChat = require('../../../models').ReportChat;
+var ReportChatSession = require('../../../models').ReportChatSession;
+
+// Define the factory
+function Session(report, callback) {
+  console.log('New Chat session...');
+  console.log('report', report);
+  var elapsed = 0;
+  var interval, timeout;
+
+  Agent
+    .findAll({
+      where: {
+        id: {
+          $in: _.map(report.agents, 'id'),
+        },
+        online: true,
+        mailPause: false
+      },
+      include: [{
+        model: ChatRoom,
+        attributes: ['id', 'status']
+      }],
+      attributes: ['id', 'name', 'chatCapacity']
+    })
+    .then(function(agents) {
+      var set1 = _.filter(agents, function(agent) {
+        if (agent.chatCapacity === 0) {
+          return true;
+        } else {
+          return agent.chatCapacity > _.filter(agent.ChatRooms, function(room) {
+            return room.id !== report.roomid && room.status !== 'CLOSED'
+          }).length;
+        }
+      });
+      var set2 = [];
+
+      var diff;
+      var agent;
+
+      report.timeslot = ((report.apptimeout - elapsed) > report.queuetimeout) ? report.queuetimeout : (report.apptimeout - elapsed);
+
+      switch (report.strategy) {
+        case 'rrmemory':
+          diff = _.differenceBy(set1, set2, 'name');
+          agent = diff.shift();
+
+          if (agent) {
+            report.available = [{
+              id: agent.id,
+              name: agent.name
+            }];
+
+            set2.push({
+              id: agent.id,
+              name: agent.name
+            });
+            saveReport(report);
+
+          } else {
+            set2 = [];
+            agent = set1.shift();
+
+            if (agent) {
+              set2.push({
+                id: agent.id,
+                name: agent.name
+              });
+              saveReport(report);
+            }
+          }
+
+          break;
+        case 'beepall':
+          report.available = set1;
+          saveReport(report);
+
+          break;
+        default:
+      }
+
+      // APPLICATION TIMEOUT
+      timeout = setTimeout(function() {
+        clearInterval(interval);
+        return callback(null, false);
+      }, report.apptimeout * 1000);
+
+      interval = setInterval(function() {
+        elapsed += report.queuetimeout;
+
+        return Agent
+          .findAll({
+            where: {
+              id: {
+                $in: _.map(report.agents, 'id'),
+              },
+              online: true,
+              mailPause: false
+            },
+            include: [{
+              model: ChatRoom,
+              attributes: ['id', 'status']
+            }],
+            attributes: ['id', 'name', 'chatCapacity']
+          })
+          .then(function(agents) {
+            set1 = _.filter(agents, function(agent) {
+              if (agent.chatCapacity === 0) {
+                return true;
+              } else {
+                return agent.chatCapacity > _.filter(agent.ChatRooms, function(room) {
+                  return room.id !== report.roomid && room.status !== 'CLOSED'
+                }).length;
+              }
+            });
+
+            report.timeslot = ((report.apptimeout - elapsed) > report.queuetimeout) ? report.queuetimeout : (report.apptimeout - elapsed);
+
+            switch (report.strategy) {
+              case 'rrmemory':
+                diff = _.differenceBy(set1, set2, 'name');
+                agent = diff.shift();
+
+                if (agent) {
+                  report.available = [{
+                    id: agent.id,
+                    name: agent.name
+                  }];
+
+                  set2.push({
+                    id: agent.id,
+                    name: agent.name
+                  });
+
+                  saveReport(report);
+
+                } else {
+                  set2 = [];
+                  agent = set1.shift();
+
+                  if (agent) {
+                    report.available = [{
+                      id: agent.id,
+                      name: agent.name
+                    }];
+
+                    set2.push({
+                      id: agent.id,
+                      name: agent.name
+                    });
+                    saveReport(report);
+                  }
+                }
+
+                break;
+              case 'beepall':
+                report.available = set1;
+                saveReport(report);
+
+                break;
+              default:
+            }
+          })
+          .catch(function(err) {
+            console.error(err);
+          });
+
+      }, report.queuetimeout * 1000);
+
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+
+  // INTERROMPO IL ROUTING
+  ReportChat.afterUpdate(function(doc) {
+    if (doc.changed('connectid')) {
+      return ChatRoom
+        .findById(doc.roomid)
+        .then(function(ChatRoom) {
+          return ChatRoom
+            .addUser(doc.agentid, {
+              individualHooks: true
+            });
+        })
+        .then(function() {
+          return ReportChatSession
+            .update({
+              application: doc.application,
+              agentid: doc.agentid,
+              agentname: doc.agentname,
+              queueid: doc.queueid || null,
+              queuename: doc.queuename || null,
+              leaveAt: moment().format("YYYY-MM-DD HH:mm:ss")
+            }, {
+              where: {
+                uniqueid: doc.uniqueid
+              },
+              individualHooks: true
+            });
+        })
+        .then(function() {
+          clearInterval(interval);
+          clearTimeout(timeout);
+          return callback(null, true);
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    }
+  });
+
+  ReportChatSession.afterUpdate(function(doc) {
+    if (doc.changed('abandonAt')) {
+      return ChatRoom
+        .findById(doc.roomid)
+        .then(function(ChatRoom) {
+          return ChatRoom
+            .update({
+              status: 'CLOSED'
+            })
+        })
+        .then(function() {
+          clearInterval(interval);
+          clearTimeout(timeout);
+          return callback(null, true, true);
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    }
+  });
+
+  return {};
+}
+
+function saveReport(report) {
+  var reports = [];
+
+  report.available.forEach(function(agent, index) {
+    reports.push({
+      uniqueid: report.uniqueid,
+      visitorid: report.visitorid,
+      visitorname: report.visitorname,
+      visitoremail: report.visitoremail,
+      timeslot: report.timeslot,
+      websiteid: report.websiteid,
+      websitename: report.websitename,
+      websiteaddress: report.websiteaddress,
+      application: report.application,
+      agentname: agent.name,
+      agentid: agent.id,
+      queueid: report.queueid || null,
+      queuename: report.queuename || null,
+      roomid: report.roomid || null,
+      calledAt: moment().format("YYYY-MM-DD HH:mm:ss")
+    });
+  });
+
+  if (reports.length) {
+    return ReportChat
+      .bulkCreate(reports, {
+        individualHooks: true
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  }
+}
+
+// Export this file as a module
+module.exports = Session;
diff --git a/server/config/routing/mail/index.js b/server/config/routing/mail/index.js
index ba34076..9268b24 100644
--- a/server/config/routing/mail/index.js
+++ b/server/config/routing/mail/index.js
@@ -1 +1,216 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var moment = require('moment');
+var util = require('util');
+var async = require('async');
+
+var Util = require('../../util');
+
+var ReportMail = require('../../../models').ReportMail;
+var ReportMailSession = require('../../../models').ReportMailSession;
+var MailMessage = require('../../../models').MailMessage;
+var MailQueue = require('../../../models').MailQueue;
+var Interval = require('../../../models').Interval;
+var User = require('../../../models').User;
+
+var Session = require('./session');
+
+module.exports = function() {
+  console.log('Mail Routing Initialization..');
+
+  MailMessage.afterCreate(function(message) {
+    if (message.status === 'RECEIVED') {
+      handleMessage(message);
+    }
+  });
+}
+
+function handleEntityNotFound(message) {
+  return function(entity) {
+    if (!entity) {
+      throw {
+        message: message
+      };
+    }
+    return entity;
+  };
+}
+
+function handleMailRoom(_report, _fidelity) {
+  return function(room) {
+    _report.roomid = room.id;
+    _report.uniqueid = util.format('%s.%s', moment().unix(), room.id);
+    _report.subject = room.subject;
+    _fidelity.agents = _.map(room.Users, function(user) {
+      return {
+        id: user.id,
+        name: user.name
+      };
+    });
+
+    return room
+      .getMailAccount();
+  }
+}
+
+function handleMailAccount(_report, _fidelity) {
+  return function(account) {
+    _report.accountid = account.id;
+    _report.accountname = account.name;
+    _report.accountaddress = account.address;
+
+    _fidelity.fidelity = account.fidelity;
+
+    if (account.fidelity) {
+      _fidelity.apptimeout = _fidelity.queuetimeout = account.timeout;
+      _fidelity.fidelity = account.timeout;
+      _fidelity.strategy = 'beepall';
+    }
+
+    return account
+      .getMailApplications({
+        order: 'priority ASC',
+        include: [{
+          model: MailQueue,
+          attributes: ['id', 'name', 'timeout', 'strategy'],
+          include: [{
+            model: User,
+            attributes: ['id', 'name', 'online', 'mailPause']
+          }]
+        }, {
+          model: User,
+          attributes: ['id', 'name', 'online', 'mailPause']
+        }, {
+          model: Interval,
+          include: [{
+            all: true
+          }]
+        }]
+      });
+  }
+}
+
+function handleApps(_report, _fidelity) {
+  return function(apps) {
+    var waterfall = [];
+
+    ReportMailSession
+      .create(_.cloneDeep(_report))
+      .catch(function(err) {
+        console.error(err);
+      });
+
+    if (_fidelity.fidelity) {
+      _report.queuetimeout = _fidelity.fidelity;
+      _report.apptimeout = _fidelity.fidelity;
+      _report.agents = _fidelity.agents;
+      _report.application = 'fidelity';
+      _report.strategy = 'beepall';
+
+      var data = _.cloneDeep(_report);
+      if (data.agents.length) {
+        waterfall.push(function(callback) {
+          new Session(data, callback);
+        });
+      }
+    }
+
+    apps.forEach(function(app, index) {
+
+      var data = _.cloneDeep(_report);
+
+      data.apptimeout = app.timeout;
+      data.application = app.app;
+
+      switch (app.app) {
+        case 'queue':
+          data.queueid = app.MailQueue.id;
+          data.queuename = app.MailQueue.name;
+          data.strategy = app.MailQueue.strategy;
+
+          data.queuetimeout = app.MailQueue.timeout;
+          data.agents = _.map(app.MailQueue.Users, function(agent) {
+            return {
+              id: agent.id,
+              name: agent.name
+            }
+          });
+
+          break;
+        case 'agent':
+          data.queuetimeout = app.timeout;
+          data.strategy = 'beepall';
+
+          data.agents = [{
+            id: app.User.id,
+            name: app.User.name
+          }];
+
+          break;
+        default:
+      }
+
+      if (waterfall.length) {
+        waterfall.push(function(exit, callback) {
+          if (exit) {
+            callback(null, exit);
+          } else {
+            new Session(data, callback);
+          }
+        });
+      } else {
+        // PRIMA CALLBACK NESSUN DATO
+        waterfall.push(function(callback) {
+          new Session(data, callback);
+        });
+      }
+    });
+
+    async.waterfall(waterfall, function(err, managed) {
+      if (err) {
+        console.error(err);
+      }
+      if (!managed) {
+        return ReportMailSession
+          .update({
+            queuecallerleaveAt: moment().format("YYYY-MM-DD HH:mm:ss"),
+            queuecallerunmanagedAt: moment().format("YYYY-MM-DD HH:mm:ss")
+          }, {
+            where: {
+              uniqueid: _report.uniqueid
+            },
+            individualHooks: true
+          });
+      }
+
+      console.log('End mail routing..');
+    });
+  }
+}
+
+function handleMessage(message) {
+  var _fidelity = {
+    agents: []
+  };
+  var _report = {
+    messageid: message.id,
+    queuecallerjoinAt: moment().format("YYYY-MM-DD HH:mm:ss")
+  };
+
+  return message
+    .getMailRoom({
+      include: [{
+        model: User,
+        attributes: ['id', 'name', 'online', 'mailPause']
+      }]
+    })
+    .then(handleEntityNotFound('Unknown MailRoom..'))
+    .then(handleMailRoom(_report, _fidelity))
+    .then(handleEntityNotFound('Unknown MailAccount..'))
+    .then(handleMailAccount(_report, _fidelity))
+    .then(handleApps(_report, _fidelity))
+    .catch(function(err) {
+      console.error('err', err);
+    });
+}
diff --git a/server/config/routing/mail/session.js b/server/config/routing/mail/session.js
index 0d64147..171d4af 100644
--- a/server/config/routing/mail/session.js
+++ b/server/config/routing/mail/session.js
@@ -1 +1,258 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var moment = require('moment');
+
+var Agent = require('../../../models').User;
+var MailRoom = require('../../../models').MailRoom;
+var MailApplication = require('../../../models').MailApplication;
+var ReportMail = require('../../../models').ReportMail;
+var ReportMailSession = require('../../../models').ReportMailSession;
+
+// Define the factory
+function Session(report, callback) {
+  console.log('New Mail session...');
+  var elapsed = 0;
+  var interval, timeout;
+
+  Agent
+    .findAll({
+      where: {
+        id: {
+          $in: _.map(report.agents, 'id'),
+        },
+        online: true,
+        mailPause: false
+      },
+      include: [{
+        model: MailRoom,
+        attributes: ['id', 'status']
+      }],
+      attributes: ['id', 'name', 'mailCapacity']
+    })
+    .then(function(agents) {
+      var set1 = _.filter(agents, function(agent) {
+        if (agent.mailCapacity === 0) {
+          return true;
+        } else {
+          return agent.mailCapacity > _.filter(agent.MailRooms, function(room) {
+            return room.id !== report.roomid && room.status !== 'CLOSED'
+          }).length;
+        }
+      });
+      var set2 = [];
+
+      var diff;
+      var agent;
+
+      report.timeslot = ((report.apptimeout - elapsed) > report.queuetimeout) ? report.queuetimeout : (report.apptimeout - elapsed);
+
+      switch (report.strategy) {
+        case 'rrmemory':
+          diff = _.differenceBy(set1, set2, 'name');
+          agent = diff.shift();
+
+          if (agent) {
+            report.available = [{
+              id: agent.id,
+              name: agent.name
+            }];
+
+            set2.push({
+              id: agent.id,
+              name: agent.name
+            });
+            saveReport(report);
+
+          } else {
+            set2 = [];
+            agent = set1.shift();
+
+            if (agent) {
+              set2.push({
+                id: agent.id,
+                name: agent.name
+              });
+              saveReport(report);
+            }
+          }
+
+          break;
+        case 'beepall':
+          report.available = set1;
+          saveReport(report);
+
+          break;
+        default:
+      }
+
+      // APPLICATION TIMEOUT
+      timeout = setTimeout(function() {
+        clearInterval(interval);
+        return callback(null, false);
+      }, report.apptimeout * 1000);
+
+      interval = setInterval(function() {
+        elapsed += report.queuetimeout;
+
+        return Agent
+          .findAll({
+            where: {
+              id: {
+                $in: _.map(report.agents, 'id'),
+              },
+              online: true,
+              mailPause: false
+            },
+            include: [{
+              model: MailRoom,
+              attributes: ['id', 'status']
+            }],
+            attributes: ['id', 'name', 'mailCapacity']
+          })
+          .then(function(agents) {
+            set1 = _.filter(agents, function(agent) {
+              if (agent.mailCapacity === 0) {
+                return true;
+              } else {
+                return agent.mailCapacity > _.filter(agent.MailRooms, function(room) {
+                  return room.id !== report.roomid && room.status !== 'CLOSED'
+                }).length;
+              }
+            });
+
+            report.timeslot = ((report.apptimeout - elapsed) > report.queuetimeout) ? report.queuetimeout : (report.apptimeout - elapsed);
+
+            switch (report.strategy) {
+              case 'rrmemory':
+                diff = _.differenceBy(set1, set2, 'name');
+                agent = diff.shift();
+
+                if (agent) {
+                  report.available = [{
+                    id: agent.id,
+                    name: agent.name
+                  }];
+
+                  set2.push({
+                    id: agent.id,
+                    name: agent.name
+                  });
+
+                  saveReport(report);
+
+                } else {
+                  set2 = [];
+                  agent = set1.shift();
+
+                  if (agent) {
+                    report.available = [{
+                      id: agent.id,
+                      name: agent.name
+                    }];
+
+                    set2.push({
+                      id: agent.id,
+                      name: agent.name
+                    });
+                    saveReport(report);
+                  }
+                }
+
+                break;
+              case 'beepall':
+                report.available = set1;
+                saveReport(report);
+
+                break;
+              default:
+            }
+          })
+          .catch(function(err) {
+            console.error(err);
+          });
+
+      }, report.queuetimeout * 1000);
+
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+
+  // INTERROMPO IL ROUTING
+  ReportMail.afterUpdate(function(doc) {
+    if (doc.changed('connectid')) {
+      return MailRoom
+        .findById(doc.roomid)
+        .then(function(mailRoom) {
+          return mailRoom
+            .addUser(doc.memberid, {
+              individualHooks: true
+            });
+        })
+        .then(function() {
+          return ReportMailSession
+            .update({
+              application: doc.application,
+              memberid: doc.memberid,
+              membername: doc.membername,
+              queue: doc.queue || null,
+              queuename: doc.queuename || null,
+              fidelity: doc.application === 'fidelity' ? doc.timeslot : null,
+              queuecallerleaveAt: moment().format("YYYY-MM-DD HH:mm:ss")
+            }, {
+              where: {
+                uniqueid: doc.uniqueid
+              },
+              individualHooks: true
+            });
+        })
+        .then(function() {
+          clearInterval(interval);
+          clearTimeout(timeout);
+          return callback(null, true);
+        })
+        .catch(function(err) {
+          console.error(err);
+        });
+    }
+  });
+
+  return {};
+}
+
+function saveReport(report) {
+  var reports = [];
+
+  report.available.forEach(function(agent, index) {
+    reports.push({
+      uniqueid: report.uniqueid,
+      timeslot: report.timeslot,
+      subject: report.subject,
+      accountid: report.accountid,
+      accountname: report.accountname,
+      accountaddress: report.accountaddress,
+      application: report.application,
+      membername: agent.name,
+      memberid: agent.id,
+      queue: report.queue || null,
+      queuename: report.queuename || null,
+      roomid: report.roomid || null,
+      messageid: report.messageid || null,
+      calledAt: moment().format("YYYY-MM-DD HH:mm:ss")
+    });
+  });
+
+  if (reports.length) {
+    return ReportMail
+      .bulkCreate(reports, {
+        individualHooks: true
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  }
+}
+
+// Export this file as a module
+module.exports = Session;
diff --git a/server/config/seed.js b/server/config/seed.js
index 85a381f..f15b870 100644
--- a/server/config/seed.js
+++ b/server/config/seed.js
@@ -1 +1,1236 @@
\ No newline at end of file
+/**
+ * Populate DB with sample data on server start
+ * to disable, edit config/environment/index.js, and set `seedDB: false`
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var models = require('../models');
+
+models.User
+  .bulkCreate([{
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Giuseppe Careri',
+    email: [email-masked]',
+    name: 'giuseppe.careri',
+    password: '75xcally75',
+    defaultuser: 'giuseppe.careri',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1000
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Daniele Cinti',
+    email: [email-masked]',
+    name: 'daniele.cinti',
+    password: '75xcally75',
+    defaultuser: 'daniele.cinti',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1001
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Andrea Bianco',
+    email: [email-masked]',
+    name: 'andrea.bianco',
+    password: '75xcally75',
+    defaultuser: 'andrea.bianco',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1002
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Massimiliano Bungaro',
+    email: [email-masked]',
+    name: 'massimiliano.bungaro',
+    password: '75xcally75',
+    defaultuser: 'massimiliano.bungaro',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1003
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Valerio Ciotta',
+    email: [email-masked]',
+    name: 'valerio.ciotta',
+    password: '75xcally75',
+    defaultuser: 'valerio.ciotta',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1004
+  }, {
+    provider: 'local',
+    role: 'admin',
+    fullname: 'Clarotech',
+    email: [email-masked]',
+    name: 'clarotech',
+    password: 'clarotech',
+    defaultuser: 'clarotech',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1005
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Abigael',
+    email: [email-masked]',
+    name: 'abigael',
+    password: '75xcally75',
+    defaultuser: 'abigael',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1006
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Tony Russell',
+    email: [email-masked]',
+    name: 'tony.russell',
+    password: 'clarotech',
+    defaultuser: 'tony.russell',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1007
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Colin Fair',
+    email: [email-masked]',
+    name: 'colin.fair',
+    password: 'clarotech',
+    defaultuser: 'colin.fair',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1008
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Diego Gosmar',
+    email: [email-masked]',
+    name: 'diego.gosmar',
+    password: '75xcally75',
+    defaultuser: 'diego.gosmar',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1009
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Giuseppe Innamorato',
+    email: [email-masked]',
+    name: 'giuseppe.innamorato',
+    password: '75xcally75',
+    defaultuser: 'giuseppe.innamorato',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'upd',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1010
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Alessandra Bessone',
+    email: [email-masked]',
+    name: 'alessandra.bessone',
+    password: '75xcally75',
+    defaultuser: 'alessandra.bessone',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'upd',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1011
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Felice Briscese',
+    email: [email-masked]',
+    name: 'felice.briscese',
+    password: '75xcally75',
+    defaultuser: 'felice.briscese',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'upd',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1012
+  }, {
+    provider: 'local',
+    role: 'admin',
+    fullname: 'xCally',
+    email: [email-masked]',
+    name: 'xcally',
+    password: '388xshuttle388',
+    defaultuser: 'xcally',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1013
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'John Doe',
+    email: [email-masked]',
+    name: 'john.doe',
+    password: '75xcally75',
+    defaultuser: 'john.doe',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1014
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Jane Miller',
+    email: [email-masked]',
+    name: 'jane.miller',
+    password: '75xcally75',
+    defaultuser: 'jane.miller',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1015
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating users');
+  });
+
+models.Trunk
+  .bulkCreate([{
+    name: 'citalia',
+    type: 'friend',
+    insecure: 'port,invite',
+    defaultuser: '01119827770',
+    secret: 'T!NPnv-qjCMe9v',
+    password: 'T!NPnv-qjCMe9v',
+    context: 'from-voip-provider',
+    language: 'it',
+    host: 'voip.eutelia.it',
+    fromdomain: 'voip.eutelia.it',
+    dtmfmode: 'rfc2833',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    callerid: ' "" <>',
+    limitonpeers: 'yes',
+    callcounter: 'yes',
+    disallow: 'all',
+    allow: 'ulaw;alaw;gsm',
+    canreinvite: 'no',
+    directmedia: 'no'
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating trunks');
+  });
+
+
+// Init modules table
+models.Module
+  .bulkCreate([{
+    name: "dashboard",
+    title: "APPLICATION_DASHBOARD"
+  }, {
+    name: "users",
+    title: "APPLICATION_USERS"
+  }, {
+    name: "agents",
+    title: "APPLICATION_AGENTS"
+  }, {
+    name: "teams",
+    title: "APPLICATION_TEAMS"
+  }, {
+    name: "chat",
+    title: "APPLICATION_CHAT"
+  }, {
+    name: "voice",
+    title: "APPLICATION_VOICE"
+  }, {
+    name: "mail",
+    title: "APPLICATION_MAIL"
+  }, {
+    name: "fax",
+    title: "APPLICATION_FAX"
+  }, {
+    name: "audio",
+    title: "APPLICATION_AUDIO"
+  }, {
+    name: "callysquare",
+    title: "APPLICATION_CALLYSQUARE"
+  }, {
+    name: "analytics",
+    title: "APPLICATION_ANALYTICS"
+  }, {
+    name: "realtime",
+    title: "APPLICATION_REALTIME"
+  }, {
+    name: "voicemails",
+    title: "APPLICATION_VOICEMAILS"
+  }, {
+    name: "settings",
+    title: "APPLICATION_SETTINGS"
+  }], {
+    ignoreDuplicates: true
+  }).then(function() {
+    console.log('Finished populating modules');
+  });
+
+models.Team
+  .create({
+    name: "T1",
+    description: "Default Team",
+    defaultEntry: true
+  })
+  .then(function(team) {
+
+    models.User
+      .findAll({
+        where: {
+          role: 'agent'
+        }
+      })
+      .then(function(users) {
+        team
+          .addUsers(_.map(users, 'id'))
+          .then(function() {
+            models.ChatQueue
+              .create({
+                name: "CQ1",
+                description: "CQ1 Desc",
+                timeout: 5,
+                strategy: "rrmemory"
+              })
+              .then(function(chatQueue) {
+                console.log('Finished populating chat queue 1');
+
+                models.Team
+                  .findOne({
+                    where: {
+                      name: 'T1'
+                    },
+                    include: [{
+                      all: true
+                    }]
+                  })
+                  .then(function(team) {
+                    chatQueue
+                      .addUsers(_.map(team.Users, 'id'))
+                      .catch(function(err) {
+                        // console.error(err);
+                      });
+                  })
+                  .catch(function(err) {
+                    // console.error(err);
+                  });
+              });
+
+            models.MailQueue
+              .create({
+                name: "MQ1",
+                description: "MQ1 Desc",
+                timeout: 5,
+                strategy: "rrmemory"
+              })
+              .then(function(mailQueue) {
+                console.log('Finished populating mail queue 1');
+
+                models.Team
+                  .findOne({
+                    where: {
+                      name: 'T1'
+                    },
+                    include: [{
+                      all: true
+                    }]
+                  })
+                  .then(function(team) {
+                    if (team) {
+                      console.log('team found');
+                    } else {
+                      console.log('test');
+                    }
+                    mailQueue
+                      .addUsers(_.map(team.Users, 'id'))
+                      .catch(function(err) {
+                        // console.error(err);
+                      });
+                  })
+                  .catch(function(err) {
+                    // console.error(err);
+                  });
+              });
+
+            models.FaxQueue
+              .create({
+                name: "FQ1",
+                description: "FQ1 Desc",
+                timeout: 5,
+                strategy: "rrmemory"
+              })
+              .then(function(faxQueue) {
+                console.log('Finished populating fax queue 1');
+
+                models.Team
+                  .findOne({
+                    where: {
+                      name: 'T1'
+                    },
+                    include: [{
+                      all: true
+                    }]
+                  })
+                  .then(function(team) {
+                    faxQueue
+                      .addUsers(_.map(team.Users, 'id'))
+                      .catch(function(err) {
+                        // console.error(err);
+                      });
+                  })
+                  .catch(function(err) {
+                    // console.error(err);
+                  });
+              });
+          })
+          .catch(function(err) {
+            // console.error(err);
+          });
+      })
+      .catch(function(err) {
+        // console.error(err);
+      });
+  })
+  .catch(function(err) {
+    // console.error(err);
+  });
+
+models.MailAccount
+  .create({
+    name: "xCally Development",
+    description: "xCally Development",
+    address: "[email-masked]"
+  })
+  .then(function(mailAccount) {
+    console.log('Finished populating mail account');
+    models.MailServerIn
+      .create({
+        description: 'xcally',
+        host: 'imap.xcally.com',
+        username: [email-masked]',
+        password: 'phokucloka',
+        port: 143,
+        mailbox: 'INBOX',
+        ssl: false,
+        delete: false,
+        filter: 'UNSEEN',
+        protocol: 'IMAP',
+        MailAccountId: mailAccount.id
+      })
+      .then(function() {
+        console.log('Finished populating mail server in');
+        models.MailServerOut
+          .create({
+            description: 'xcally',
+            host: 'smtp.xcally.com',
+            username: [email-masked]',
+            password: 'phokucloka',
+            port: 587,
+            ssl: false,
+            MailAccountId: mailAccount.id
+          })
+          .then(function() {
+            console.log('Finished populating mail server out');
+          })
+          .catch(function(err) {
+            // console.error(err);
+          });
+      })
+      .catch(function(err) {
+        // console.error(err);
+      });
+  });
+
+models.FaxAccount
+  .create({
+    description: 'FA1 Desc',
+    name: 'FA1',
+    phone: '01119827770',
+    ecm: true,
+    faxheader: 'xCALLY',
+    localid: '01119827770',
+    maxrate: '9600',
+    minrate: '14400',
+  })
+  .then(function(faxAccount) {
+    console.log('Finished populating fax account');
+
+    models.Trunk
+      .findOne({
+        where: {
+          name: 'citalia'
+        }
+      })
+      .then(function(trunk) {
+        faxAccount
+          .setTrunk(trunk)
+          .catch(function(err) {
+            // console.error(err);
+          });
+
+
+        models.VoiceExtension
+          .create({
+            context: 'from-voip-provider',
+            exten: '01119827770',
+            priority: 1,
+            app: 'Goto',
+            appdata: 'inbound-fax,s,1',
+            type: 'inbound-fax'
+          })
+
+      })
+      .catch(function(err) {
+        // console.error(err);
+      });
+
+    models.FaxQueue
+      .findAll()
+      .then(function(faxQueues) {
+        models.FaxApplication
+          .create({
+            app: 'queue',
+            appdata: faxQueues[0].id,
+            priority: 1,
+            timeout: 30,
+            FaxAccountId: faxAccount.id,
+            FaxQueueId: faxQueues[0].id
+          })
+          .then(function() {
+            console.log('Finished populating chat website application 1');
+          })
+          .catch(function(err) {
+            // console.error(err);
+          });
+
+        models.FaxApplication
+          .create({
+            app: 'queue',
+            appdata: faxQueues[1].id,
+            priority: 2,
+            timeout: 30,
+            FaxAccountId: faxAccount.id,
+            FaxQueueId: faxQueues[1].id
+          })
+          .then(function() {
+            console.log('Finished populating chat website application 2');
+          })
+          .catch(function(err) {
+            // console.error(err);
+          });
+      })
+      .catch(function(err) {
+        // console.error(err);
+      });
+  })
+  .catch(function(err) {
+    // console.error(err);
+  });
+// models.FaxAccount
+//   .create({
+//     description: 'FA1 Desc',
+//     name: 'FA1',
+//     phone: '01119827770',
+//     ecm: true,
+//     faxheader: 'xCALLY',
+//     localid: '01119827770',
+//     maxrate: '9600',
+//     minrate: '14400',
+//   })
+//   .then(function (faxAccount) {
+//     console.log('Finished populating fax account');
+//
+//     models.Trunk
+//       .findOne({
+//         where: {
+//           name: 'citalia'
+//         }
+//       })
+//       .then(function (trunk) {
+//         faxAccount
+//           .setTrunk(trunk)
+//           .catch(function (err) {
+//             // console.error(err);
+//           });
+//
+//
+//         models.VoiceExtension
+//           .create({
+//             context: 'from-voip-provider',
+//             exten: '01119827770',
+//             priority: 1,
+//             app: 'Goto',
+//             appdata: 'inbound-fax,s,1',
+//             type: 'inbound-fax',
+//             TrunkId: trunk.name
+//           })
+//
+//       })
+//       .catch(function (err) {
+//         // console.error(err);
+//       });
+//
+//     models.FaxQueue
+//       .findAll()
+//       .then(function (faxQueues) {
+//         models.FaxApplication
+//           .create({
+//             app: 'queue',
+//             appdata: faxQueues[0].id,
+//             priority: 1,
+//             timeout: 30,
+//             FaxAccountId: faxAccount.id,
+//             FaxQueueId: faxQueues[0].id
+//           })
+//           .then(function () {
+//             console.log('Finished populating chat website application 1');
+//           })
+//           .catch(function (err) {
+//             // console.error(err);
+//           });
+//
+//         models.FaxApplication
+//           .create({
+//             app: 'queue',
+//             appdata: faxQueues[1].id,
+//             priority: 2,
+//             timeout: 30,
+//             FaxAccountId: faxAccount.id,
+//             FaxQueueId: faxQueues[1].id
+//           })
+//           .then(function () {
+//             console.log('Finished populating chat website application 2');
+//           })
+//           .catch(function (err) {
+//             // console.error(err);
+//           });
+//       })
+//       .catch(function (err) {
+//         // console.error(err);
+//       });
+//   })
+//   .catch(function (err) {
+//     // console.error(err);
+//   });
+
+models.VoiceExtension
+  .bulkCreate([{
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 1,
+    app: 'NoOp',
+    appdata: 'Fax UUID: ${FAXUUID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 2,
+    app: 'UserEvent',
+    appdata: 'Fax,uuid: ${FAXUUID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 3,
+    app: 'Set',
+    appdata: 'FAXOPT(filename):${FAXFILE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 4,
+    app: 'Set',
+    appdata: 'FAXOPT(ecm):${ECM}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 5,
+    app: 'Set',
+    appdata: 'FAXOPT(headerinfo):${FAXHEADER}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 6,
+    app: 'Set',
+    appdata: 'FAXOPT(localstationid):${LOCALID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 7,
+    app: 'Set',
+    appdata: 'FAXOPT(maxrate):${MAXRATE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 8,
+    app: 'Set',
+    appdata: 'FAXOPT(minrate):${MINRATE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 9,
+    app: 'SendFAX',
+    appdata: '${FAXFILE},d',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 10,
+    app: 'NoOp',
+    appdata: 'ERROR "${TIMESTAMP}" "NO_STATUS" "NO_PAGES"',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 1,
+    app: 'NoOp',
+    appdata: 'FAXOPT(ecm) : ${FAXOPT(ecm)}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 2,
+    app: 'NoOp',
+    appdata: 'FaxStatus : ${FAXSTATUS}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 3,
+    app: 'NoOp',
+    appdata: 'FaxStatusString : ${FAXSTATUSSTRING}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 4,
+    app: 'NoOp',
+    appdata: 'FaxError : ${FAXERROR}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 5,
+    app: 'NoOp',
+    appdata: 'RemoteStationID : ${REMOTESTATIONID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 6,
+    app: 'NoOp',
+    appdata: 'FaxPages : ${FAXPAGES}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 7,
+    app: 'NoOp',
+    appdata: 'FaxBitRate : ${FAXBITRATE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 8,
+    app: 'NoOp',
+    appdata: 'FaxResolution : ${FAXRESOLUTION}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 9,
+    app: 'NoOp',
+    appdata: 'OK "${TIMESTAMP}" "${FAXSTATUSSTRING}" "${FAXPAGES}"',
+    type: 'outbound-fax'
+  }], {
+    ignoreDuplicates: true
+  })
+  .then(function() {
+    console.log('Finished populating fax outbound voice extension');
+  });
+
+models.VoiceExtension
+  .bulkCreate([{
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 1,
+    app: 'NoOp',
+    appdata: '**** FAX RECEIVED from ${CALLERID(num)} ${STRFTIME(${EPOCH},,%c)} ****',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 2,
+    app: 'Set',
+    appdata: 'FAXOPT(ecm):yes',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 3,
+    app: 'Set',
+    appdata: 'FILENAME:${UNIQUEID}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 4,
+    app: 'Set',
+    appdata: 'FAXFILE:${FILENAME}.tif',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 5,
+    app: 'Set',
+    appdata: 'FAXOPT(headerinfo):Received by XeniaLAB ${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 6,
+    app: 'Set',
+    appdata: 'FAXOPT(localstationid):${LOCALID}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 7,
+    app: 'Set',
+    appdata: 'FAXOPT(maxrate):${MAXRATE}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 8,
+    app: 'Set',
+    appdata: 'FAXOPT(minrate):${MINRATE}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 9,
+    app: 'NoOp',
+    appdata: 'FAXOPT(ecm) : ${FAXOPT(ecm)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 10,
+    app: 'NoOp',
+    appdata: 'FAXOPT(headerinfo) : ${FAXOPT(headerinfo)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 11,
+    app: 'NoOp',
+    appdata: 'FAXOPT(localstationid) : ${FAXOPT(localstationid)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 12,
+    app: 'NoOp',
+    appdata: 'FAXOPT(maxrate) : ${FAXOPT(maxrate)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 13,
+    app: 'NoOp',
+    appdata: 'FAXOPT(minrate) : ${FAXOPT(minrate)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 14,
+    app: 'NoOp',
+    appdata: '**** RECEIVING FAX : ${FAXFILE} ****',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 15,
+    app: 'ReceiveFax',
+    appdata: '/tmp/${FAXFILE}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 16,
+    app: 'Hangup',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 'h',
+    priority: 1,
+    app: 'NoOp',
+    appdata: 'FAXOPT(ecm) : ${FAXOPT(ecm)}',
+    type: 'inbound-fax'
+  }], {
+    ignoreDuplicates: true
+  })
+  .then(function() {
+    console.log('Finished populating fax outbound voice extension');
+  });
+
+models.SquareProject
+  .bulkCreate([{
+    name: "project1",
+    description: "description project"
+  }], {
+    ignoreDuplicates: true
+  }).then(function() {
+    console.log('Finished populating square projects');
+  });
+
+models.ChatWebsite
+  .create({
+    name: 'xCally Shop',
+    description: 'xCally Shop',
+    address: 'http://www.xcally.com/shop/',
+    color: '#f51111',
+    color_focus: '#f51111',
+    color_button: '#f51111',
+    remote: 'http://localhost:9000',
+    animation: true,
+    header_shape: 'squared',
+    header_online: 'We are here!',
+    online_message: 'Questions? Insert your name and email address to start a live-chat with our support team.',
+    username_placeholder: 'Your name',
+    email_placeholder: 'Your email address',
+    start_chat_button: 'Chat',
+    header_offline: 'Contact us',
+    offline_message: "<strong>We're not online.</strong><br />It doesn't mean we're not there to help - leave your message below and we'll be in touch as soon as possible.'",
+    enquiry_message_placeholder: 'Your message...',
+    enquiry_button: 'Leave message',
+    download_transcript: true
+  })
+  .then(function(chatWebsite) {
+    console.log('Finished populating chat website');
+    models.ChatQueue
+      .findAll()
+      .then(function(chatQueues) {
+        models.ChatApplication
+          .create({
+            app: 'queue',
+            appdata: chatQueues[0].id,
+            priority: 1,
+            timeout: 30,
+            ChatWebsiteId: chatWebsite.id,
+            ChatQueueId: chatQueues[0].id
+          })
+          .then(function() {
+            console.log('Finished populating chat website application 1');
+          })
+          .catch(function(err) {
+            // console.error(err);
+          });
+
+        models.ChatApplication
+          .create({
+            app: 'queue',
+            appdata: chatQueues[1].id,
+            priority: 2,
+            timeout: 30,
+            ChatWebsiteId: chatWebsite.id,
+            ChatQueueId: chatQueues[1].id
+          })
+          .then(function() {
+            console.log('Finished populating chat website application 2');
+          })
+          .catch(function(err) {
+            // console.error(err);
+          });
+      })
+      .catch(function(err) {
+        // console.error(err);
+      });
+  })
+  .catch(function(err) {
+    // console.error(err);
+  });
+
+models.Variable
+  .bulkCreate([{
+    name: "variable1",
+    description: "description project"
+  }], {
+    ignoreDuplicates: true
+  }).then(function() {
+    console.log('Finished populating variables');
+  });
+
+models.VoiceContext
+  .bulkCreate([{
+    name: "from-sip",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "from-voip-provider",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "inbound-fax",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "outbound-fax",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "from-voicemail",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating context variables');
+  });
+
+models.VoiceMusicOnHold
+  .bulkCreate([{
+    name: "default",
+    mode: "files",
+    directory: 'var/lib/asterisk/moh',
+    sort: 'alpha',
+    format: 'wav',
+    defaultEntry: true
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating moh variables');
+  });
+
+models.Settings
+  .bulkCreate([{
+    id: 1,
+    min_internal: 1000,
+    soundPath: '/var/opt/motion/server/files/sounds/converted'
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating moh variables');
+  });
+
+models.Dashboard
+  .bulkCreate([{
+    id: 1,
+    name: 'Motion Dashboard',
+    model: JSON.stringify({
+      title: 'Motion Dashboard',
+      rows: [{
+        columns: [{
+          styleClass: 'col-md-4',
+          widgets: [{
+            type: 'clock',
+            title: 'Clock',
+            config: {
+              timePattern: 'hh:mm:ss a',
+              datePattern: 'dddd, MMMM Do YYYY'
+            }
+          }]
+        }, {
+          styleClass: 'col-md-8',
+          widgets: [{
+            type: 'iframe',
+            title: 'Iframe',
+            config: {
+              url: 'https://demo.xcally.com/shop/'
+            }
+          }]
+        }]
+      }]
+    }),
+    defaultEntry: true,
+    editable: false
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating moh variables');
+  });
+
+models.Integration
+  .bulkCreate([{
+    name: 'Zendesk',
+    version: '1.0.0',
+    active: false,
+    author: 'XeniaLAB s.r.l.',
+    logo: 'assets/images/media/zendesk2.png',
+    state: 'zendesk',
+    description: 'Description for Zendesk'
+  }, {
+    name: 'Desk.com',
+    version: '1.0.0',
+    active: false,
+    author: 'XeniaLAB s.r.l.',
+    logo: 'assets/images/media/desk2.png',
+    state: 'desk',
+    description: 'Description for Desk.com'
+  }, {
+    name: 'Salesforce',
+    version: '1.0.0',
+    active: false,
+    author: 'XeniaLAB s.r.l.',
+    logo: 'assets/images/media/salesforce2.png',
+    state: 'salesforce',
+    description: 'Description for Salesforce'
+  }, {
+    name: 'Freshdesk',
+    version: '1.0.0',
+    active: false,
+    author: 'XeniaLAB s.r.l.',
+    logo: 'assets/images/media/freshdesk.png',
+    state: 'freshdesk',
+    description: 'Description for Freshdesk'
+  }, {
+    name: 'SugarCRM',
+    version: '1.0.0',
+    active: false,
+    author: 'XeniaLAB s.r.l.',
+    logo: 'assets/images/media/sugarcrm2.png',
+    state: 'sugarcrm',
+    description: 'Description for SugarCRM'
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating integrations');
+  });
+
+models.ReportTree
+  .bulkCreate([{
+    id: 1,
+    tree: '[{"id":"1","parent":"#","text":"Motion Reports","icon":"icon-rocket","state":{"selected":true,"opened":true}},{"id":"2","parent":"1","text":"Basic Node","state":{"selected":false,"opened":false}}]',
+  }, {
+    id: 2,
+    tree: '[{"id":"1","text":"Custom Reports","icon":"icon-book-open","li_attr":{"id":"1"},"a_attr":{"href":"#","id":"1_anchor"},"state":{"loaded":true,"opened":true,"selected":false,"disabled":false},"data":{},"parent":"#"},{"id":"2","text":"Basic Node","icon":true,"li_attr":{"id":"2"},"a_attr":{"href":"#","id":"2_anchor"},"state":{"loaded":true,"opened":true,"selected":false,"disabled":false},"data":{},"parent":"1"}]'
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating integrations');
+  });
+
+models.Service
+  .bulkCreate([{
+    id: 1,
+    name: 'Motion Service',
+    defaultEntry: true
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating integrations');
+  });
diff --git a/server/config/seedp.js b/server/config/seedp.js
index 81ff4a6..4b844ca 100644
--- a/server/config/seedp.js
+++ b/server/config/seedp.js
@@ -1 +1,550 @@
\ No newline at end of file
+/**
+ * Populate DB with sample data on server start
+ * to disable, edit config/environment/index.js, and set `seedDB: false`
+ */
+
+'use strict';
+
+var _ = require('lodash');
+var models = require('../models');
+var _users;
+var _team;
+
+models.User
+  .bulkCreate([{
+    provider: 'local',
+    role: 'admin',
+    fullname: 'Administrator',
+    email: [email-masked]',
+    name: 'admin',
+    password: 'password',
+    defaultuser: 'xcally',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1000
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'John Doe',
+    email: [email-masked]',
+    name: 'john.doe',
+    password: 'shuttle55',
+    defaultuser: 'john.doe',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1001
+  }, {
+    provider: 'local',
+    role: 'agent',
+    fullname: 'Jane Miller',
+    email: [email-masked]',
+    name: 'jane.miller',
+    password: 'shuttle55',
+    defaultuser: 'jane.miller',
+    host: 'dynamic',
+    type: 'FRIEND',
+    transport: 'udp',
+    allow: 'alaw;ulaw;gsm',
+    qualify: 'yes',
+    nat: 'force_rport,comedia',
+    disallow: 'all',
+    context: 'from-sip',
+    internal: 1002
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function(users) {
+    console.log('Finished populating users');
+    _users = _.filter(users, {
+      role: 'agent'
+    });
+    return models.Team
+      .create({
+        name: "Default Team",
+        description: "Default Team",
+        defaultEntry: true
+      });
+  }).then(function(team) {
+    _team = team;
+    return _team.setUsers(_users);
+  });
+
+// Init modules table
+models.Module
+  .bulkCreate([{
+    name: "dashboard",
+    title: "APPLICATION_DASHBOARD"
+  }, {
+    name: "users",
+    title: "APPLICATION_USERS"
+  }, {
+    name: "agents",
+    title: "APPLICATION_AGENTS"
+  }, {
+    name: "teams",
+    title: "APPLICATION_TEAMS"
+  }, {
+    name: "chat",
+    title: "APPLICATION_CHAT"
+  }, {
+    name: "voice",
+    title: "APPLICATION_VOICE"
+  }, {
+    name: "mail",
+    title: "APPLICATION_MAIL"
+  }, {
+    name: "fax",
+    title: "APPLICATION_FAX"
+  }, {
+    name: "audio",
+    title: "APPLICATION_AUDIO"
+  }, {
+    name: "callysquare",
+    title: "APPLICATION_CALLYSQUARE"
+  }, {
+    name: "analytics",
+    title: "APPLICATION_ANALYTICS"
+  }, {
+    name: "realtime",
+    title: "APPLICATION_REALTIME"
+  }, {
+    name: "voicemails",
+    title: "APPLICATION_VOICEMAILS"
+  }, {
+    name: "settings",
+    title: "APPLICATION_SETTINGS"
+  }], {
+    ignoreDuplicates: true
+  }).then(function() {
+    console.log('Finished populating modules');
+  });
+
+models.VoiceExtension
+  .bulkCreate([{
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 1,
+    app: 'NoOp',
+    appdata: 'Fax UUID: ${FAXUUID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 2,
+    app: 'UserEvent',
+    appdata: 'Fax,uuid: ${FAXUUID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 3,
+    app: 'Set',
+    appdata: 'FAXOPT(filename):${FAXFILE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 4,
+    app: 'Set',
+    appdata: 'FAXOPT(ecm):${ECM}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 5,
+    app: 'Set',
+    appdata: 'FAXOPT(headerinfo):${FAXHEADER}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 6,
+    app: 'Set',
+    appdata: 'FAXOPT(localstationid):${LOCALID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 7,
+    app: 'Set',
+    appdata: 'FAXOPT(maxrate):${MAXRATE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 8,
+    app: 'Set',
+    appdata: 'FAXOPT(minrate):${MINRATE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 9,
+    app: 'SendFAX',
+    appdata: '${FAXFILE},d',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 's',
+    priority: 10,
+    app: 'NoOp',
+    appdata: 'ERROR "${TIMESTAMP}" "NO_STATUS" "NO_PAGES"',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 1,
+    app: 'NoOp',
+    appdata: 'FAXOPT(ecm) : ${FAXOPT(ecm)}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 2,
+    app: 'NoOp',
+    appdata: 'FaxStatus : ${FAXSTATUS}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 3,
+    app: 'NoOp',
+    appdata: 'FaxStatusString : ${FAXSTATUSSTRING}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 4,
+    app: 'NoOp',
+    appdata: 'FaxError : ${FAXERROR}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 5,
+    app: 'NoOp',
+    appdata: 'RemoteStationID : ${REMOTESTATIONID}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 6,
+    app: 'NoOp',
+    appdata: 'FaxPages : ${FAXPAGES}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 7,
+    app: 'NoOp',
+    appdata: 'FaxBitRate : ${FAXBITRATE}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 8,
+    app: 'NoOp',
+    appdata: 'FaxResolution : ${FAXRESOLUTION}',
+    type: 'outbound-fax'
+  }, {
+    context: 'outbound-fax',
+    exten: 'h',
+    priority: 9,
+    app: 'NoOp',
+    appdata: 'OK "${TIMESTAMP}" "${FAXSTATUSSTRING}" "${FAXPAGES}"',
+    type: 'outbound-fax'
+  }], {
+    ignoreDuplicates: true
+  })
+  .then(function() {
+    console.log('Finished populating fax outbound voice extension');
+  });
+
+models.VoiceExtension
+  .bulkCreate([{
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 1,
+    app: 'NoOp',
+    appdata: '**** FAX RECEIVED from ${CALLERID(num)} ${STRFTIME(${EPOCH},,%c)} ****',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 2,
+    app: 'Set',
+    appdata: 'FAXOPT(ecm):yes',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 3,
+    app: 'Set',
+    appdata: 'FILENAME:${UNIQUEID}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 4,
+    app: 'Set',
+    appdata: 'FAXFILE:${FILENAME}.tif',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 5,
+    app: 'Set',
+    appdata: 'FAXOPT(headerinfo):Received by XeniaLAB ${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 6,
+    app: 'Set',
+    appdata: 'FAXOPT(localstationid):${LOCALID}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 7,
+    app: 'Set',
+    appdata: 'FAXOPT(maxrate):${MAXRATE}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 8,
+    app: 'Set',
+    appdata: 'FAXOPT(minrate):${MINRATE}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 9,
+    app: 'NoOp',
+    appdata: 'FAXOPT(ecm) : ${FAXOPT(ecm)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 10,
+    app: 'NoOp',
+    appdata: 'FAXOPT(headerinfo) : ${FAXOPT(headerinfo)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 11,
+    app: 'NoOp',
+    appdata: 'FAXOPT(localstationid) : ${FAXOPT(localstationid)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 12,
+    app: 'NoOp',
+    appdata: 'FAXOPT(maxrate) : ${FAXOPT(maxrate)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 13,
+    app: 'NoOp',
+    appdata: 'FAXOPT(minrate) : ${FAXOPT(minrate)}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 14,
+    app: 'NoOp',
+    appdata: '**** RECEIVING FAX : ${FAXFILE} ****',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 15,
+    app: 'ReceiveFax',
+    appdata: '/tmp/${FAXFILE}',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 's',
+    priority: 16,
+    app: 'Hangup',
+    type: 'inbound-fax'
+  }, {
+    context: 'inbound-fax',
+    exten: 'h',
+    priority: 1,
+    app: 'NoOp',
+    appdata: 'FAXOPT(ecm) : ${FAXOPT(ecm)}',
+    type: 'inbound-fax'
+  }], {
+    ignoreDuplicates: true
+  })
+  .then(function() {
+    console.log('Finished populating fax outbound voice extension');
+  });
+
+models.SquareProject
+  .bulkCreate([{
+    name: "project1",
+    description: "description project"
+  }], {
+    ignoreDuplicates: true
+  }).then(function() {
+    console.log('Finished populating square projects');
+  });
+
+models.Variable
+  .bulkCreate([{
+    name: "variable1",
+    description: "description project"
+  }], {
+    ignoreDuplicates: true
+  }).then(function() {
+    console.log('Finished populating variables');
+  });
+
+models.VoiceContext
+  .bulkCreate([{
+    name: "from-sip",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "from-voip-provider",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "inbound-fax",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "outbound-fax",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }, {
+    name: "from-voicemail",
+    description: "Default context (DO NOT DELETE)",
+    defaultEntry: true
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating context variables');
+  });
+
+models.VoiceMusicOnHold
+  .bulkCreate([{
+    name: "default",
+    mode: "files",
+    directory: 'var/lib/asterisk/moh',
+    sort: 'alpha',
+    format: 'wav',
+    defaultEntry: true
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating moh variables');
+  });
+
+models.Settings
+  .create({
+    min_internal: 1003,
+    soundPath: '/var/opt/motion/server/files/sounds/converted'
+  }).then(function() {
+    console.log('Finished populating settings');
+  });
+
+models.Dashboard
+  .bulkCreate([{
+    id: 1,
+    name: 'Motion Dashboard',
+    model: JSON.stringify({
+      title: 'Motion Dashboard',
+      rows: [{
+        columns: [{
+          styleClass: 'col-md-4',
+          widgets: [{
+            type: 'clock',
+            title: 'Clock',
+            config: {
+              timePattern: 'hh:mm:ss a',
+              datePattern: 'dddd, MMMM Do YYYY'
+            }
+          }]
+        }, {
+          styleClass: 'col-md-8',
+          widgets: [{
+            type: 'iframe',
+            title: 'Iframe',
+            config: {
+              url: 'https://demo.xcally.com/shop/'
+            }
+          }]
+        }]
+      }]
+    }),
+    defaultEntry: true,
+    editable: false
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating moh variables');
+  });
+
+models.Integration
+  .bulkCreate([{
+    name: 'Zendesk',
+    version: '1.0.0',
+    active: false,
+    author: 'XeniaLAB s.r.l.',
+    logo: 'assets/images/media/zendesk2.png',
+    state: 'zendesk',
+    description: 'Description for Zendesk'
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating integrations');
+  });
+
+models.ReportTree
+  .bulkCreate([{
+    id: 1,
+    tree: '[{"id":"1","parent":"#","text":"Motion Reports","icon":"icon-rocket","state":{"selected":true,"opened":true}},{"id":"2","parent":"1","text":"Basic Node","state":{"selected":false,"opened":false}}]',
+  }, {
+    id: 2,
+    tree: '[{"id":"1","text":"Custom Reports","icon":"icon-book-open","li_attr":{"id":"1"},"a_attr":{"href":"#","id":"1_anchor"},"state":{"loaded":true,"opened":true,"selected":false,"disabled":false},"data":{},"parent":"#"},{"id":"2","text":"Basic Node","icon":true,"li_attr":{"id":"2"},"a_attr":{"href":"#","id":"2_anchor"},"state":{"loaded":true,"opened":true,"selected":false,"disabled":false},"data":{},"parent":"1"}]'
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating integrations');
+  });
+
+models.Service
+  .bulkCreate([{
+    id: 1,
+    name: 'Motion Service',
+    defaultEntry: true
+  }], {
+    ignoreDuplicates: true,
+    individualHooks: true
+  }).then(function() {
+    console.log('Finished populating integrations');
+  });
diff --git a/server/config/smtp/index.js b/server/config/smtp/index.js
index a284400..25f595d 100644
--- a/server/config/smtp/index.js
+++ b/server/config/smtp/index.js
@@ -1 +1,21 @@
\ No newline at end of file
+'use strict';
+
+var MailServerOut = require('../../models').MailServerOut;
+var Smtp = require('./smtp');
+
+module.exports = function () {
+  MailServerOut.afterCreate(function (doc) {
+    new Smtp(doc);
+  });
+
+  return MailServerOut
+    .findAll()
+    .then(function (data) {
+      data.forEach(function (elm) {
+        new Smtp(elm);
+      });
+    })
+    .catch(function (err) {
+      console.error(err);
+    });
+}
diff --git a/server/config/smtp/smtp.js b/server/config/smtp/smtp.js
index 32ff8c0..365afdd 100644
--- a/server/config/smtp/smtp.js
+++ b/server/config/smtp/smtp.js
@@ -1 +1,240 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Promise = require('bluebird');
+var nodemailer = require('nodemailer');
+var htmlToText = require('html-to-text');
+var smtpTransport = require('nodemailer-smtp-transport');
+
+var Agent = require('../../models').User;
+var Contact = require('../../models').Contact;
+var MailRoom = require('../../models').MailRoom;
+var MailMessage = require('../../models').MailMessage;
+var ContactEmail = require('../../models').ContactEmail;
+var MailServerOut = require('../../models').MailServerOut;
+
+function Smtp(doc) {
+
+  var _smtp;
+  var _doc;
+
+  function onSave(doc) {
+    console.log("MAIL - Account " + doc.username + " SMTP CREATE");
+    _doc = doc;
+    _smtp = nodemailer.createTransport(smtpTransport({
+      host: doc.host,
+      port: doc.port,
+      secure: doc.ssl,
+      debug: true,
+      auth: {
+        user: doc.username,
+        pass: doc.password
+      }
+    }));
+  }
+
+  function onUpdate(doc) {
+    if (!doc.changed('state') && _smtp.transporter.options.auth.user === doc.username) {
+      console.log("MAIL - Account " + doc.username + " SMTP UPDATE");
+      _smtp = null;
+      _smtp = nodemailer.createTransport(smtpTransport({
+        host: doc.host,
+        port: doc.port,
+        secure: doc.ssl,
+        debug: true,
+        auth: {
+          user: doc.username,
+          pass: doc.password
+        }
+      }));
+    }
+  }
+
+  function onRemove(doc) {
+    if (_smtp.transporter.options.auth.user === doc.username) {
+      console.log("[MAIL - Account " + doc.username + " SMTP DESTROY");
+      _smtp = null;
+    }
+  }
+
+  function onSend(mailMessage, options) {
+    if (mailMessage.status === 'SENDING' && _smtp.transporter.options.auth.user === mailMessage.dataValues.from) {
+      var _mRoom;
+      var _mMessage = mailMessage;
+      var _mFrom, _mTo, _mCc, _mBcc;
+
+      if (_mMessage.inReplyTo) {
+        return MailMessage
+          .findOne({
+            where: {
+              messageId: _mMessage.inReplyTo
+            }
+          })
+          .then(function(mailMessageParent) {
+            return mailMessageParent
+              .getMailRoom();
+          })
+          .then(function(mailRoom) {
+            _mRoom = mailRoom;
+            return _mRoom
+              .addMailMessage(_mMessage, {
+                transaction: options ? options.transaction : null
+              });
+          })
+          .then(function() {
+            return _mRoom
+              .addUser(_mMessage.UserId, {
+                transaction: options ? options.transaction : null
+              });
+          })
+          .then(function(mBcc) {
+            _mBcc = _.map(mBcc, function(elm) {
+              return elm[0];
+            });
+
+            return;
+          })
+          .then(function() {
+            var mail = {
+              status: _mMessage.status,
+              from: _mMessage.from,
+              to: _mMessage.to,
+              cc: _mMessage.cc,
+              subject: _mMessage.subject,
+              html: _mMessage.html,
+              text: htmlToText.fromString(_mMessage.html, {
+                wordwrap: false
+              }),
+              headers: {
+                'X-Laziness-level': 1000
+              }
+            };
+
+            if (_mMessage.MailAttachments) {
+              mail.attachments = _.map(_mMessage.MailAttachments, function(elm) {
+                return {
+                  path: elm.path,
+                  filename: elm.name
+                }
+              });
+            }
+
+            return _smtp.sendMail(mail);
+          })
+          .then(function(info) {
+            return _mMessage
+              .update({
+                messageId: info.messageId,
+                status: 'SENT'
+              }, {
+                transaction: options ? options.transaction : null
+              });
+          })
+          .catch(function(err) {
+            console.error('err', err);
+            throw {
+              message: err.message || err.response || 'Internal Server Error'
+            };
+          });
+      } else {
+        console.log(1);
+
+        return MailRoom
+          .findOrCreate({
+            where: {
+              id: _mMessage.MailRoomId
+            },
+            defaults: {
+              subject: _mMessage.subject,
+              from: _mMessage.from.join(';'),
+              status: 'OPEN',
+              MailAccountId: _doc.MailAccountId
+            },
+            transaction: options ? options.transaction : null
+          })
+          .spread(function(mailRoom) {
+            console.log(2);
+
+            _mRoom = mailRoom;
+            return _mRoom
+              .addMailMessage(_mMessage, {
+                transaction: options ? options.transaction : null
+              });
+          })
+          .then(function() {
+            console.log(3);
+
+            return _mRoom
+              .addUser(_mMessage.UserId, {
+                transaction: options ? options.transaction : null
+              });
+          })
+          .then(function() {
+            console.log(4);
+
+            var mail = {
+              status: _mMessage.status,
+              from: _mMessage.from,
+              to: _mMessage.to,
+              cc: _mMessage.cc,
+              subject: _mMessage.subject,
+              html: _mMessage.html,
+              text: htmlToText.fromString(_mMessage.html, {
+                wordwrap: false
+              }),
+              headers: {
+                'X-Laziness-level': 1000
+              }
+            };
+
+            if (_mMessage.MailAttachments) {
+              mail.attachments = _.map(_mMessage.MailAttachments, function(elm) {
+                return {
+                  path: elm.path,
+                  filename: elm.name
+                }
+              });
+            }
+
+            return _smtp
+              .sendMail(mail);
+          })
+          .then(function(info) {
+
+            console.log('info', info);
+            return _mMessage
+              .update({
+                messageId: info.messageId,
+                status: 'SENT'
+              }, {
+                transaction: options ? options.transaction : null
+              });
+          })
+          .catch(function(err) {
+            console.error('err', err);
+            throw {
+              message: err.message || err.response || 'Internal Server Error'
+            };
+          });
+      }
+    }
+  }
+
+  onSave(doc);
+
+  // HANDLE ACCOUNT UPDATE/DELETE
+  MailServerOut.afterUpdate(function(doc) {
+    return onUpdate(doc);
+  });
+  MailServerOut.afterDestroy(function(doc) {
+    return onRemove(doc);
+  });
+  // HANDLE SEND MAIL
+  MailMessage.afterCreate(function(doc, options) {
+    return onSend(doc, options);
+  });
+
+  return {};
+}
+
+module.exports = Smtp;
diff --git a/server/config/socketio.js b/server/config/socketio.js
index 1312ad6..bffebfa 100644
--- a/server/config/socketio.js
+++ b/server/config/socketio.js
@@ -1 +1,201 @@
\ No newline at end of file
+/**
+ * Socket.io configuration
+ */
+
+'use strict';
+
+var moment = require('moment');
+var config = require('./environment');
+var User = require('../models').User;
+
+function onDisconnect(socket) {
+  if (socket.userId) {
+    return User
+      .findById(socket.userId)
+      .then(function(user) {
+        if (user) {
+          return user
+            .updateAttributes({
+              online: false
+            });
+        } else {
+          throw new Error('socket: anyone users found');
+        }
+      })
+      .then(function(user) {
+        console.log('socket: user offline', user.name);
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  } else {
+    console.log('socket: anyone users found');
+  }
+}
+
+function onConnect(socket) {
+  if (socket.userId) {
+    return User
+      .findById(socket.userId)
+      .then(function(user) {
+        if (user) {
+          return user
+            .updateAttributes({
+              online: true,
+              lastLoginAt: moment().format("YYYY-MM-DD HH:mm:ss")
+            })
+        } else {
+          throw new Error('socket: anyone users found');
+        }
+      })
+      .then(function(user) {
+        console.log('socket: user online', user.name);
+
+        // Insert sockets below
+        require('../api/chat_proactive_action/chat_proactive_action.socket').register(socket);
+        // require('../api/report_jscripty_input/report_jscripty_input.socket').register(socket);
+        // require('../api/report_jscripty_question/report_jscripty_question.socket').register(socket);
+        // require('../api/report_jscripty_session/report_jscripty_session.socket').register(socket);
+        require('../api/canned_answer/canned_answer.socket').register(socket);
+        require('../api/chat_websites_field/chat_websites_field.socket').register(socket);
+        require('../api/voice_recording/voice_recording.socket').register(socket);
+        require('../api/license/license.socket').register(socket);
+        require('../api/jscripty_project/jscripty_project.socket').register(socket);
+
+        //Ordered alphabetically
+        require('../api/agent/agent.socket').register(socket);
+        require('../api/ami/ami.socket').register(socket);
+        require('../api/automation/automation.socket').register(socket);
+        require('../api/chat_application/chat_application.socket').register(socket);
+        require('../api/chat_enquiry/enquiry.socket').register(socket);
+        require('../api/chat_message/chat_message.socket').register(socket);
+        require('../api/chat_queue/chat_queue.socket').register(socket);
+        require('../api/chat_room/chat_room.socket').register(socket);
+        require('../api/chat_visitor/chat_visitor.socket').register(socket);
+        require('../api/chat_website/chat_website.socket').register(socket);
+        require('../api/company/company.socket').register(socket);
+        require('../api/contact_manager/contact_manager.socket').register(socket);
+        require('../api/dashboard/dashboard.socket').register(socket);
+        require('../api/desk_account/desk_account.socket').register(socket);
+        require('../api/desk_configuration/desk_configuration.socket').register(socket);
+        require('../api/desk_field/desk_field.socket').register(socket);
+        require('../api/fax_account/fax_account.socket').register(socket);
+        require('../api/fax_application/fax_application.socket').register(socket);
+        require('../api/fax_message/fax_message.socket').register(socket);
+        require('../api/fax_queue/fax_queue.socket').register(socket);
+        require('../api/fax_room/fax_room.socket').register(socket);
+        require('../api/freshdesk_account/freshdesk_account.socket').register(socket);
+        require('../api/freshdesk_configuration/freshdesk_configuration.socket').register(socket);
+        require('../api/freshdesk_field/freshdesk_field.socket').register(socket);
+        require('../api/integration/integration.socket').register(socket);
+        require('../api/interval/interval.socket').register(socket);
+        require('../api/mail_account/mail_account.socket').register(socket);
+        require('../api/mail_application/mail_application.socket').register(socket);
+        require('../api/mail_attachment/mail_attachment.socket').register(socket);
+        require('../api/mail_message/mail_message.socket').register(socket);
+        require('../api/mail_queue/mail_queue.socket').register(socket);
+        require('../api/mail_room/mail_room.socket').register(socket);
+        require('../api/mail_server_in/mail_server_in.socket').register(socket);
+        require('../api/mail_template/mail_template.socket').register(socket);
+        require('../api/module/module.socket').register(socket);
+        require('../api/motionbar/motionbar.socket').register(socket);
+        require('../api/default_report/default_report.socket').register(socket);
+        require('../api/custom_report/custom_report.socket').register(socket);
+        require('../api/report_tree/report_tree.socket').register(socket);
+        require('../api/report_agent/report_agent.socket').register(socket);
+        require('../api/report_chat/report_chat.socket').register(socket);
+        require('../api/report_chat_session/report_chat_session.socket').register(socket);
+        require('../api/report_integration/report_integration.socket').register(socket);
+        require('../api/report_mail/report_mail.socket').register(socket);
+        require('../api/report_mail_session/report_mail_session.socket').register(socket);
+        require('../api/report_queue/report_queue.socket').register(socket);
+        require('../api/salesforce_account/salesforce_account.socket').register(socket);
+        require('../api/salesforce_configuration/salesforce_configuration.socket').register(socket);
+        require('../api/salesforce_field/salesforce_field.socket').register(socket);
+        require('../api/setting/setting.socket').register(socket);
+        require('../api/square_odbc/square_odbc.socket').register(socket);
+        require('../api/square_project/square_project.socket').register(socket);
+        require('../api/sugarcrm_account/sugarcrm_account.socket').register(socket);
+        require('../api/sugarcrm_configuration/sugarcrm_configuration.socket').register(socket);
+        require('../api/sugarcrm_field/sugarcrm_field.socket').register(socket);
+        require('../api/tag/tag.socket').register(socket);
+        require('../api/team/team.socket').register(socket);
+        require('../api/trigger/trigger.socket').register(socket);
+        require('../api/trunk/trunk.socket').register(socket);
+        require('../api/user/user.socket').register(socket);
+        require('../api/sound/sound.socket').register(socket);
+        require('../api/user_has_chat_queue/user_has_chat_queue.socket').register(socket);
+        require('../api/user_has_chat_room/user_has_chat_room.socket').register(socket);
+        require('../api/user_has_fax_queue/user_has_fax_queue.socket').register(socket);
+        require('../api/user_has_mail_queue/user_has_mail_queue.socket').register(socket);
+        require('../api/user_has_team/user_has_team.socket').register(socket);
+        require('../api/user_has_voice_queue/user_has_voice_queue.socket').register(socket);
+        require('../api/user_has_service/user_has_service.socket').register(socket);
+        require('../api/variable/variable.socket').register(socket);
+        require('../api/chanspy/chanspy.socket').register(socket);
+        require('../api/metric/metric.socket').register(socket);
+        require('../api/extracted_report/extracted_report.socket').register(socket);
+        require('../api/voice_context/voice_context.socket').register(socket);
+        require('../api/voice_extension/voice_extension.socket').register(socket);
+        require('../api/voice_musiconhold/voice_musiconhold.socket').register(socket);
+        require('../api/voice_queue/voice_queue.socket').register(socket);
+        require('../api/service/service.socket').register(socket);
+        require('../api/voice_voicemail/voice_voicemail.socket').register(socket);
+        require('../api/voice_voicemail_messages/voice_voicemail_messages.socket').register(socket);
+        require('../api/xchatty/xchatty.socket').register(socket);
+        require('../api/zendesk_account/zendesk_account.socket').register(socket);
+        require('../api/zendesk_configuration/zendesk_configuration.socket').register(socket);
+        require('../api/zendesk_field/zendesk_field.socket').register(socket);
+
+      })
+      .catch(function(err) {
+        console.error(err);
+      });
+  } else {
+    console.log('socket: anyone users found');
+  }
+
+  socket.on('info', function(data) {
+    console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2));
+  });
+
+}
+
+module.exports = function(socketio) {
+  // socket.io (v1.x.x) is powered by debug.
+  // In order to see all the debug output, set DEBUG (in server/config/local.env.js) to including the desired scope.
+  //
+  // ex: DEBUG: "http*,socket.io:socket"
+
+  // We can authenticate socket.io users and access their token through socket.handshake.decoded_token
+  //
+  // 1. You will need to send the token in `client/components/socket/socket.service.js`
+  //
+  // 2. Require authentication here:
+  socketio.use(require('socketio-jwt').authorize({
+    secret: config.session.secret,
+    handshake: true
+  }));
+
+  socketio.on('connection', function(socket) {
+    socket.address = socket.handshake.address !== null ? socket.handshake
+      .address.address + ':' + socket.handshake.address.port : process.env
+      .DOMAIN;
+
+    socket.connectedAt = new Date();
+    socket.userId = socket.handshake.query.userId;
+    socket.role = socket.handshake.query.role;
+    socket.name = socket.handshake.query.name;
+    socket.source = socket.handshake.query.source;
+
+    // Call onDisconnect.
+    socket.on('disconnect', function() {
+      onDisconnect(socket);
+      console.info('[%s] SOCKET DISCONNECTED', socket.handshake.address);
+    });
+
+    // Call onConnect.
+    onConnect(socket);
+    console.info('[%s] SOCKET CONNECTED', socket.handshake.address);
+  });
+};
diff --git a/server/config/tools/chanspy.js b/server/config/tools/chanspy.js
index e1584c2..468c7e4 100644
--- a/server/config/tools/chanspy.js
+++ b/server/config/tools/chanspy.js
@@ -1 +1,114 @@
\ No newline at end of file
+'use strict';
+
+var fs = require('fs');
+var util = require('util');
+var path = require('path');
+var config = require('../../config/environment');
+var ChanSpyModel = require('../../models').ChanSpy;
+var User = require('../../models').User;
+var VoiceExtension = require('../../models').VoiceExtension;
+var sequelize = require('../../models').sequelize;
+
+function ChanSpy() {
+  ChanSpyModel.afterCreate(function(doc) {
+    createSpyExtensions(doc);
+  })
+  ChanSpyModel.afterUpdate(function(doc) {
+    deleteSpyExtensions(doc);
+    createSpyExtensions(doc);
+  })
+  ChanSpyModel.afterDelete(function(doc) {
+    deleteSpyExtensions(doc);
+  })
+}
+
+function deleteSpyExtensions(doc) {
+  return sequelize.transaction(function(t) {
+      return VoiceExtension
+        .destroy({
+          where: {
+            ChanSpyId: doc.id
+          },
+          transaction: t
+        });
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function createSpyExtensions(doc) {
+  return sequelize.transaction(function(t) {
+      return User
+        .scope('agent')
+        .findAll({
+          where: {
+            chanspy: true
+          }
+        })
+        .then(function(agents) {
+          var bulkSpies = [];
+          agents.forEach(function(elem) {
+            var spies = [];
+            var priority = 1;
+            if (doc.auth) {
+              spies.push({
+                context: 'from-sip',
+                exten: util.format('%s%s', doc.prefix, elem.name),
+                priority: priority,
+                app: 'Authenticate',
+                appdata: doc.password,
+                UserId: elem.id,
+                ChanSpyId: doc.id,
+                type: 'system'
+              });
+              priority++;
+            }
+            spies.push({
+              context: 'from-sip',
+              exten: util.format('%s%s', doc.prefix, elem.name),
+              priority: priority,
+              app: 'ChanSpy',
+              appdata: util.format('SIP/%s,%s', elem.name, doc.options),
+              UserId: elem.id,
+              ChanSpyId: doc.id,
+              type: 'system'
+            });
+            priority = 1;
+            if (doc.auth) {
+              spies.push({
+                context: 'from-sip',
+                exten: util.format('%s%s', doc.prefix, elem.internal),
+                priority: priority,
+                app: 'Authenticate',
+                appdata: doc.password,
+                UserId: elem.id,
+                ChanSpyId: doc.id,
+                type: 'system'
+              });
+              priority++;
+            }
+            spies.push({
+              context: 'from-sip',
+              exten: util.format('%s%s', doc.prefix, elem.internal),
+              priority: priority,
+              app: 'ChanSpy',
+              appdata: util.format('SIP/%s,%s', elem.name, doc.options),
+              UserId: elem.id,
+              ChanSpyId: doc.id,
+              type: 'system'
+            });
+            bulkSpies.push(VoiceExtension.bulkCreate(spies, {
+              transaction: t
+            }));
+          });
+          return bulkSpies;
+        })
+        .all();
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+module.exports = ChanSpy;
diff --git a/server/config/tools/chatRoom.js b/server/config/tools/chatRoom.js
index da98f30..a36f4e6 100644
--- a/server/config/tools/chatRoom.js
+++ b/server/config/tools/chatRoom.js
@@ -1 +1,27 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+
+var ChatRoomModel = require('../../models').ChatRoom;
+var ChatVisitorModel = require('../../models').ChatVisitor;
+
+function ChatRoom() {
+  ChatRoomModel.afterUpdate(function(doc) {
+    if (doc.changed('status') && doc.status === 'CLOSED') {
+      if (doc.ChatVisitorId) {
+        return ChatVisitorModel
+          .findById(doc.ChatVisitorId)
+          .then(function(chatVisitor) {
+            if (chatVisitor) {
+              var updated = _.merge(chatVisitor, {
+                status: 'served'
+              });
+              return updated.save();
+            }
+          });
+      }
+    }
+  })
+}
+
+module.exports = ChatRoom;
diff --git a/server/config/tools/index.js b/server/config/tools/index.js
index 9cbc5ab..8e605bb 100644
--- a/server/config/tools/index.js
+++ b/server/config/tools/index.js
@@ -1 +1,14 @@
-var _0xcb2c=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x2E\x2F\x69\x6E\x74\x65\x72\x76\x61\x6C","\x2E\x2F\x75\x73\x65\x72","\x2E\x2F\x63\x68\x61\x6E\x73\x70\x79","\x2E\x2F\x63\x68\x61\x74\x52\x6F\x6F\x6D","\x65\x78\x70\x6F\x72\x74\x73"];_0xcb2c[0];var Interval=require(_0xcb2c[1]);var User=require(_0xcb2c[2]);var ChanSpy=require(_0xcb2c[3]);var ChatRoom=require(_0xcb2c[4]);module[_0xcb2c[5]]=function(){ new Interval(); new User(); new ChanSpy(); new ChatRoom()}
\ No newline at end of file
+'use strict';
+
+var Interval = require('./interval');
+var User = require('./user');
+var ChanSpy = require('./chanspy');
+var ChatRoom = require('./chatRoom');
+
+module.exports = function() {
+  // Tools Mangament
+  new Interval();
+  new User();
+  new ChanSpy();
+  new ChatRoom();
+}
diff --git a/server/config/tools/interval.js b/server/config/tools/interval.js
index 882640c..223b7bf 100644
--- a/server/config/tools/interval.js
+++ b/server/config/tools/interval.js
@@ -1 +1,74 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var util = require('util');
+var IntervalModel = require('../../models').Interval;
+var VoiceExtension = require('../../models').VoiceExtension;
+var veUtil = require('../../utils/voice_extension');
+var sequelize = require('../../models').sequelize;
+var Applications = veUtil.Applications;
+
+function Interval() {
+  console.log('Interval Initialization..');
+  IntervalModel.afterUpdate(function(interval) {
+    updatevoiceExtension(interval);
+  })
+
+  IntervalModel.beforeDestroy(function(interval) {
+    if (interval.IntervalId !== null) {
+      updatevoiceExtension(interval);
+    }
+  })
+
+  IntervalModel.afterCreate(function(interval) {
+    if (interval.IntervalId !== null) {
+      updatevoiceExtension(interval);
+    }
+  })
+}
+
+function updatevoiceExtension(interval) {
+  var IntervalId = interval.IntervalId === null ? interval.id : interval.IntervalId;
+  return VoiceExtension
+    .findAll({
+      where: {
+        VoiceExtensionId: null
+      },
+      include: [{
+        model: VoiceExtension,
+        as: 'Applications',
+        where: {
+          IntervalId: IntervalId
+        },
+        attributes: ['app', 'interval', 'IntervalId', 'callerID', 'appdata', 'type']
+      }]
+    })
+    .then(function(voiceExtensions) {
+      voiceExtensions.forEach(function(elem) {
+        return sequelize.transaction(function(t) {
+            return veUtil.routeUpdate({
+              body: elem.dataValues,
+              params: {
+                id: elem.id
+              }
+            }, t);
+          })
+          .catch(sequelize.UniqueConstraintError, function(err) {
+            if (err.fields.PRIMARY) {
+              err.message = 'Extension/context combination already exist';
+              err.translatedMessage = 'MESSAGE_EXIST_ROUTE'
+            }
+            console.log(err);
+          })
+          .catch(function(err) {
+            console.log(err);
+          });
+      });
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+
+module.exports = Interval;
diff --git a/server/config/tools/user.js b/server/config/tools/user.js
index c45e647..a55d7c8 100644
--- a/server/config/tools/user.js
+++ b/server/config/tools/user.js
@@ -1 +1,193 @@
\ No newline at end of file
+'use strict';
+
+var fs = require('fs');
+var util = require('util');
+var path = require('path');
+var config = require('../../config/environment');
+var UserModel = require('../../models').User;
+var VoiceExtension = require('../../models').VoiceExtension;
+var ChanSpy = require('../../models').ChanSpy;
+var sequelize = require('../../models').sequelize;
+
+function User() {
+  UserModel.afterCreate(function(doc) {
+    createCallerID(doc);
+    createExtension(doc);
+    if (doc.chanspy) {
+      createChanspy(doc);
+    }
+  })
+  UserModel.afterUpdate(function(doc) {
+    updateExtension(doc);
+    createCallerID(doc);
+    if ((doc.chanspy && doc.changed('chanspy'))) {
+      createChanspy(doc);
+    } else if (doc.chanspy && !doc.changed('chanspy') && doc.changed('name')) {
+      deleteChanSpy(doc);
+      createChanspy(doc);
+    } else if (!doc.chanspy && doc.changed('chanspy')) {
+      deleteChanSpy(doc);
+    }
+  })
+}
+
+function createChanspy(doc) {
+  return sequelize.transaction(function(t) {
+      return ChanSpy
+        .findAll()
+        .then(function(chanspy) {
+          var bulkSpies = [];
+          chanspy.forEach(function(elem) {
+            var spies = [];
+            var priority = 1;
+            if (elem.auth) {
+              spies.push({
+                context: 'from-sip',
+                exten: util.format('%s%s', elem.prefix, doc.name),
+                priority: priority,
+                app: 'Authenticate',
+                appdata: elem.password,
+                UserId: doc.id,
+                ChanSpyId: elem.id,
+                type: 'system'
+              });
+              priority++;
+            }
+            spies.push({
+              context: 'from-sip',
+              exten: util.format('%s%s', elem.prefix, doc.name),
+              priority: priority,
+              app: 'ChanSpy',
+              appdata: util.format('SIP/%s,%s', doc.name, elem.options),
+              UserId: doc.id,
+              ChanSpyId: elem.id,
+              type: 'system'
+            });
+            priority = 1;
+            if (elem.auth) {
+              spies.push({
+                context: 'from-sip',
+                exten: util.format('%s%s', elem.prefix, doc.internal),
+                priority: priority,
+                app: 'Authenticate',
+                appdata: elem.password,
+                UserId: doc.id,
+                ChanSpyId: elem.id,
+                type: 'system'
+              });
+              priority++;
+            }
+            spies.push({
+              context: 'from-sip',
+              exten: util.format('%s%s', elem.prefix, doc.internal),
+              priority: priority,
+              app: 'ChanSpy',
+              appdata: util.format('SIP/%s,%s', doc.name, elem.options),
+              UserId: doc.id,
+              ChanSpyId: elem.id,
+              type: 'system'
+            });
+            bulkSpies.push(VoiceExtension.bulkCreate(spies, {
+              transaction: t
+            }));
+          });
+          return bulkSpies;
+        })
+        .all();
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function deleteChanSpy(doc) {
+  return sequelize.transaction(function(t) {
+      return VoiceExtension
+        .destroy({
+          where: {
+            UserId: doc.id,
+            ChanSpyId: {
+              $ne: null
+            }
+          },
+          transaction: t
+        });
+    })
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+function createCallerID(doc) {
+  // devo aspettare che ci siano entrambi i campi
+  if (doc.fullname && doc.internal) {
+    doc.callerid = util.format('"%s" <%s>', doc.fullname, doc.internal);
+    doc.save();
+  }
+
+  return;
+}
+
+function updateExtension(doc) {
+  if (doc.changed('name')) {
+    doc
+      .getUserExtensions()
+      .then(function(userExtensions) {
+        userExtensions.forEach(function(elem) {
+          elem.exten = (elem.exten === doc._previousDataValues.internal) ? doc.internal : doc.name;
+          elem.appdata = 'SIP/' + doc.name;
+          elem.save();
+        })
+      })
+      .catch(function(err) {
+        console.log(err);
+      });
+  }
+}
+
+
+function createExtension(doc) {
+  if (doc.internal) {
+    var userDials = [{
+      context: 'from-sip',
+      exten: doc.internal,
+      priority: 1,
+      app: 'Set',
+      appdata: 'CDR(type)=internal',
+      UserId: doc.id,
+      type: 'system'
+    }, {
+      context: 'from-sip',
+      exten: doc.internal,
+      priority: 2,
+      app: 'Dial',
+      appdata: 'SIP/' + doc.name,
+      UserId: doc.id,
+      type: 'system'
+    }, {
+      context: 'from-sip',
+      exten: doc.internal,
+      priority: 1,
+      app: 'Set',
+      appdata: 'CDR(type)=internal',
+      UserId: doc.id,
+      type: 'system'
+    }, {
+      context: 'from-sip',
+      exten: doc.name,
+      priority: 2,
+      app: 'Dial',
+      appdata: 'SIP/' + doc.name,
+      UserId: doc.id,
+      type: 'system'
+    }];
+
+    return VoiceExtension
+      .bulkCreate(userDials)
+      .catch(function(err) {
+        console.log(err);
+      });
+  }
+}
+
+module.exports = User;
diff --git a/server/config/triggers/chat.js b/server/config/triggers/chat.js
index 35d27da..72fc1e9 100644
--- a/server/config/triggers/chat.js
+++ b/server/config/triggers/chat.js
@@ -1 +1,76 @@
\ No newline at end of file
+var _ = require('lodash');
+var Mustache = require('mustache');
+
+var sequelize = require('../../models').sequelize;
+var ChatEnquiry = require('../../models').ChatEnquiry;
+var ChatWebsite = require('../../models').ChatWebsite;
+var MailMessage = require('../../models').MailMessage;
+var MailServerOut = require('../../models').MailServerOut;
+
+function Chat() {
+  console.log('Trigger Chat Initialization...');
+
+  ChatEnquiry.afterCreate(function(chatEnquiry, options) {
+    var _chatEnquiry = chatEnquiry;
+    var _chatWebsite;
+    var _mailServerOut;
+    var _offlineTemplate;
+
+    return ChatWebsite
+      .findById(chatEnquiry.ChatWebsiteId)
+      .then(function(chatWebsite) {
+        _chatWebsite = chatWebsite;
+        _chatEnquiry.chatWebsite = _chatWebsite.name;
+
+        if (_chatWebsite.enquiry_forwarding && _chatWebsite.enquiry_forwarding_address) {
+          return _chatWebsite
+            .getOfflineTemplate();
+        } else {
+          // throw new Error({
+          //   message: 'chatWebsite can\'t send offline messages via mail'
+          // });
+          return null;
+        }
+      })
+      .then(function(offlineTemplate) {
+        _offlineTemplate = offlineTemplate;
+        if (_offlineTemplate) {
+          _offlineTemplate.html = Mustache.render(_offlineTemplate.html, _chatEnquiry);
+          _offlineTemplate.subject = Mustache.render(_offlineTemplate.subject, _chatEnquiry);
+
+          return MailServerOut
+            .findOne({
+              where: {
+                service: true
+              }
+            });
+
+        } else {
+          // throw new Error({
+          //   message: 'offlineTemplate not found. ChatWebsite: ' + _chatWebsite.name
+          // });
+          return null;
+        }
+
+
+      })
+      .then(function(mailServerOut) {
+        _mailServerOut = mailServerOut;
+
+        if (_mailServerOut) {
+          // throw new Error('Motion Service SMTP not configured!');
+          return MailMessage
+            .create({
+              from: [_mailServerOut.username],
+              to: [_chatWebsite.enquiry_forwarding_address],
+              subject: _offlineTemplate.subject,
+              html: _offlineTemplate.html
+            }, {
+              transaction: options ? options.transaction : null
+            });
+        }
+      });
+  });
+}
+
+module.exports = Chat;
diff --git a/server/config/triggers/cm.js b/server/config/triggers/cm.js
index 5b914e0..ae02e6e 100644
--- a/server/config/triggers/cm.js
+++ b/server/config/triggers/cm.js
@@ -1 +1,25 @@
\ No newline at end of file
+var _ = require('lodash');
+
+function Cm() {
+  console.log('Trigger Cm Initialization...');
+}
+
+Cm.prototype.manageContact = function(doc, channel) {
+  manageContact(doc, channel);
+}
+
+function manageContact(doc, channel) {
+  var Contact = require('../../models').Contact;
+  var ContactPhone = require('../../models').ContactPhone;
+  var ContactEmail = require('../../models').ContactEmail;
+  var ReportIntegration = require('../../models').ReportIntegration;
+  delete doc.id
+  doc.integration = 'contactmanager';
+  doc.eventChannel = channel;
+  ReportIntegration.create(doc)
+    .catch(function(err) {
+      console.log(err);
+    });
+}
+
+module.exports = Cm;
diff --git a/server/config/triggers/index.js b/server/config/triggers/index.js
index ad62857..947b5f0 100644
--- a/server/config/triggers/index.js
+++ b/server/config/triggers/index.js
@@ -1 +1,103 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var Util = require('./util');
+var Voice = require('./voice');
+var Mail = require('./mail');
+var Chat = require('./chat');
+var Cm = require('./cm');
+var Integration = require('../integrations');
+var Trigger = require('../../models').Trigger;
+var ReportAgent = require('../../models').ReportAgent;
+var MailMessage = require('../../models').MailMessage;
+
+module.exports = function(ami) {
+
+  var util = new Util();
+  var voice = new Voice();
+  var chat = new Chat();
+  var mail = new Mail();
+  var cm = new Cm();
+  var integration = new Integration(ami);
+
+  Trigger
+    .scope('all')
+    .findAll()
+    .then(function(triggers) {
+      synchTriggers(triggers);
+      console.log('Triggers are ', triggers.length);
+      ReportAgent.afterCreate(function(doc) {
+        var actions = util.getActionsByTriggers(doc, _.filter(triggers, {
+          channel: 'voice'
+        }));
+        console.log('After agent create, Actions are ', actions.length);
+        execActions(actions, doc, 'voice', util, voice, mail, cm, integration);
+      })
+      ReportAgent.afterUpdate(function(doc) {
+        var actions = util.getActionsByTriggers(doc, _.filter(triggers, {
+          channel: 'voice'
+        }));
+        console.log('After agent update, Actions are ', actions.length);
+        execActions(actions, doc, 'voice', util, voice, mail, cm, integration);
+      })
+      MailMessage.afterCreate(function(doc) {
+        if (doc.status === 'RECEIVED') {
+          var actions = util.getActionsByTriggers(doc, _.filter(triggers, {
+            channel: 'mail'
+          }));
+          execActions(actions, doc, 'mail', util, voice, mail, cm, integration);
+        }
+      })
+    })
+    .catch(function(err) {
+      console.error(err);
+    });
+}
+
+
+function execActions(actions, doc, channel, util, voice, mail, cm, integration) {
+  actions.forEach(function(action) {
+    console.log('Executing action "' + action.action + '"');
+    switch (action.action) {
+      case 'contactManager':
+        cm.manageContact(doc.dataValues, channel);
+        break;
+      case 'template':
+        mail.sendTemplate(action);
+        break;
+      case 'integration':
+        integration.manageEvent(action, doc, channel);
+        break;
+      default:
+    }
+  });
+}
+
+function synchTriggers(triggers) {
+  Trigger.afterCreate(function(doc) {
+    updateCollection(triggers, doc);
+    console.log('After create, Triggers are ', triggers.length);
+  });
+  Trigger.afterUpdate(function(doc) {
+    updateCollection(triggers, doc);
+    console.log('After update, Triggers are ', triggers.length);
+  });
+  Trigger.afterDestroy(function(doc) {
+    _.remove(triggers, {
+      id: doc.id
+    });
+    console.log('After destroy, Triggers are ', triggers.length);
+  });
+}
+
+function updateCollection(collection, doc) {
+  var oldItem = _.find(collection, {
+    id: doc.id
+  });
+  var index = collection.indexOf(oldItem);
+  if (oldItem) {
+    _.merge(collection[index], doc);
+  } else {
+    collection.unshift(doc);
+  }
+}
diff --git a/server/config/triggers/mail.js b/server/config/triggers/mail.js
index 518003b..21afef4 100644
--- a/server/config/triggers/mail.js
+++ b/server/config/triggers/mail.js
@@ -1 +1,49 @@
\ No newline at end of file
+var _ = require('lodash');
+
+function Mail() {
+  console.log('Trigger Mail Initialization...');
+}
+
+Mail.prototype.sendTemplate = function(templateId) {
+  sendTemplate(templateId);
+}
+
+function sendTemplate(action) {
+  var MailTemplate = require('../../models').MailTemplate;
+  var MailMessage = require('../../models').MailMessage;
+  var MailServerOut = require('../../models').MailServerOut;
+  var _smtp;
+  return MailServerOut
+    .findOne({
+      where: {
+        service: true
+      }
+    })
+    .then(function(smtp) {
+      _smtp = smtp;
+      return MailTemplate
+        .findById(Number(action.data1), {
+          include: [{
+            all: true
+          }]
+        });
+    })
+    .then(function(mailTemplate) {
+      // console.log('found template');
+      return MailMessage
+        .create({
+          status: 'SENDING',
+          subject: mailTemplate.subject,
+          from: [_smtp.username],
+          to: action.data3.split(';'),
+          cc: action.data4 ? action.data4.split(';') : [],
+          html: mailTemplate.html || null,
+          text: mailTemplate.text || null
+        })
+        .catch(function(err) {
+          console.log(err);
+        });
+    });
+}
+
+module.exports = Mail;
diff --git a/server/config/triggers/util.js b/server/config/triggers/util.js
index d3fea13..1ad1569 100644
--- a/server/config/triggers/util.js
+++ b/server/config/triggers/util.js
@@ -1 +1,80 @@
\ No newline at end of file
+var _ = require('lodash');
+
+function Util() {
+  console.log('Trigger Utils Initialization...');
+}
+
+Util.prototype.getActionsByTriggers = function(doc, triggers) {
+  var actions = [];
+  doc.dataValues.from = doc.dataValues.from ? doc.dataValues.from.split(';') : [];
+  doc.dataValues.cc = doc.dataValues.cc ? doc.dataValues.cc.split(';') : [];
+  doc.dataValues.to = doc.dataValues.to ? doc.dataValues.to.split(';') : [];
+  triggers.forEach(function(trigger) {
+    if (trigger.status) {
+      var All = [];
+      var Any = [];
+      trigger.All.forEach(function(condition) {
+        All.push(checkCondition(condition.dataValues, doc.dataValues));
+      });
+      trigger.Any.forEach(function(condition) {
+        Any.push(checkCondition(condition.dataValues, doc.dataValues));
+      });
+
+      var allResult = [],
+        anyResult = [];
+      allResult.push(_.reduce(All.length ? All : [true], function(total, n) {
+        return total && n;
+      }));
+      anyResult.push(_.reduce(Any.length ? Any : [true], function(total, n) {
+        return total || n;
+      }));
+      var lastResults = allResult.concat(anyResult);
+      var finalResult = _.reduce(lastResults.length ? lastResults : [true], function(total, n) {
+          return total && n;
+        })
+        // console.log('final result is', finalResult);
+
+      if (finalResult) {
+        //push rules into array to return
+        actions = actions.concat(trigger.Actions);
+      }
+    }
+  });
+  doc.dataValues.from = doc.dataValues.from.join(';');
+  doc.dataValues.cc = doc.dataValues.cc.join(';');
+  doc.dataValues.to = doc.dataValues.to.join(';');
+  //return actions array
+  return actions;
+}
+
+function checkCondition(condition, doc) {
+  // console.log('CONDITION', condition);
+  // console.log('DOC FIELD', doc[condition.field]);
+  // console.log('trigger value ', trigger[rule.field.value]);
+  // console.log('rule data ', rule.data);
+  var result;
+  switch (condition.operator) {
+    case 'equals':
+      result = (doc[condition.field] === condition.value);
+      break;
+    case 'not_equals':
+      result = (doc[condition.field] !== condition.value);
+      break;
+    case 'greater_or_equal':
+      result = (doc[condition.field] >= Number(condition.value));
+      break;
+    case 'less_or_equal':
+      result = (doc[condition.field] <= Number(condition.value));
+      break;
+    case 'greater':
+      result = (doc[condition.field] > Number(condition.value));
+      break;
+    case 'less':
+      result = (doc[condition.field] < Number(condition.value));
+      break;
+    default:
+  }
+  return result;
+}
+
+module.exports = Util;
diff --git a/server/config/triggers/voice.js b/server/config/triggers/voice.js
index 1238936..2465c50 100644
--- a/server/config/triggers/voice.js
+++ b/server/config/triggers/voice.js
@@ -1 +1,7 @@
-var _0x249e=["\x6C\x6F\x64\x61\x73\x68","\x54\x72\x69\x67\x67\x65\x72\x20\x56\x6F\x69\x63\x65\x20\x49\x6E\x69\x74\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x2E\x2E\x2E","\x6C\x6F\x67","\x65\x78\x70\x6F\x72\x74\x73"];var _=require(_0x249e[0]);function Voice(){console[_0x249e[2]](_0x249e[1])}module[_0x249e[3]]=Voice
\ No newline at end of file
+var _ = require('lodash');
+
+function Voice() {
+  console.log('Trigger Voice Initialization...');
+}
+
+module.exports = Voice;
diff --git a/server/config/util/error.js b/server/config/util/error.js
index 21dcdcf..e265f76 100644
--- a/server/config/util/error.js
+++ b/server/config/util/error.js
@@ -1 +1,24 @@
\ No newline at end of file
+'use strict';
+
+var util = require('util');
+
+module.exports = {
+  handleUniqueConstraintError: function(res, err) {
+    var paths = [];
+
+    err.errors.forEach(function(error) {
+      switch (error.path) {
+        default: paths.push(error.path);
+      }
+    });
+
+    paths.push(' must be unique');
+    err.message = paths.join();
+
+    return res.status(500).send(err);
+  },
+  handleForeignKeyConstraintError: function(res, app, name, err) {
+    err.message = util.format('Impossible to remove %s %s because of an existing association', app, name);
+    return res.status(500).send(err);
+  }
+};
diff --git a/server/config/util/index.js b/server/config/util/index.js
index 53542fc..fe69138 100644
--- a/server/config/util/index.js
+++ b/server/config/util/index.js
@@ -1 +1,151 @@
\ No newline at end of file
+'use strict';
+
+var _ = require('lodash');
+var moment = require('moment');
+
+var checkInterval = function(interval) {
+
+  var daysOfWeek = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
+  var daysOfMonth = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
+  var months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
+  var i, shift;
+
+  var tRange = interval.split(',')[0];
+  var dwRange = interval.split(',')[1];
+  var dmRange = interval.split(',')[2];
+  var mRange = interval.split(',')[3];
+  // TIME RANGE CHECKING
+  if (tRange !== '*') {
+    var time = moment().format("HH:mm");
+    var tFrom = tRange.split('-')[0];
+    var tTo = tRange.split('-')[1];
+
+    if ((time < tFrom) || (time > tTo)) {
+      console.log('TRANGE KO');
+      return false;
+    }
+  }
+  // DAYS OF WEEK RANGE CHECKING
+  if (dwRange !== '*') {
+    var cWeekDay = daysOfWeek[moment().day()];
+    var dwFrom = _.indexOf(daysOfWeek, dwRange.split('-')[0]);
+    var dwTo = _.indexOf(daysOfWeek, dwRange.split('-')[1]);
+
+    for (i = 0; i < dwFrom; i++) {
+      shift = daysOfWeek.shift();
+      daysOfWeek.push(shift);
+    }
+
+    dwFrom = _.indexOf(daysOfWeek, dwRange.split('-')[0]);
+    dwTo = _.indexOf(daysOfWeek, dwRange.split('-')[1]) + 1;
+
+    daysOfWeek = daysOfWeek.slice(dwFrom, dwTo);
+
+    if (!_.includes(daysOfWeek, cWeekDay)) {
+      console.log('DWRANGE KO');
+      return false;
+    }
+  }
+  // DAYS OF MONTH RANGE CHECKING
+  if (dmRange !== '*') {
+    var cMonthDay = moment().date();
+    var dmFrom = _.indexOf(daysOfMonth, parseInt(dmRange.split('-')[0], 10));
+    var dmTo = _.indexOf(daysOfMonth, parseInt(dmRange.split('-')[1]));
+
+    for (i = 0; i < dmFrom; i++) {
+      shift = daysOfMonth.shift();
+      daysOfMonth.push(shift);
+    }
+
+    dmFrom = _.indexOf(daysOfMonth, parseInt(dmRange.split('-')[0], 10));
+    dmTo = _.indexOf(daysOfMonth, parseInt(dmRange.split('-')[1]), 10) + 1;
+
+    daysOfMonth = daysOfMonth.slice(dmFrom, dmTo);
+
+    if (!_.includes(daysOfMonth, cMonthDay)) {
+      console.log('DMRANGE KO');
+      return false;
+    }
+  }
+  // MONTHS RANGE CHECKING
+  if (mRange !== '*') {
+    var cMonth = months[moment().month()];
+    var mFrom = _.indexOf(months, mRange.split('-')[0]);
+    var mTo = _.indexOf(months, mRange.split('-')[1]);
+
+    for (i = 0; i < mFrom; i++) {
+      shift = months.shift();
+      months.push(shift);
+    }
+
+    mFrom = _.indexOf(months, mRange.split('-')[0]);
+    mTo = _.indexOf(months, mRange.split('-')[1]) + 1;
+
+    months = months.slice(mFrom, mTo);
+
+    if (!_.includes(months, cMonth)) {
+      console.log('MRANGE KO');
+      return false;
+    }
+  }
+
+  console.log('INTERVAL OK');
+
+  return true;
+}
+
+var isValidInterval = function(interval) {
+  if (_.isObject(interval)) {
+    for (var i = 0; i < interval.SubIntervals.length; i++) {
+      if (!checkInterval(interval.SubIntervals[i].interval)) {
+        return false;
+      }
+    }
+    return true;
+  } else {
+    return checkInterval(interval);
+  }
+}
+
+var getQuery = function(query) {
+  var _query = {
+    where: {}
+  };
+  if (query) {
+    _.forEach(query, function(value, key) {
+      switch (key) {
+        case 'offset':
+        case 'limit':
+          _query[key] = Number(value);
+          break;
+        case 'order':
+          _query[key] = value;
+          break;
+        case 'type':
+        case 'parent':
+          _query.where[key] = value;
+          break;
+        default:
+          _query.where[key] = {
+            $like: '%' + value + '%'
+          };
+      }
+    });
+  }
+
+  return _query;
+}
+
+var stripTrailingSlash = function(str) {
+  if (str.substr(-1) === '/') {
+    return str.substr(0, str.length - 1);
+  }
+  return str;
+}
+
+module.exports = {
+  getQuery: getQuery,
+  stripTrailingSlash: stripTrailingSlash,
+  isValidInterval: isValidInterval,
+  errorManager: require('./error')
+};
diff --git a/server/migrations/20160315183016-motion.js b/server/migrations/20160315183016-motion.js
index eef35c4..4410805 100644
--- a/server/migrations/20160315183016-motion.js
+++ b/server/migrations/20160315183016-motion.js
@@ -1 +1,33 @@
-var _0x5575=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x65\x78\x70\x6F\x72\x74\x73","\x75\x73\x65\x72\x73","\x74\x65\x73\x74\x41\x74\x74\x72","\x53\x54\x52\x49\x4E\x47","\x61\x64\x64\x43\x6F\x6C\x75\x6D\x6E","\x72\x65\x6D\x6F\x76\x65\x43\x6F\x6C\x75\x6D\x6E"];_0x5575[0];module[_0x5575[1]]={up:function(_0xe993x1,_0xe993x2){return _0xe993x1[_0x5575[5]](_0x5575[2],_0x5575[3],{type:_0xe993x2[_0x5575[4]]})},down:function(_0xe993x1,_0xe993x2){return _0xe993x1[_0x5575[6]](_0x5575[2],_0x5575[3])}}
\ No newline at end of file
+'use strict';
+
+module.exports = {
+  up: function(queryInterface, Sequelize) {
+    /*
+      Add altering commands here.
+      Return a promise to correctly handle asynchronicity.
+
+      Example:
+      return queryInterface.createTable('users', { id: Sequelize.INTEGER });
+    */
+    return queryInterface
+      .addColumn(
+        'users',
+        'testAttr', {
+          type: Sequelize.STRING
+        }
+      );
+  },
+
+  down: function(queryInterface, Sequelize) {
+    /*
+      Add reverting commands here.
+      Return a promise to correctly handle asynchronicity.
+
+      Example:
+      return queryInterface.dropTable('users');
+    */
+
+    return queryInterface
+      .removeColumn('users', 'testAttr');
+  }
+};
diff --git a/server/models/action.js b/server/models/action.js
index 04e4886..3e22553 100644
--- a/server/models/action.js
+++ b/server/models/action.js
@@ -1 +1,32 @@
\ No newline at end of file
+/**
+ * Action Model
+ */
+
+module.exports = function (sequelize, DataTypes) {
+
+	var Action = sequelize.define('Action', {
+		name: {
+			type: DataTypes.STRING
+		},
+		data1: {
+			type: DataTypes.STRING
+		},
+		data2: {
+			type: DataTypes.STRING
+		},
+		data3: {
+			type: DataTypes.STRING
+		},
+		data4: {
+			type: DataTypes.STRING
+		},
+		data5: {
+			type: DataTypes.STRING
+		}
+	}, {
+		tableName: 'actions',
+		associate: function (models) {}
+	});
+
+	return Action;
+};
diff --git a/server/models/asterisk_manager.js b/server/models/asterisk_manager.js
index 2b405bc..8cc071d 100644
--- a/server/models/asterisk_manager.js
+++ b/server/models/asterisk_manager.js
@@ -1 +1,19 @@
\ No newline at end of file
+/**
+ * Asterisk Manager Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var AsteriskManager = sequelize.define('AsteriskManager', {
+    address: DataTypes.STRING,
+    port: DataTypes.INTEGER,
+    username: DataTypes.STRING,
+    password: DataTypes.STRING
+  }, {
+    tableName: 'asterisk_manager'
+  });
+
+  return AsteriskManager;
+};
diff --git a/server/models/asterisk_manager_interface.js b/server/models/asterisk_manager_interface.js
index 024b12c..539add1 100644
--- a/server/models/asterisk_manager_interface.js
+++ b/server/models/asterisk_manager_interface.js
@@ -1 +1,24 @@
\ No newline at end of file
+'use strict';
+
+/**
+ * Asterisk Manager Interface Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Ami = sequelize.define('Ami', {
+    actionid: DataTypes.STRING,
+    action: DataTypes.STRING,
+    response: DataTypes.STRING,
+    message: DataTypes.STRING,
+    data: DataTypes.TEXT
+  }, {
+    tableName: 'asterisk_manager_interface',
+    associate: function(models) {
+      // USER RELATIONS
+      Ami.belongsTo(models.User);
+    }
+  });
+
+  return Ami;
+};
diff --git a/server/models/autodialer_contact.js b/server/models/autodialer_contact.js
index bf14a25..0719b90 100644
--- a/server/models/autodialer_contact.js
+++ b/server/models/autodialer_contact.js
@@ -1 +1,15 @@
-var _0x413e=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x65\x78\x70\x6F\x72\x74\x73","\x41\x75\x74\x6F\x64\x69\x61\x6C\x65\x72\x43\x6F\x6E\x74\x61\x63\x74","\x53\x54\x52\x49\x4E\x47","\x61\x75\x74\x6F\x64\x69\x61\x6C\x65\x72\x5F\x63\x6F\x6E\x74\x61\x63\x74\x73","\x64\x65\x66\x69\x6E\x65"];_0x413e[0];module[_0x413e[1]]=function(_0xe496x1,_0xe496x2){var _0xe496x3=_0xe496x1[_0x413e[5]](_0x413e[2],{status:{type:_0xe496x2[_0x413e[3]]}},{tableName:_0x413e[4]});return _0xe496x3}
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var AutodialerContact = sequelize.define('AutodialerContact', {
+    status: {
+      type: DataTypes.STRING
+    }
+  }, {
+    tableName: 'autodialer_contacts'
+  });
+
+  return AutodialerContact;
+
+};
diff --git a/server/models/autodialer_process.js b/server/models/autodialer_process.js
index f914548..7ddc225 100644
--- a/server/models/autodialer_process.js
+++ b/server/models/autodialer_process.js
@@ -1 +1,53 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var AutodialerProcess = sequelize.define('AutodialerProcess', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    description: {
+      type: DataTypes.STRING,
+    },
+    context: {
+        type: DataTypes.STRING,
+    },
+    extension: {
+        type: DataTypes.STRING,
+    },
+    priority: {
+        type: DataTypes.INTEGER,
+    },
+    callerid: {
+        type: DataTypes.STRING,
+    },
+    waittime: {
+        type: DataTypes.INTEGER,
+    },
+    maxretry: {
+        type: DataTypes.INTEGER,
+    },
+    retrytime: {
+        type: DataTypes.INTEGER,
+    },
+    prefix: {
+        type: DataTypes.STRING,
+    },
+    maxcall: {
+        type: DataTypes.INTEGER,
+    },
+    enable: {
+    type: DataTypes.BOOLEAN,
+    defaultValue: true
+    }
+  }, {
+    tableName: 'autodialer_processes',
+    associate: function(models) {
+        AutodialerProcess.belongsToMany(models.Contact, { through: models.AutodialerContact })
+    }
+  });
+
+  return AutodialerProcess;
+
+};
diff --git a/server/models/automation.js b/server/models/automation.js
index 24f6b12..7ee4308 100644
--- a/server/models/automation.js
+++ b/server/models/automation.js
@@ -1 +1,64 @@
\ No newline at end of file
+/**
+ * Automation Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Automation = sequelize.define('Automation', {
+    name: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    description: DataTypes.STRING,
+    status: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    timeout: {
+      type: DataTypes.INTEGER,
+      defaultValue: 10
+    }
+  }, {
+    tableName: 'automations',
+    associate: function(models) {
+      Automation.hasMany(models.ToolsCondition, {
+        as: {
+          plural: 'All',
+          singular: 'OneAll'
+        },
+        foreignKey: 'AutomationAllId',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      Automation.hasMany(models.ToolsCondition, {
+        as: {
+          plural: 'Any',
+          singular: 'OneAny'
+        },
+        foreignKey: 'AutomationAnyId',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      Automation.hasMany(models.ToolsAction, {
+        as: 'Actions',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      Automation.addScope('all', {
+        include: [{
+          model: models.ToolsCondition,
+          as: 'All',
+          attributes: ['field', 'operator', 'value']
+        }, {
+          model: models.ToolsCondition,
+          as: 'Any',
+          attributes: ['field', 'operator', 'value']
+        }, {
+          model: models.ToolsAction,
+          as: 'Actions',
+          attributes: ['action', 'data1', 'data2', 'data3', 'data4', 'data5']
+        }]
+      });
+    }
+  });
+
+  return Automation;
+};
diff --git a/server/models/canned_answer.js b/server/models/canned_answer.js
index 7c7ba1e..e4dd86f 100644
--- a/server/models/canned_answer.js
+++ b/server/models/canned_answer.js
@@ -1 +1,11 @@
-var _0xda8b=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x65\x78\x70\x6F\x72\x74\x73","\x43\x61\x6E\x6E\x65\x64\x41\x6E\x73\x77\x65\x72","\x53\x54\x52\x49\x4E\x47","\x54\x45\x58\x54","\x63\x61\x6E\x6E\x65\x64\x5F\x61\x6E\x73\x77\x65\x72\x73","\x64\x65\x66\x69\x6E\x65"];_0xda8b[0];module[_0xda8b[1]]=function(_0x789dx1,_0x789dx2){return _0x789dx1[_0xda8b[6]](_0xda8b[2],{key:_0x789dx2[_0xda8b[3]],value:_0x789dx2[_0xda8b[4]],description:_0x789dx2[_0xda8b[3]]},{tableName:_0xda8b[5]})}
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('CannedAnswer', {
+    key: DataTypes.STRING,
+    value: DataTypes.TEXT,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'canned_answers'
+  });
+};
diff --git a/server/models/channel.js b/server/models/channel.js
index dc21aaa..e1c6d79 100644
--- a/server/models/channel.js
+++ b/server/models/channel.js
@@ -1 +1,46 @@
\ No newline at end of file
+/**
+ * Channel Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Channel = sequelize.define('Channel', {
+    name: {
+      type: DataTypes.STRING,
+      validate: {
+        isUnique: function(name, next) {
+          Channel
+            .find({
+              where: {
+                name: name
+              }
+            })
+            .done(function(error, channel) {
+              if (error)
+                next(error);
+
+              if (channel)
+                next({
+                  message: 'name already used'
+                });
+
+              next();
+            });
+        }
+      }
+    },
+    title: DataTypes.STRING,
+    link: DataTypes.STRING,
+    icon: DataTypes.STRING,
+    position: DataTypes.STRING
+  }, {
+    tableName: 'channels',
+    associate: function(models) {
+      Channel.belongsToMany(models.User, {
+        through: 'user_has_channels'
+      });
+    }
+  });
+
+  return Channel;
+};
diff --git a/server/models/chanspy.js b/server/models/chanspy.js
index e5f1084..39b47de 100644
--- a/server/models/chanspy.js
+++ b/server/models/chanspy.js
@@ -1 +1,35 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ChanSpy = sequelize.define('ChanSpy', {
+    name: DataTypes.STRING,
+    prefix: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true,
+      },
+    },
+    options: DataTypes.STRING,
+    auth: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    password: DataTypes.STRING,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'chanspy',
+    associate: function(models) {
+      ChanSpy.hasMany(models.VoiceExtension, {
+        as: 'SpiedAgents',
+        onDelete: 'cascade',
+        hooks: true
+      });
+    }
+  });
+
+  return ChanSpy;
+
+};
diff --git a/server/models/chat_application.js b/server/models/chat_application.js
index ccfa29e..e0789ff 100644
--- a/server/models/chat_application.js
+++ b/server/models/chat_application.js
@@ -1 +1,30 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ChatApplication = sequelize.define('ChatApplication', {
+    app: {
+      type: DataTypes.ENUM('queue', 'agent')
+    },
+    priority: {
+      type: DataTypes.INTEGER
+    },
+    timeout: {
+      type: DataTypes.INTEGER
+    },
+    interval: {
+      type: DataTypes.STRING
+    }
+  }, {
+    tableName: 'chat_applications',
+    associate: function(models) {
+      // hasMany relations
+      ChatApplication.belongsTo(models.ChatWebsite);
+      ChatApplication.belongsTo(models.Interval);
+      ChatApplication.belongsTo(models.ChatQueue);
+      ChatApplication.belongsTo(models.User);
+    }
+  });
+
+  return ChatApplication;
+};
diff --git a/server/models/chat_enquiry.js b/server/models/chat_enquiry.js
index 2fb56b5..74dcc2c 100644
--- a/server/models/chat_enquiry.js
+++ b/server/models/chat_enquiry.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ChatEnquiry = sequelize.define('ChatEnquiry', {
+    username: DataTypes.STRING,
+    email: DataTypes.STRING,
+    text: DataTypes.TEXT,
+    read: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    }
+  }, {
+    tableName: 'chat_enquiries',
+    associate: function(models) {
+      ChatEnquiry.belongsTo(models.ChatWebsite);
+      ChatEnquiry.belongsTo(models.ChatVisitor);
+    }
+  });
+
+  return ChatEnquiry;
+};
diff --git a/server/models/chat_message.js b/server/models/chat_message.js
index ddec5bd..4b6dff4 100644
--- a/server/models/chat_message.js
+++ b/server/models/chat_message.js
@@ -1 +1,25 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function (sequelize, DataTypes) {
+
+	var ChatMessage = sequelize.define('ChatMessage', {
+		body: {
+			type: DataTypes.TEXT,
+			notNull: true,
+			notEmpty: true,
+		}
+	}, {
+		tableName: 'chat_messages',
+		associate: function (models) {
+			ChatMessage.belongsTo(models.ChatRoom);
+			ChatMessage.belongsTo(models.ChatVisitor);
+			ChatMessage.belongsTo(models.User);
+		}
+	});
+
+	return ChatMessage;
+};
diff --git a/server/models/chat_proactive_action.js b/server/models/chat_proactive_action.js
index 6951291..9ec80af 100644
--- a/server/models/chat_proactive_action.js
+++ b/server/models/chat_proactive_action.js
@@ -1 +1,18 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('ChatProactiveAction', {
+    name: DataTypes.STRING,
+    type: {
+      type: DataTypes.ENUM('mouseOver', 'timeout'),
+      defaultValue: 'mouseOver'
+    },
+    selector: DataTypes.STRING,
+    timeout: DataTypes.INTEGER
+  }, {
+    tableName: 'chat_proactive_actions',
+    associate: function(models) {
+      models.ChatProactiveAction.belongsTo(models.ChatWebsite);
+    }
+  });
+};
diff --git a/server/models/chat_queue.js b/server/models/chat_queue.js
index 9f83013..cac04e9 100644
--- a/server/models/chat_queue.js
+++ b/server/models/chat_queue.js
@@ -1 +1,42 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ChatQueue = sequelize.define('ChatQueue', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true,
+      }
+    },
+    description: {
+      type: DataTypes.STRING,
+    },
+    timeout: {
+      type: DataTypes.INTEGER,
+    },
+    strategy: {
+      type: DataTypes.ENUM('rrmemory', 'beepall')
+    }
+  }, {
+    tableName: 'chat_queues',
+    associate: function(models) {
+      // hasMany relations
+      ChatQueue.belongsToMany(models.User, {
+        through: models.UserHasChatQueue
+      });
+
+      //SCOPES
+      ChatQueue.addScope('default', {
+        include: [{
+          model: models.User,
+          attributes: ['id', 'name', 'fullname', 'email', 'online', 'lastLoginAt', 'chatPause', 'pauseType']
+        }]
+      });
+    }
+  });
+
+  return ChatQueue;
+};
diff --git a/server/models/chat_room.js b/server/models/chat_room.js
index ad5820d..6487c71 100644
--- a/server/models/chat_room.js
+++ b/server/models/chat_room.js
@@ -1 +1,38 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var ChatRoom = sequelize.define('ChatRoom', {
+		type: DataTypes.ENUM('internal', 'external'),
+		token: {
+			type: DataTypes.STRING,
+			unique: 'token'
+		},
+		status: {
+			type: DataTypes.ENUM('OPEN', 'CLOSED'),
+			defaultValue: 'OPEN'
+		},
+		rating: DataTypes.ENUM('good', 'bad'),
+		ratingMessage: DataTypes.TEXT,
+		ChatVisitorId: {
+			type: DataTypes.INTEGER,
+			unique: 'token'
+		}
+	}, {
+		tableName: 'chat_rooms',
+		associate: function(models) {
+			ChatRoom.hasMany(models.ChatMessage);
+			ChatRoom.belongsTo(models.ChatVisitor);
+			ChatRoom.belongsTo(models.ChatWebsite);
+			ChatRoom.belongsToMany(models.User, {
+				through: models.UserHasChatRoom
+			});
+		}
+	});
+
+	return ChatRoom;
+};
diff --git a/server/models/chat_visitor.js b/server/models/chat_visitor.js
index e0250a5..9874a13 100644
--- a/server/models/chat_visitor.js
+++ b/server/models/chat_visitor.js
@@ -1 +1,55 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var ChatVisitor = sequelize.define('ChatVisitor', {
+		fullname: DataTypes.STRING,
+		email: DataTypes.STRING,
+		remote_address: {
+			type: DataTypes.STRING
+		},
+		user_agent: DataTypes.STRING,
+		visitor_language: DataTypes.STRING,
+		referer: DataTypes.STRING,
+		browser: DataTypes.STRING,
+		engine: DataTypes.STRING,
+		os: DataTypes.STRING,
+		device: DataTypes.STRING,
+		country: DataTypes.STRING,
+		region: DataTypes.STRING,
+		city: DataTypes.STRING,
+		latitude: DataTypes.STRING,
+		longitude: DataTypes.STRING,
+		origin: DataTypes.ENUM('webchat', 'enquiry'),
+		status: {
+			type: DataTypes.ENUM('pending', 'accepted', 'unserved', 'served', 'blacklist', 'abandon'),
+			defaultValue: 'pending'
+		},
+		data1: DataTypes.STRING,
+		data2: DataTypes.STRING,
+		data3: DataTypes.STRING,
+		offlineData1: DataTypes.STRING,
+		offlineData2: DataTypes.STRING,
+		offlineData3: DataTypes.STRING,
+		labelData1: DataTypes.STRING,
+		labelData2: DataTypes.STRING,
+		labelData3: DataTypes.STRING,
+		labelOfflineData1: DataTypes.STRING,
+		labelOfflineData2: DataTypes.STRING,
+		labelOfflineData3: DataTypes.STRING
+	}, {
+		tableName: 'chat_visitors',
+		associate: function(models) {
+			ChatVisitor.hasOne(models.ChatEnquiry);
+			ChatVisitor.hasMany(models.ChatMessage);
+			ChatVisitor.hasMany(models.ChatRoom);
+			ChatVisitor.belongsTo(models.ChatWebsite);
+		}
+	});
+
+	return ChatVisitor;
+};
diff --git a/server/models/chat_website.js b/server/models/chat_website.js
index 65535c1..27d5757 100644
--- a/server/models/chat_website.js
+++ b/server/models/chat_website.js
@@ -1 +1,126 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var ChatWebsite = sequelize.define('ChatWebsite', {
+		name: DataTypes.STRING,
+		address: {
+			type: DataTypes.STRING,
+			unique: true
+		},
+		description: DataTypes.STRING,
+		color: {
+			type: DataTypes.STRING,
+			defaultValue: '#000080'
+		},
+		color_focus: {
+			type: DataTypes.STRING,
+			defaultValue: '#000080'
+		},
+		color_button: {
+			type: DataTypes.STRING,
+			defaultValue: '#000080'
+		},
+		remote: DataTypes.STRING,
+		animation: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: true
+		},
+		header_shape: {
+			type: DataTypes.ENUM('rounded', 'squared'),
+			defaultValue: 'rounded'
+		},
+		header_online: {
+			type: DataTypes.STRING,
+			defaultValue: 'We are here!'
+		},
+		online_message: {
+			type: DataTypes.STRING,
+			defaultValue: '<strong>Questions?<\/strong><br \/>Insert your name and email address to start a live-chat with our support team.'
+		},
+		username_placeholder: {
+			type: DataTypes.STRING,
+			defaultValue: 'Your name'
+		},
+		email_placeholder: {
+			type: DataTypes.STRING,
+			defaultValue: 'Your e-mail address'
+		},
+		start_chat_button: {
+			type: DataTypes.STRING,
+			defaultValue: 'Chat'
+		},
+		header_offline: {
+			type: DataTypes.STRING,
+			defaultValue: 'Contact us'
+		},
+		offline_message: {
+			type: DataTypes.STRING,
+			defaultValue: '<strong>We\'re not online.<\/strong><br \/>It doesn\'t mean we\'re not there to help - leave your message below and we\'ll be in touch as soon as possible.'
+		},
+		enquiry_message_placeholder: {
+			type: DataTypes.STRING,
+			defaultValue: 'Your message...'
+		},
+		enquiry_button: {
+			type: DataTypes.STRING,
+			defaultValue: 'Leave message'
+		},
+		download_transcript: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: true
+		},
+		enquiry_forwarding: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		enquiry_forwarding_address: {
+			type: DataTypes.STRING
+		},
+		enquiry_enable: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: true
+		},
+		rating_enable: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: true
+		},
+		rating_message: {
+			type: DataTypes.STRING,
+			defaultValue: 'Would you like rate this chat?'
+		},
+		rating_send: {
+			type: DataTypes.STRING,
+			defaultValue: 'Send'
+		},
+		rating_skip: {
+			type: DataTypes.STRING,
+			defaultValue: 'Skip'
+		},
+	}, {
+		tableName: 'chat_websites',
+		associate: function(models) {
+			// hasMany relations
+			ChatWebsite.belongsTo(models.MailTemplate, {
+				as: 'OfflineTemplate'
+			});
+			ChatWebsite.hasMany(models.ChatRoom);
+			ChatWebsite.hasMany(models.ChatApplication);
+			ChatWebsite.hasMany(models.ChatWebsitesField, {
+				as: 'Online',
+				foreignKey: 'OnlineId'
+			});
+			ChatWebsite.hasMany(models.ChatWebsitesField, {
+				as: 'Offline',
+				foreignKey: 'OfflineId'
+			});
+			ChatWebsite.hasMany(models.ChatProactiveAction);
+		}
+	});
+
+	return ChatWebsite;
+};
diff --git a/server/models/chat_websites_field.js b/server/models/chat_websites_field.js
index b57be48..77383f9 100644
--- a/server/models/chat_websites_field.js
+++ b/server/models/chat_websites_field.js
@@ -1 +1,44 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('ChatWebsitesField', {
+    component: DataTypes.STRING,
+    description: DataTypes.STRING,
+    index: DataTypes.INTEGER,
+    label: DataTypes.STRING,
+    options: {
+      type: DataTypes.STRING,
+      get: function() {
+        return this.getDataValue('options').split(',');
+      },
+      set: function(val) {
+        this.setDataValue('options', val.join(','));
+      }
+    },
+    placeholder: DataTypes.STRING,
+    required: DataTypes.BOOLEAN,
+    validation: DataTypes.STRING,
+    editable: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    }
+  }, {
+    tableName: 'chat_websites_fields',
+    associate: function(models) {
+      models.ChatWebsitesField.addScope('online', function(WebsiteId) {
+        return {
+          where: {
+            OnlineId: WebsiteId
+          }
+        }
+      });
+      models.ChatWebsitesField.addScope('offline', function(WebsiteId) {
+        return {
+          where: {
+            OfflineId: WebsiteId
+          }
+        }
+      });
+    }
+  });
+};
diff --git a/server/models/company.js b/server/models/company.js
index b47af13..12768bd 100644
--- a/server/models/company.js
+++ b/server/models/company.js
@@ -1 +1,30 @@
\ No newline at end of file
+/**
+ * tag Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Company = sequelize.define('Company', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    street: DataTypes.STRING,
+    postalCode: DataTypes.STRING,
+    city: DataTypes.STRING,
+    country: DataTypes.STRING,
+    sStreet: DataTypes.STRING,
+    sPostalCode: DataTypes.STRING,
+    sCity: DataTypes.STRING,
+    sCountry: DataTypes.STRING,
+  }, {
+    tableName: 'companies',
+    associate: function(models) {
+      Company.hasMany(models.Contact, {
+        as: 'Contacts'
+      });
+    }
+  });
+
+  return Company;
+};
diff --git a/server/models/contact.js b/server/models/contact.js
index e446295..9fd0abd 100644
--- a/server/models/contact.js
+++ b/server/models/contact.js
@@ -1 +1,47 @@
\ No newline at end of file
+/**
+ * Contact Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Contact = sequelize.define('Contact', {
+    fullname: {
+      type: DataTypes.STRING,
+      validate: {
+        notEmpty: true
+      }
+    },
+    tags: DataTypes.STRING,
+    // ServiceId: DataTypes.INTEGER,/Da creare con l' associazione
+    street: DataTypes.STRING,
+    postalCode: DataTypes.STRING,
+    city: DataTypes.STRING,
+    country: DataTypes.STRING,
+    dateOfBirth: DataTypes.STRING,
+    description: DataTypes.STRING,
+  }, {
+    tableName: 'contacts',
+    associate: function(models) {
+      Contact.belongsToMany(models.ContactPhone, {
+        through: 'contact_has_phones',
+        as: 'Phones'
+      });
+      Contact.belongsToMany(models.ContactEmail, {
+        through: 'contact_has_emails',
+        as: 'Emails'
+      });
+      Contact.belongsToMany(models.CustomField, {
+        through: models.ContactHasCustomField,
+        as: 'CustomFields'
+      });
+      Contact.belongsTo(models.Company);
+      Contact.belongsTo(models.User);
+      Contact.belongsTo(models.Service);
+      Contact.addScope('service', {
+        include: [models.Service]
+      });
+    }
+  });
+
+  return Contact;
+};
diff --git a/server/models/contact_email.js b/server/models/contact_email.js
index bccd893..597fbde 100644
--- a/server/models/contact_email.js
+++ b/server/models/contact_email.js
@@ -1 +1,22 @@
\ No newline at end of file
+/**
+ * Contact Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ContactEmail = sequelize.define('ContactEmail', {
+    email: {
+      type: DataTypes.STRING,
+      primaryKey: true
+    }
+  }, {
+    tableName: 'contact_emails',
+    associate: function(models) {
+      ContactEmail.belongsToMany(models.Contact, {
+        through: 'contact_has_emails'
+      });
+    }
+  });
+
+  return ContactEmail;
+};
diff --git a/server/models/contact_has_custom_field.js b/server/models/contact_has_custom_field.js
index 0ec9b29..650b530 100644
--- a/server/models/contact_has_custom_field.js
+++ b/server/models/contact_has_custom_field.js
@@ -1 +1,16 @@
\ No newline at end of file
+/**
+ * contact has customfield Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var ContactHasCustomField = sequelize.define('ContactHasCustomField', {
+		value: DataTypes.STRING
+	}, {
+		tableName: 'contact_has_custom_fields'
+	});
+
+	return ContactHasCustomField;
+};
diff --git a/server/models/contact_phone.js b/server/models/contact_phone.js
index a26acef..08d704f 100644
--- a/server/models/contact_phone.js
+++ b/server/models/contact_phone.js
@@ -1 +1,32 @@
\ No newline at end of file
+/**
+ * Contact Model
+ */
+
+module.exports = function (sequelize, DataTypes) {
+
+  var ContactPhone = sequelize.define('ContactPhone', {
+    phone: {
+      type: DataTypes.STRING,
+      primaryKey: true
+    }
+  }, {
+    tableName: 'contact_phones',
+    associate: function (models) {
+      ContactPhone.belongsToMany(models.Contact, {
+        through: 'contact_has_phones'
+      });
+      ContactPhone.hasMany(models.ReportCall, {
+        as: 'Inbounds',
+        foreignKey: 'source',
+        constraints: false
+      });
+      ContactPhone.hasMany(models.ReportCall, {
+        as: 'Outbounds',
+        foreignKey: 'destination',
+        constraints: false
+      });
+    }
+  });
+
+  return ContactPhone;
+};
diff --git a/server/models/custom_field.js b/server/models/custom_field.js
index e78d9d2..54f5ec6 100644
--- a/server/models/custom_field.js
+++ b/server/models/custom_field.js
@@ -1 +1,28 @@
\ No newline at end of file
+/**
+ * customfield Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var CustomField = sequelize.define('CustomField', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    type: DataTypes.STRING,
+    active: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    values: DataTypes.STRING
+  }, {
+    tableName: 'custom_fields',
+    associate: function(models) {
+      CustomField.belongsToMany(models.Contact, {
+        through: models.ContactHasCustomField
+      });
+    }
+  });
+
+  return CustomField;
+};
diff --git a/server/models/custom_report.js b/server/models/custom_report.js
index 28c8a51..fe9d484 100644
--- a/server/models/custom_report.js
+++ b/server/models/custom_report.js
@@ -1 +1,32 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var CustomReport = sequelize.define('CustomReport', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    parent: DataTypes.STRING,
+    table: DataTypes.STRING,
+    conditions: DataTypes.TEXT
+  }, {
+    paranoid: true,
+    tableName: 'custom_reports',
+    associate: function(models) {
+      CustomReport.hasMany(models.ReportField, {
+        as: 'Fields',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      CustomReport.addScope('fields', {
+        include: [{
+          model: models.ReportField,
+          as: 'Fields',
+          required: false,
+          attributes: ['field', 'alias', 'function', 'groupBy', 'orderBy', 'MetricId']
+        }]
+      });
+    }
+  });
+
+  return CustomReport;
+};
diff --git a/server/models/dashboard.js b/server/models/dashboard.js
index bd3926a..b47660b 100644
--- a/server/models/dashboard.js
+++ b/server/models/dashboard.js
@@ -1 +1,61 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Dashboard = sequelize.define('Dashboard', {
+    name: {
+      type: DataTypes.STRING,
+    },
+    description: {
+      type: DataTypes.STRING,
+    },
+    model: {
+      type: DataTypes.TEXT,
+    },
+    collapsible: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    editable: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    maximizable: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    structure: {
+      type: DataTypes.STRING(32),
+      defaultValue: '4-8'
+    },
+    enableconfirmdelete: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    defaultEntry: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: 0
+    },
+    admin: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    user: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    agent: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    visible: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    }
+  }, {
+    tableName: 'dashboards'
+  });
+
+  return Dashboard;
+
+};
diff --git a/server/models/default_report.js b/server/models/default_report.js
index 9feb61d..16cdd08 100644
--- a/server/models/default_report.js
+++ b/server/models/default_report.js
@@ -1 +1,23 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var DefaultReport = sequelize.define('DefaultReport', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    parent: DataTypes.STRING,
+    table: DataTypes.STRING,
+    conditions: DataTypes.TEXT
+  }, {
+    tableName: 'default_reports',
+    associate: function(models) {
+      DefaultReport.hasMany(models.ReportField, {
+        as: 'Fields',
+        onDelete: 'cascade',
+        hooks: true
+      });
+    }
+  });
+
+  return DefaultReport;
+};
diff --git a/server/models/desk_account.js b/server/models/desk_account.js
index 731022d..12615eb 100644
--- a/server/models/desk_account.js
+++ b/server/models/desk_account.js
@@ -1 +1,45 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var DeskAccount = sequelize.define('DeskAccount', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    username: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    remoteUri: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    authType: {
+      type: DataTypes.ENUM('basic', 'oauth'),
+      defaultValue: 'basic'
+    },
+    password: DataTypes.STRING,
+    consumerKey: DataTypes.STRING,
+    consumerSecret: DataTypes.STRING,
+    token: DataTypes.STRING,
+    tokenSecret: DataTypes.STRING
+  }, {
+    tableName: 'desk_accounts',
+    associate: function(models) {
+      DeskAccount.hasMany(models.DeskConfiguration, {
+        as: 'Configurations',
+        foreignKey: 'AccountId'
+      });
+      DeskAccount.addScope('configurations', {
+        include: [{
+          model: models.DeskConfiguration,
+          as: 'Configurations',
+          required: false,
+          attributes: ['id', 'name']
+        }]
+      });
+    }
+  });
+
+  return DeskAccount;
+
+};
diff --git a/server/models/desk_configuration.js b/server/models/desk_configuration.js
index b96f432..fb92e7e 100644
--- a/server/models/desk_configuration.js
+++ b/server/models/desk_configuration.js
@@ -1 +1,27 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('DeskConfiguration', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'desk_configurations',
+    associate: function(models) {
+      models.DeskConfiguration.belongsTo(models.DeskAccount, {
+        foreignKey: 'AccountId'
+      });
+      models.DeskConfiguration.hasMany(models.DeskField, {
+        as: 'Subject',
+        foreignKey: 'SubjectId'
+      });
+      models.DeskConfiguration.hasMany(models.DeskField, {
+        as: 'Description',
+        foreignKey: 'DescriptionId'
+      });
+      models.DeskConfiguration.hasMany(models.DeskField, {
+        as: 'Field',
+        foreignKey: 'FieldId'
+      });
+    }
+  });
+};
diff --git a/server/models/desk_field.js b/server/models/desk_field.js
index 842124a..befea49 100644
--- a/server/models/desk_field.js
+++ b/server/models/desk_field.js
@@ -1 +1,26 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('DeskField', {
+    type: {
+      type: DataTypes.ENUM('string', 'variable', 'key_value'),
+      defaultValue: 'string'
+    },
+    content: DataTypes.STRING,
+    key: DataTypes.STRING,
+    keyType: {
+      type: DataTypes.ENUM('string', 'variable')
+    },
+    keyContent: DataTypes.STRING,
+    idField: DataTypes.STRING,
+    customField: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    }
+  }, {
+    tableName: 'desk_fields',
+    associate: function(models) {
+      models.DeskField.belongsTo(models.Variable);
+    }
+  });
+};
diff --git a/server/models/event.js b/server/models/event.js
index 9214260..fba2c1c 100644
--- a/server/models/event.js
+++ b/server/models/event.js
@@ -1 +1,45 @@
\ No newline at end of file
+/**
+ * Event Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+	var Event = sequelize.define('Event', {
+		name: {
+			type: DataTypes.ENUM('INCOMING', 'OUTGOING', 'ATTEMPT', 'ASSIGNED', 'UNASSIGNED')
+		},
+		channel: {
+			type: DataTypes.ENUM('MAIL', 'CHAT', 'FAX', 'VOICE'),
+		},
+		timeout: {
+			type: DataTypes.INTEGER
+		},
+		holdtime: {
+			type: DataTypes.INTEGER
+		}
+	}, {
+		tableName: 'events',
+		associate: function(models) {
+			// USER RELATIONS
+			Event.belongsTo(models.User);
+			// MAIL RELATIONS
+			Event.belongsTo(models.MailRoom);
+			Event.belongsTo(models.MailMessage);
+			Event.belongsTo(models.MailQueue);
+			Event.belongsTo(models.MailAccount);
+			Event.belongsTo(models.MailApplication);
+			// FAX RELATIONS
+			Event.belongsTo(models.FaxRoom);
+			Event.belongsTo(models.FaxQueue);
+			Event.belongsTo(models.FaxAccount);
+			Event.belongsTo(models.FaxApplication);
+			// CHAT RELATIONS
+			Event.belongsTo(models.ChatRoom);
+			Event.belongsTo(models.ChatQueue);
+			Event.belongsTo(models.ChatWebsite);
+			Event.belongsTo(models.ChatApplication);
+		}
+	});
+
+	return Event;
+};
diff --git a/server/models/extracted_report.js b/server/models/extracted_report.js
index 62a221e..984f130 100644
--- a/server/models/extracted_report.js
+++ b/server/models/extracted_report.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * tag Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ExtractedReport = sequelize.define('ExtractedReport', {
+    name: DataTypes.STRING,
+    output: DataTypes.STRING, //csv,pdf,..
+    savename: DataTypes.STRING,
+    startDate: DataTypes.DATE,
+    endDate: DataTypes.DATE,
+    status: {
+      type: DataTypes.STRING,
+      defaultValue: 'Loading'
+    }
+  }, {
+    tableName: 'extracted_reports',
+    // associate: function(models) {
+    //   Tag.belongsToMany(models.Contact, {
+    //     through: 'contact_tags'
+    //   });
+    // }
+  });
+
+  return ExtractedReport;
+};
diff --git a/server/models/fax_account.js b/server/models/fax_account.js
index f8466ab..71a9227 100644
--- a/server/models/fax_account.js
+++ b/server/models/fax_account.js
@@ -1 +1,40 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var FaxAccount = sequelize.define('FaxAccount', {
+		description: DataTypes.STRING,
+		name: DataTypes.STRING,
+		phone: {
+			type: DataTypes.STRING,
+			unique: true
+		},
+		ecm: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		faxheader: DataTypes.STRING,
+		localid: DataTypes.STRING,
+		maxrate: {
+			type: DataTypes.ENUM('9600', '14400', '33600'),
+			defaultValue: '33600'
+		},
+		minrate: {
+			type: DataTypes.ENUM('9600', '14400', '33600'),
+			defaultValue: '9600'
+		}
+	}, {
+		tableName: 'fax_accounts',
+		associate: function(models) {
+			// FaxAccount.belongsTo(models.Trunk);
+			FaxAccount.hasMany(models.FaxRoom);
+			FaxAccount.hasMany(models.FaxApplication);
+		}
+	});
+
+	return FaxAccount;
+};
diff --git a/server/models/fax_application.js b/server/models/fax_application.js
index c0ebdf1..b4c6d10 100644
--- a/server/models/fax_application.js
+++ b/server/models/fax_application.js
@@ -1 +1,33 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var FaxApplication = sequelize.define('FaxApplication', {
+    app: {
+      type: DataTypes.ENUM('queue', 'agent')
+    },
+    appdata: {
+      type: DataTypes.INTEGER
+    },
+    priority: {
+      type: DataTypes.INTEGER
+    },
+    timeout: {
+      type: DataTypes.INTEGER
+    },
+    interval: {
+      type: DataTypes.STRING,
+      defaultValue: '*|*|*|*'
+    }
+  }, {
+    tableName: 'fax_applications',
+    associate: function(models) {
+      // hasMany relations
+      FaxApplication.belongsTo(models.FaxAccount);
+      FaxApplication.belongsTo(models.FaxQueue);
+      FaxApplication.belongsTo(models.User);
+    }
+  });
+
+  return FaxApplication;
+};
diff --git a/server/models/fax_message.js b/server/models/fax_message.js
index 86490b3..78700ac 100644
--- a/server/models/fax_message.js
+++ b/server/models/fax_message.js
@@ -1 +1,63 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var FaxMessage = sequelize.define('FaxMessage', {
+		actionid: DataTypes.STRING,
+		channel: DataTypes.STRING,
+		channelstate: DataTypes.INTEGER,
+		channelstatedesc: DataTypes.STRING,
+		calleridnum: DataTypes.STRING,
+		calleridname: DataTypes.STRING,
+		connectedlinenum: DataTypes.STRING,
+		connectedlinename: DataTypes.STRING,
+		status: {
+			type: DataTypes.ENUM('RECEIVED', 'NOT SENT', 'SENDING', 'SENT',
+				'FAILED'),
+			allowNull: true
+		},
+		read: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		favorite: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		language: DataTypes.STRING,
+		accountcode: DataTypes.STRING,
+		context: DataTypes.STRING,
+		exten: DataTypes.STRING,
+		priority: DataTypes.INTEGER,
+		uniqueid: DataTypes.STRING,
+		localstationid: DataTypes.STRING,
+		remotestationid: DataTypes.STRING,
+		pagestransferred: DataTypes.INTEGER,
+		resolution: DataTypes.STRING,
+		transferrate: DataTypes.STRING,
+		filename: DataTypes.STRING,
+		filenamePDF: DataTypes.STRING,
+		operation: DataTypes.STRING,
+		uuid: DataTypes.STRING,
+		laststatus: DataTypes.STRING,
+		error: DataTypes.STRING,
+		body: DataTypes.TEXT
+	}, {
+		tableName: 'fax_messages',
+		associate: function(models) {
+			FaxMessage.belongsTo(models.FaxRoom);
+			FaxMessage.belongsTo(models.Contact, {
+				as: 'From'
+			});
+			FaxMessage.belongsTo(models.Contact, {
+				as: 'To'
+			});
+		}
+	});
+
+	return FaxMessage;
+};
diff --git a/server/models/fax_queue.js b/server/models/fax_queue.js
index f958c8f..035bad7 100644
--- a/server/models/fax_queue.js
+++ b/server/models/fax_queue.js
@@ -1 +1,36 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var FaxQueue = sequelize.define('FaxQueue', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true,
+      }
+    },
+    description: {
+      type: DataTypes.STRING,
+    },
+    timeout: {
+      type: DataTypes.INTEGER,
+    },
+    strategy: {
+      type: DataTypes.ENUM('rrmemory', 'beepall')
+    }
+  }, {
+    tableName: 'fax_queues',
+    associate: function(models) {
+      // hasMany relations
+      FaxQueue.belongsToMany(models.User, {
+        hooks: true,
+        through: models.UserHasFaxQueue
+      });
+    }
+  });
+
+  return FaxQueue;
+
+};
diff --git a/server/models/fax_room.js b/server/models/fax_room.js
index 5d8701e..112966e 100644
--- a/server/models/fax_room.js
+++ b/server/models/fax_room.js
@@ -1 +1,25 @@
\ No newline at end of file
+/**
+ * Fax Room Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var FaxRoom = sequelize.define('FaxRoom', {
+    roomId: DataTypes.STRING,
+    // subject: DataTypes.STRING,
+    from: DataTypes.STRING,
+    status: DataTypes.ENUM('NEW', 'OPEN', 'PENDING', 'SOLVED', 'CLOSED')
+  }, {
+    tableName: 'fax_rooms',
+    associate: function(models) {
+      // hasMany relations
+      FaxRoom.hasMany(models.FaxMessage);
+      FaxRoom.belongsTo(models.FaxAccount);
+      FaxRoom.belongsTo(models.User);
+    }
+  });
+
+  return FaxRoom;
+};
diff --git a/server/models/freshdesk_account.js b/server/models/freshdesk_account.js
index dec30e3..f9f9f2b 100644
--- a/server/models/freshdesk_account.js
+++ b/server/models/freshdesk_account.js
@@ -1 +1,33 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('FreshdeskAccount', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    username: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    remoteUri: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    apiKey: DataTypes.STRING
+  }, {
+    tableName: 'freshdesk_accounts',
+    associate: function(models) {
+      models.FreshdeskAccount.hasMany(models.FreshdeskConfiguration, {
+        as: 'Configurations',
+        foreignKey: 'AccountId'
+      });
+      models.FreshdeskAccount.addScope('configurations', {
+        include: [{
+          model: models.FreshdeskConfiguration,
+          as: 'Configurations',
+          required: false,
+          attributes: ['id', 'name']
+        }]
+      });
+    }
+  });
+};
diff --git a/server/models/freshdesk_configuration.js b/server/models/freshdesk_configuration.js
index b4afc2d..4a1299a 100644
--- a/server/models/freshdesk_configuration.js
+++ b/server/models/freshdesk_configuration.js
@@ -1 +1,27 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('FreshdeskConfiguration', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'freshdesk_configurations',
+    associate: function(models) {
+      models.FreshdeskConfiguration.belongsTo(models.FreshdeskAccount, {
+        foreignKey: 'AccountId'
+      });
+      models.FreshdeskConfiguration.hasMany(models.FreshdeskField, {
+        as: 'Subject',
+        foreignKey: 'SubjectId'
+      });
+      models.FreshdeskConfiguration.hasMany(models.FreshdeskField, {
+        as: 'Description',
+        foreignKey: 'DescriptionId'
+      });
+      models.FreshdeskConfiguration.hasMany(models.FreshdeskField, {
+        as: 'Field',
+        foreignKey: 'FieldId'
+      });
+    }
+  });
+};
diff --git a/server/models/freshdesk_field.js b/server/models/freshdesk_field.js
index 0109513..a1c54c9 100644
--- a/server/models/freshdesk_field.js
+++ b/server/models/freshdesk_field.js
@@ -1 +1,26 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('FreshdeskField', {
+    type: {
+      type: DataTypes.ENUM('string', 'variable', 'key_value'),
+      defaultValue: 'string'
+    },
+    content: DataTypes.STRING,
+    key: DataTypes.STRING,
+    keyType: {
+      type: DataTypes.ENUM('string', 'variable')
+    },
+    keyContent: DataTypes.STRING,
+    idField: DataTypes.STRING,
+    customField: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    }
+  }, {
+    tableName: 'freshdesk_fields',
+    associate: function(models) {
+      models.FreshdeskField.belongsTo(models.Variable);
+    }
+  });
+};
diff --git a/server/models/history/report_agent_history.js b/server/models/history/report_agent_history.js
index c4f0151..5c42540 100644
--- a/server/models/history/report_agent_history.js
+++ b/server/models/history/report_agent_history.js
@@ -1 +1,74 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+  var ReportAgentHistory = sequelize.define('ReportAgentHistory', {
+    uniqueid: DataTypes.STRING,
+    calleridnum: DataTypes.STRING,
+    calleridname: DataTypes.STRING,
+    queue: DataTypes.STRING,
+    interface: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    agentcalledAt: DataTypes.DATE,
+    agentconnectAt: {
+      type: DataTypes.DATE,
+      get: function() {
+        return moment(this.getDataValue('agentconnectAt')).format("MM-DD-YYYY HH:mm");
+      },
+    },
+    holdtime: DataTypes.INTEGER,
+    agentcomplete: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentcompleteAt: DataTypes.DATE,
+    talktime: DataTypes.INTEGER,
+    agentacw: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    acwtime: DataTypes.INTEGER,
+    reason: DataTypes.STRING,
+    agentringnoanswer: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentringnoanswerAt: DataTypes.DATE,
+    agentdump: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentdumpAt: DataTypes.DATE,
+    lastevent: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    channelstate: DataTypes.INTEGER,
+    channelstatedesc: DataTypes.STRING,
+    connectedlinenum: DataTypes.STRING,
+    connectedlinename: DataTypes.STRING,
+    language: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    context: DataTypes.STRING,
+    exten: DataTypes.STRING,
+    priority: DataTypes.STRING,
+    destchannel: DataTypes.STRING,
+    destchannelstate: DataTypes.INTEGER,
+    destchannelstatedesc: DataTypes.STRING,
+    destcalleridnum: DataTypes.STRING,
+    destcalleridname: DataTypes.STRING,
+    destconnectedlinenum: DataTypes.STRING,
+    destconnectedlinename: DataTypes.STRING,
+    destlanguage: DataTypes.STRING,
+    destaccountcode: DataTypes.STRING,
+    destcontext: DataTypes.STRING,
+    destexten: DataTypes.STRING,
+    destpriority: DataTypes.STRING,
+    destuniqueid: DataTypes.STRING
+  }, {
+    tableName: 'report_agent_history'
+  });
+
+  ReportAgentHistory.removeAttribute('id');
+  return ReportAgentHistory;
+};
diff --git a/server/models/history/report_call_history.js b/server/models/history/report_call_history.js
index a25de95..0bda734 100644
--- a/server/models/history/report_call_history.js
+++ b/server/models/history/report_call_history.js
@@ -1 +1,60 @@
\ No newline at end of file
+/**
+ * CDR Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportCallHistory = sequelize.define('ReportCallHistory', {
+    uniqueid: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    type: {
+      type: DataTypes.ENUM,
+      values: ['inbound', 'internal', 'outbound']
+    },
+    tag: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    source: DataTypes.STRING,
+    destination: DataTypes.STRING,
+    destinationcontext: DataTypes.STRING,
+    callerid: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    destinationchannel: DataTypes.STRING,
+    lastapplication: DataTypes.STRING,
+    lastdata: DataTypes.STRING,
+    starttime: DataTypes.DATE,
+    answertime: {
+      type: DataTypes.DATE,
+      set: function(answertime) {
+        if (answertime !== '') {
+          this.setDataValue('answertime', answertime);
+        } else {
+          this.setDataValue('answertime', null);
+        }
+      }
+    },
+    endtime: DataTypes.DATE,
+    duration: DataTypes.INTEGER,
+    billableseconds: DataTypes.INTEGER,
+    disposition: DataTypes.STRING,
+    amaflags: DataTypes.STRING,
+    userfield: DataTypes.STRING
+  }, {
+    tableName: 'report_call_history',
+    associate: function(models) {
+      ReportCallHistory.hasMany(models.ReportQueueHistory, {
+        as: 'HistoryQueueCalls',
+        foreignKey: 'uniqueid',
+        constraints: false
+      });
+      ReportCallHistory.hasMany(models.ReportAgentHistory, {
+        as: 'HistoryReportAgents',
+        foreignKey: 'uniqueid',
+        constraints: false
+      });
+    }
+  });
+
+  return ReportCallHistory;
+};
diff --git a/server/models/history/report_integration_history.js b/server/models/history/report_integration_history.js
index 62db635..d481e00 100644
--- a/server/models/history/report_integration_history.js
+++ b/server/models/history/report_integration_history.js
@@ -1 +1,86 @@
\ No newline at end of file
+/**
+ * report_integrations Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportIntegrationHistory = sequelize.define('ReportIntegrationHistory', {
+    integration: DataTypes.STRING,
+    eventChannel: DataTypes.STRING,
+    exitStatus: DataTypes.STRING,
+    ticketId: DataTypes.STRING,
+    integrationId: DataTypes.INTEGER,
+    uniqueid: DataTypes.STRING,
+    calleridnum: DataTypes.STRING,
+    calleridname: DataTypes.STRING,
+    queue: DataTypes.STRING,
+    interface: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    agentcalledAt: DataTypes.DATE,
+    agentconnectAt: DataTypes.DATE,
+    holdtime: DataTypes.INTEGER,
+    agentcomplete: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentcompleteAt: DataTypes.DATE,
+    talktime: DataTypes.INTEGER,
+    agentacw: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    acwtime: DataTypes.INTEGER,
+    reason: DataTypes.STRING,
+    agentringnoanswer: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentringnoanswerAt: DataTypes.DATE,
+    agentdump: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentdumpAt: DataTypes.DATE,
+    lastevent: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    channelstate: DataTypes.INTEGER,
+    channelstatedesc: DataTypes.STRING,
+    connectedlinenum: DataTypes.STRING,
+    connectedlinename: DataTypes.STRING,
+    language: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    context: DataTypes.STRING,
+    exten: DataTypes.STRING,
+    priority: DataTypes.STRING,
+    destchannel: DataTypes.STRING,
+    destchannelstate: DataTypes.INTEGER,
+    destchannelstatedesc: DataTypes.STRING,
+    destcalleridnum: DataTypes.STRING,
+    destcalleridname: DataTypes.STRING,
+    destconnectedlinenum: DataTypes.STRING,
+    destconnectedlinename: DataTypes.STRING,
+    destlanguage: DataTypes.STRING,
+    destaccountcode: DataTypes.STRING,
+    destcontext: DataTypes.STRING,
+    destexten: DataTypes.STRING,
+    destpriority: DataTypes.STRING,
+    destuniqueid: DataTypes.STRING,
+    messageId: DataTypes.STRING,
+    inReplyTo: DataTypes.STRING,
+    subject: DataTypes.STRING,
+    from: DataTypes.STRING,
+    to: DataTypes.STRING,
+    cc: DataTypes.STRING,
+    attachment: DataTypes.TEXT,
+    html: DataTypes.TEXT,
+    text: DataTypes.TEXT,
+    status: {
+      type: DataTypes.ENUM('SENT', 'SENDING', 'RECEIVED', 'FAILED')
+    }
+  }, {
+    tableName: 'report_integration_history'
+  });
+
+  ReportIntegrationHistory.removeAttribute('id');
+  return ReportIntegrationHistory;
+};
diff --git a/server/models/history/report_mail_session_history.js b/server/models/history/report_mail_session_history.js
index 6ae5093..e25f7ae 100644
--- a/server/models/history/report_mail_session_history.js
+++ b/server/models/history/report_mail_session_history.js
@@ -1 +1,38 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportMailSession = sequelize.define('ReportMailSessionHistory', {
+    uniqueid: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    subject: DataTypes.STRING,
+    accountid: DataTypes.STRING,
+    accountname: DataTypes.STRING,
+    accountaddress: DataTypes.STRING,
+    application: DataTypes.STRING,
+    memberid: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    queue: DataTypes.INTEGER,
+    queuename: DataTypes.STRING,
+    roomid: DataTypes.INTEGER,
+    fidelity: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0
+    },
+    holdtime: DataTypes.INTEGER,
+    queuecallerjoinAt: DataTypes.DATE,
+    queuecallerleaveAt: DataTypes.DATE,
+    queuecallerunmanagedAt: DataTypes.DATE,
+    queuecallerabandonedAt: DataTypes.DATE
+  }, {
+    tableName: 'report_mail_session_history'
+  });
+
+  return ReportMailSession;
+};
diff --git a/server/models/history/report_member_history.js b/server/models/history/report_member_history.js
index 8e0e5c9..b19ec9b 100644
--- a/server/models/history/report_member_history.js
+++ b/server/models/history/report_member_history.js
@@ -1 +1,32 @@
\ No newline at end of file
+/**
+ * DIAL Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportMemberHistory = sequelize.define('ReportMemberHistory', {
+    channel: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    interface: DataTypes.STRING,
+    type: {
+      type: DataTypes.STRING,
+      defaultValue: 'DEFAULT PAUSE'
+    },
+    duration: DataTypes.INTEGER,
+    enterAt: DataTypes.DATE,
+    exitAt: DataTypes.DATE,
+    data1: DataTypes.STRING,
+    data2: DataTypes.STRING,
+    data3: DataTypes.STRING,
+    data4: DataTypes.STRING,
+    data5: DataTypes.STRING
+  }, {
+    tableName: 'report_member_history',
+    // indexes: [{
+    //   fields: ['membername', 'enterdate', 'exitdate']
+    // }]
+  });
+
+  ReportMemberHistory.removeAttribute('id');
+  return ReportMemberHistory;
+};
diff --git a/server/models/history/report_queue_history.js b/server/models/history/report_queue_history.js
index 9bbd158..8b1c216 100644
--- a/server/models/history/report_queue_history.js
+++ b/server/models/history/report_queue_history.js
@@ -1 +1,49 @@
\ No newline at end of file
+/**
+ * QueueLog Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportQueueHistory = sequelize.define('ReportQueueHistory', {
+    uniqueid: DataTypes.STRING,
+    calleridnum: DataTypes.STRING,
+    calleridname: DataTypes.STRING,
+    queue: DataTypes.STRING,
+    queuecallerjoinAt: DataTypes.DATE,
+    queuecallerleaveAt: DataTypes.DATE,
+    position: DataTypes.INTEGER,
+    count: DataTypes.INTEGER,
+    queuecallerabandon: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    queuecallerabandonAt: DataTypes.DATE,
+    queuecallercomplete: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    queuecallercompleteAt: DataTypes.DATE,
+    queuecallerexit: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    queuecallerexitAt: DataTypes.DATE,
+    queuecallerexitreason: DataTypes.STRING,
+    originalposition: DataTypes.INTEGER,
+    channel: DataTypes.STRING,
+    channelstate: DataTypes.INTEGER,
+    channelstatedesc: DataTypes.STRING,
+    connectedlinenum: DataTypes.STRING,
+    connectedlinename: DataTypes.STRING,
+    language: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    context: DataTypes.STRING,
+    exten: DataTypes.STRING,
+    priority: DataTypes.STRING,
+  }, {
+    tableName: 'report_queue_history'
+  });
+
+  ReportQueueHistory.removeAttribute('id');
+  return ReportQueueHistory;
+};
diff --git a/server/models/index.js b/server/models/index.js
index bc3899f..00e56f7 100644
--- a/server/models/index.js
+++ b/server/models/index.js
@@ -1 +1,86 @@
\ No newline at end of file
+"use strict";
+
+var fs = require("fs");
+var path = require("path");
+var config = require('../config/environment');
+var Sequelize = require("sequelize");
+var moment = require('moment');
+
+//Used to disable all warnings for Promise!
+Sequelize.Promise.config({
+  // Enables all warnings except forgotten return statements.
+  warnings: {
+    wForgottenReturn: false
+  }
+});
+
+var sequelize = new Sequelize(config.db.database, config.db.username, config.db.password, {
+  host: config.db.host,
+  dialect: 'mysql',
+  timezone: moment().format("Z"),
+  logging: false
+});
+
+var db = {};
+
+fs
+  .readdirSync(__dirname)
+  .filter(function(file) {
+    return (fs.lstatSync(path.join(__dirname, file)).isFile()) &&
+      (file.indexOf(".") !== 0) && (file.indexOf('.spec.') === -1) && (file !== "index.js");
+  })
+  .forEach(function(file) {
+    var model = sequelize["import"](path.join(__dirname, file));
+    db[model.name] = model;
+  });
+
+// Invoke associations on each of the models
+Object.keys(db).forEach(function(modelName) {
+  if (db[modelName].options.hasOwnProperty('associate')) {
+    db[modelName].options.associate(db);
+  }
+});
+
+db.history = {};
+
+var sequelize_history = new Sequelize(config.db_history.database, config.db_history.username, config.db_history.password, {
+  host: config.db_history.host,
+  dialect: 'mysql',
+  timezone: moment().format("Z"),
+  logging: false
+});
+
+fs
+  .readdirSync(path.join(__dirname, 'history'))
+  .filter(function(file) {
+    return (fs.lstatSync(path.join(__dirname, 'history', file)).isFile()) &&
+      (file.indexOf(".") !== 0) && (file.indexOf('.spec.') === -1) && (file !== "index.js");
+  })
+  .forEach(function(file) {
+    var model = sequelize_history["import"](path.join(__dirname, 'history', file));
+    db.history[model.name] = model;
+  });
+
+// Invoke associations on each of the models
+Object.keys(db.history).forEach(function(modelName) {
+  if (db.history[modelName].options.hasOwnProperty('associate')) {
+    db.history[modelName].options.associate(db.history);
+  }
+});
+
+db.ContactPhone.hasMany(db.history.ReportCallHistory, {
+  as: 'HistoryInbounds',
+  foreignKey: 'source',
+  constraints: false
+});
+db.ContactPhone.hasMany(db.history.ReportCallHistory, {
+  as: 'HistoryOutbounds',
+  foreignKey: 'destination',
+  constraints: false
+});
+
+db.sequelize = sequelize;
+db.sequelize_history = sequelize_history;
+db.Sequelize = Sequelize;
+
+module.exports = db;
diff --git a/server/models/integration.js b/server/models/integration.js
index 6135aad..1cbee6f 100644
--- a/server/models/integration.js
+++ b/server/models/integration.js
@@ -1 +1,65 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Integration = sequelize.define('Integration', {
+    name: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: 'unique_name_and_version',
+      validate: {
+        notEmpty: true
+      }
+    },
+    version: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: 'unique_name_and_version',
+      validate: {
+        notEmpty: true
+      }
+    },
+    main: {
+      type: DataTypes.STRING,
+      defaultValue: 'app.js'
+    },
+    filename: {
+      type: DataTypes.STRING,
+      // allowNull: false,
+      // validate: {
+      //   notEmpty: true
+      // }
+    },
+    path: {
+      type: DataTypes.STRING,
+      // allowNull: false,
+      // validate: {
+      //   notEmpty: true
+      // }
+    },
+    type: {
+      type: DataTypes.STRING,
+      // allowNull: false
+    },
+    size: {
+      type: DataTypes.INTEGER,
+      // allowNull: false
+    },
+    active: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    author: DataTypes.STRING,
+    logo: DataTypes.STRING,
+    state: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    description: DataTypes.STRING
+  }, {
+    tableName: 'integrations'
+  });
+
+  return Integration;
+
+};
diff --git a/server/models/interval.js b/server/models/interval.js
index 99d72dd..19e66f4 100644
--- a/server/models/interval.js
+++ b/server/models/interval.js
@@ -1 +1,53 @@
\ No newline at end of file
+/* jshint indent: 2 */
+
+module.exports = function(sequelize, DataTypes) {
+  var Interval = sequelize.define('Interval', {
+    name: {
+      type: DataTypes.STRING
+    },
+    description: DataTypes.STRING,
+    interval: {
+      type: DataTypes.STRING,
+      allowNull: true
+    }
+  }, {
+    tableName: 'intervals',
+    associate: function(models) {
+      Interval.hasMany(models.Interval, {
+        as: 'SubIntervals',
+        onDelete: 'cascade',
+        hooks: true,
+        required: false
+      });
+      Interval.hasMany(models.VoiceExtension, {
+        as: 'VoiceExtensions',
+        foreignKey: 'IntervalId',
+        onDelete: 'restrict'
+      });
+      Interval.hasMany(models.MailApplication, {
+        as: 'MailApplications'
+      });
+      Interval.hasMany(models.MailApplication, {
+        as: 'ChatApplications'
+      });
+      // SCOPES MANAGEMENT
+      Interval.addScope('subintervals', {
+        include: [{
+          model: models.Interval,
+          as: 'SubIntervals',
+          required: false,
+          attributes: ['name', 'interval']
+        }]
+      });
+
+      Interval.addScope('intervals', {
+        where: {
+          IntervalId: null
+        }
+      });
+    }
+  });
+
+
+  return Interval;
+};
diff --git a/server/models/jscripty_project.js b/server/models/jscripty_project.js
index 95a906c..c31f2e2 100644
--- a/server/models/jscripty_project.js
+++ b/server/models/jscripty_project.js
@@ -1 +1,30 @@
\ No newline at end of file
+/**
+ * Fax Room Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var JscriptyProject = sequelize.define('JscriptyProject', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    description: DataTypes.STRING,
+    draft: DataTypes.TEXT,
+    production: DataTypes.TEXT
+  }, {
+    tableName: 'jscripty_projects',
+    associate: function(models) {
+      JscriptyProject.hasMany(models.ReportJscriptySession, {
+        // as: 'sessions',
+        foreignKey: 'projectId'
+      });
+      JscriptyProject.hasMany(models.ReportJscriptyQuestion, {
+        // as: 'questions',
+        foreignKey: 'projectId'
+      });
+    }
+  });
+
+  return JscriptyProject;
+};
diff --git a/server/models/mail_account.js b/server/models/mail_account.js
index 1f3071b..84f4aa6 100644
--- a/server/models/mail_account.js
+++ b/server/models/mail_account.js
@@ -1 +1,66 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+	var MailAccount = sequelize.define('MailAccount', {
+		description: DataTypes.STRING,
+		name: {
+			type: DataTypes.STRING
+		},
+		address: {
+			type: DataTypes.STRING,
+			unique: true
+		},
+		fidelity: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		timeout: {
+			type: DataTypes.INTEGER,
+			defaultValue: 0
+		}
+	}, {
+		tableName: 'mail_accounts',
+		associate: function(models) {
+			// RELATIONS
+			MailAccount.hasMany(models.MailRoom);
+			MailAccount.hasMany(models.MailApplication);
+			MailAccount.hasOne(models.MailServerIn, {
+				onDelete: 'cascade',
+				hooks: true
+			});
+			MailAccount.hasOne(models.MailServerOut, {
+				onDelete: 'cascade',
+				hooks: true
+			});
+
+			// SCOPES
+			MailAccount.addScope('default', {
+				include: [{
+					model: models.MailServerIn,
+					attributes: ['id', 'host', 'port', 'username', 'password', 'ssl', 'delete', 'filter', 'state', 'mailbox']
+				}, {
+					model: models.MailServerOut,
+					attributes: ['id', 'host', 'port', 'username', 'password', 'ssl', 'service', 'state']
+				}, {
+					model: models.MailApplication,
+					include: [{
+						model: models.User,
+						attributes: ['id',
+							'name',
+							'email',
+							'internal',
+							'fullname'
+						]
+					}, {
+						model: models.MailQueue
+					}]
+				}]
+			});
+		}
+	});
+
+	return MailAccount;
+};
diff --git a/server/models/mail_application.js b/server/models/mail_application.js
index b3fabb7..f4a7a03 100644
--- a/server/models/mail_application.js
+++ b/server/models/mail_application.js
@@ -1 +1,36 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var MailApplication = sequelize.define('MailApplication', {
+    app: {
+      type: DataTypes.ENUM('queue', 'agent')
+    },
+    priority: {
+      type: DataTypes.INTEGER
+    },
+    timeout: {
+      type: DataTypes.INTEGER
+    },
+    interval: {
+      type: DataTypes.STRING
+    }
+  }, {
+    tableName: 'mail_applications',
+    associate: function(models) {
+      // hasMany relations
+      MailApplication.belongsTo(models.User, {
+        onDelete: 'restrict'
+      });
+      MailApplication.belongsTo(models.Interval);
+      MailApplication.belongsTo(models.MailQueue, {
+        onDelete: 'restrict'
+      });
+      MailApplication.belongsTo(models.MailAccount, {
+        onDelete: 'restrict'
+      });
+    }
+  });
+
+  return MailApplication;
+};
diff --git a/server/models/mail_attachment.js b/server/models/mail_attachment.js
index 37d66d6..9b2efb5 100644
--- a/server/models/mail_attachment.js
+++ b/server/models/mail_attachment.js
@@ -1 +1,21 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function (sequelize, DataTypes) {
+
+	var MailAttachment = sequelize.define('MailAttachment', {
+		domain: DataTypes.STRING,
+		size: DataTypes.INTEGER,
+		path: DataTypes.STRING,
+		name: DataTypes.STRING,
+		basename: DataTypes.STRING,
+		type: DataTypes.STRING
+	}, {
+		tableName: 'mail_attachments'
+	});
+
+	return MailAttachment;
+};
diff --git a/server/models/mail_automation.js b/server/models/mail_automation.js
index 3994d1a..218cb9c 100644
--- a/server/models/mail_automation.js
+++ b/server/models/mail_automation.js
@@ -1 +1,23 @@
\ No newline at end of file
+/**
+ * User Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var MailAutomation = sequelize.define('MailAutomation', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    conditions: DataTypes.TEXT,
+    actions: DataTypes.TEXT,
+    status: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    }
+  }, {
+    tableName: 'mail_automations'
+  });
+
+  return MailAutomation;
+};
diff --git a/server/models/mail_message.js b/server/models/mail_message.js
index ee50842..50675a7 100644
--- a/server/models/mail_message.js
+++ b/server/models/mail_message.js
@@ -1 +1,88 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var MailMessage = sequelize.define('MailMessage', {
+		messageId: DataTypes.STRING,
+		inReplyTo: DataTypes.STRING,
+		subject: DataTypes.STRING,
+		from: {
+			type: DataTypes.STRING,
+			get: function() {
+				return this.getDataValue('from') ? this.getDataValue('from').split(';') : [];
+			},
+			set: function(val) {
+				this.setDataValue('from', val.join(';'));
+			}
+		},
+		to: {
+			type: DataTypes.STRING,
+			get: function() {
+				return this.getDataValue('to') ? this.getDataValue('to').split(';') : [];
+			},
+			set: function(val) {
+				this.setDataValue('to', val.join(';'));
+			}
+		},
+		cc: {
+			type: DataTypes.STRING,
+			get: function() {
+				return this.getDataValue('cc') ? this.getDataValue('cc').split(';') : [];
+			},
+			set: function(val) {
+				this.setDataValue('cc', val.join(';'));
+			}
+		},
+		bcc: {
+			type: DataTypes.STRING,
+			get: function() {
+				return this.getDataValue('bcc') ? this.getDataValue('bcc').split(';') : [];
+			},
+			set: function(val) {
+				this.setDataValue('bcc', val.join(';'));
+			}
+		},
+		attachment: DataTypes.TEXT,
+		text: {
+			type: DataTypes.BLOB,
+			get: function() {
+				if (this.getDataValue('text')) {
+					return this.getDataValue('text').toString();
+				}
+			}
+		},
+		html: {
+			type: DataTypes.BLOB,
+			get: function() {
+				if (this.getDataValue('html')) {
+					return this.getDataValue('html').toString();
+				}
+			}
+		},
+		reason: DataTypes.TEXT,
+		status: {
+			type: DataTypes.ENUM('SENT', 'SENDING', 'RECEIVED', 'FAILED', 'NOTE'),
+			defaultValue: 'SENDING'
+		}
+	}, {
+		tableName: 'mail_messages',
+		paranoid: true,
+		associate: function(models) {
+			MailMessage.hasMany(models.MailAttachment);
+			MailMessage.belongsTo(models.User);
+			MailMessage.belongsTo(models.MailRoom, {
+				onDelete: 'cascade'
+			});
+			MailMessage.belongsTo(models.ContactEmail, {
+				foreignKey: 'from',
+				constraints: false
+			});
+		}
+	});
+
+	return MailMessage;
+};
diff --git a/server/models/mail_queue.js b/server/models/mail_queue.js
index 8bfb38f..421dc37 100644
--- a/server/models/mail_queue.js
+++ b/server/models/mail_queue.js
@@ -1 +1,37 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var MailQueue = sequelize.define('MailQueue', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    description: {
+      type: DataTypes.STRING,
+    },
+    timeout: {
+      type: DataTypes.INTEGER,
+    },
+    strategy: {
+      type: DataTypes.ENUM('rrmemory', 'beepall')
+    }
+  }, {
+    tableName: 'mail_queues',
+    associate: function(models) {
+      // RELATIONS
+      MailQueue.belongsToMany(models.User, {
+        through: models.UserHasMailQueue
+      });
+      //SCOPES
+      MailQueue.addScope('default', {
+        include: [{
+          model: models.User,
+          attributes: ['id', 'name', 'fullname', 'email', 'online', 'lastLoginAt', 'mailPause', 'pauseType']
+        }]
+      });
+    }
+  });
+
+  return MailQueue;
+};
diff --git a/server/models/mail_room.js b/server/models/mail_room.js
index 1e13234..13d7977 100644
--- a/server/models/mail_room.js
+++ b/server/models/mail_room.js
@@ -1 +1,63 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+
+module.exports = function(sequelize, DataTypes) {
+	var MailRoom = sequelize.define('MailRoom', {
+		subject: DataTypes.STRING,
+		from: DataTypes.STRING,
+		attachment: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		status: {
+			type: DataTypes.ENUM('NEW', 'OPEN', 'CLOSED'),
+			defaultValue: 'NEW'
+		}
+	}, {
+		tableName: 'mail_rooms',
+		paranoid: true,
+		associate: function(models) {
+			// BINDING
+			MailRoom.hasMany(models.MailMessage);
+			MailRoom.belongsTo(models.MailAccount);
+			MailRoom.belongsToMany(models.User, {
+				through: 'user_has_mail_rooms'
+			});
+			// SCOPES
+			MailRoom.addScope('default', {
+				order: [
+					['createdAt', 'DESC']
+				],
+				include: [{
+					model: models.MailAccount
+				}, {
+					model: models.MailMessage,
+					include: [{
+						model: models.MailAttachment
+					}, {
+						model: models.User,
+						attributes: ['id', 'name', 'fullname', 'email']
+					}]
+				}, {
+					model: models.User,
+					attributes: ['id', 'name', 'fullname', 'email']
+				}]
+			});
+
+			MailRoom.addScope('agent', function(id) {
+				return {
+					include: [{
+						model: models.User,
+						where: {
+							id: id
+						}
+					}]
+				}
+			});
+		}
+	});
+
+	return MailRoom;
+};
diff --git a/server/models/mail_room_status.js b/server/models/mail_room_status.js
index c8b73ba..20b29e9 100644
--- a/server/models/mail_room_status.js
+++ b/server/models/mail_room_status.js
@@ -1 +1,14 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+	var MailRoomStatus = sequelize.define('MailRoomStatus', {
+		status: DataTypes.ENUM('RECEIVED', 'ASSIGNED', 'REPLIED', 'ARCHIVED')
+	}, {
+		tableName: 'mail_room_states'
+	});
+
+	return MailRoomStatus;
+};
diff --git a/server/models/mail_server_in.js b/server/models/mail_server_in.js
index 80e74c6..bb7f978 100644
--- a/server/models/mail_server_in.js
+++ b/server/models/mail_server_in.js
@@ -1 +1,44 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var MailServerIn = sequelize.define('MailServerIn', {
+		description: DataTypes.STRING,
+		host: DataTypes.STRING,
+		username: {
+			type: DataTypes.STRING,
+			unique: true
+		},
+		password: DataTypes.STRING,
+		port: DataTypes.INTEGER,
+		mailbox: DataTypes.STRING,
+		ssl: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		delete: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		filter: {
+			type: DataTypes.ENUM('UNSEEN'),
+			defaultValue: 'UNSEEN'
+		},
+		protocol: {
+			type: DataTypes.ENUM('IMAP', 'POP3'),
+			defaultValue: 'IMAP'
+		},
+		state: {
+			type: DataTypes.ENUM('CONNECTED', 'DISCONNECTED', 'ERROR', 'UNKNOWN'),
+			defaultValue: 'UNKNOWN'
+		}
+	}, {
+		tableName: 'mail_servers_in'
+	});
+
+	return MailServerIn;
+};
diff --git a/server/models/mail_server_out.js b/server/models/mail_server_out.js
index b74b0f8..5394b23 100644
--- a/server/models/mail_server_out.js
+++ b/server/models/mail_server_out.js
@@ -1 +1,32 @@
\ No newline at end of file
+/**
+ *
+ */
+module.exports = function(sequelize, DataTypes) {
+
+	var MailServerOut = sequelize.define('MailServerOut', {
+		description: DataTypes.STRING,
+		host: DataTypes.STRING,
+		username: {
+			type: DataTypes.STRING,
+			unique: true
+		},
+		password: DataTypes.STRING,
+		port: DataTypes.INTEGER,
+		ssl: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		service: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		state: {
+			type: DataTypes.ENUM('CONNECTED', 'DISCONNECTED', 'ERROR', 'UNKNOWN'),
+			defaultValue: 'UNKNOWN'
+		},
+	}, {
+		tableName: 'mail_servers_out'
+	});
+
+	return MailServerOut;
+};
diff --git a/server/models/mail_template.js b/server/models/mail_template.js
index 61d7f8e..4856564 100644
--- a/server/models/mail_template.js
+++ b/server/models/mail_template.js
@@ -1 +1,21 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var MailTemplate = sequelize.define('MailTemplate', {
+		name: DataTypes.STRING,
+		description: DataTypes.STRING,
+		subject: DataTypes.STRING,
+		attachment: DataTypes.TEXT,
+		html: DataTypes.TEXT,
+		text: DataTypes.TEXT
+	}, {
+		tableName: 'mail_templates'
+	});
+
+	return MailTemplate;
+};
diff --git a/server/models/mail_trigger.js b/server/models/mail_trigger.js
index f8ccd72..73be985 100644
--- a/server/models/mail_trigger.js
+++ b/server/models/mail_trigger.js
@@ -1 +1,23 @@
\ No newline at end of file
+/**
+ * User Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var MailTrigger = sequelize.define('MailTrigger', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    conditions: DataTypes.TEXT,
+    actions: DataTypes.TEXT,
+    status: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    }
+  }, {
+    tableName: 'mail_triggers'
+  });
+
+  return MailTrigger;
+};
diff --git a/server/models/metric.js b/server/models/metric.js
index 8d3f36f..dfd57d2 100644
--- a/server/models/metric.js
+++ b/server/models/metric.js
@@ -1 +1,37 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Metric = sequelize.define('Metric', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      validate: {
+        notEmpty: true
+      },
+    },
+    table: DataTypes.STRING,
+    metric: DataTypes.TEXT,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'metrics',
+    associate: function(models) {
+      Metric.hasMany(models.ReportField, {
+        as: 'ReportFields'
+      });
+      Metric.addScope('attr', {
+        attributes: ['name', 'table', 'metric']
+      });
+      // Metric.addScope('fields', {
+      //   include: [{
+      //     model: models.ReportField,
+      //     as: 'ReportFields',
+      //     required: false
+      //   }]
+      // });
+    }
+  });
+
+  return Metric;
+
+};
diff --git a/server/models/module.js b/server/models/module.js
index ea86145..c2bef6c 100644
--- a/server/models/module.js
+++ b/server/models/module.js
@@ -1 +1,27 @@
\ No newline at end of file
+/**
+ * Module Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Module = sequelize.define('Module', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true
+      }
+    },
+    title: DataTypes.STRING
+  }, {
+    tableName: 'modules',
+    associate: function(models) {
+      Module.belongsToMany(models.User, {
+        through: 'user_has_modules'
+      });
+    }
+  });
+
+  return Module;
+};
diff --git a/server/models/motionbar.js b/server/models/motionbar.js
index efbcafc..ad6cba8 100644
--- a/server/models/motionbar.js
+++ b/server/models/motionbar.js
@@ -1 +1,32 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Motionbar = sequelize.define('Motionbar', {
+    action: {
+      type: DataTypes.ENUM,
+      values: ['hangup'],
+      allowNull: false
+    },
+    state: {
+      type: DataTypes.ENUM,
+      values: ['incoming', 'active'],
+    },
+    phone: {
+      type: DataTypes.STRING
+    }
+  }, {
+    tableName: 'motionbar',
+    associate: function(models) {
+      Motionbar.belongsTo(models.User, {
+        as: 'Admin'
+      });
+      Motionbar.belongsTo(models.User, {
+        as: 'User'
+      });
+    }
+  });
+
+  return Motionbar;
+
+};
diff --git a/server/models/pause.js b/server/models/pause.js
index c4db19e..ee1ef95 100644
--- a/server/models/pause.js
+++ b/server/models/pause.js
@@ -1 +1,25 @@
-var _0xc4d1=["\x65\x78\x70\x6F\x72\x74\x73","\x50\x61\x75\x73\x65","\x53\x54\x52\x49\x4E\x47","\x70\x61\x75\x73\x65\x73","\x64\x65\x66\x69\x6E\x65"];module[_0xc4d1[0]]=function(_0xb3b8x1,_0xb3b8x2){var _0xb3b8x3=_0xb3b8x1[_0xc4d1[4]](_0xc4d1[1],{name:{type:_0xb3b8x2[_0xc4d1[2]],unique:true,allowNull:false,validate:{notEmpty:true}},description:{type:_0xb3b8x2[_0xc4d1[2]]}},{tableName:_0xc4d1[3],associate:function(_0xb3b8x4){}});return _0xb3b8x3}
\ No newline at end of file
+/**
+ * tag Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Pause = sequelize.define('Pause', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true,
+      }
+    },
+    description: {
+      type: DataTypes.STRING
+    }
+  }, {
+    tableName: 'pauses',
+    associate: function(models) {}
+  });
+
+  return Pause;
+};
diff --git a/server/models/report_agent.js b/server/models/report_agent.js
index ee1795f..a7628cf 100644
--- a/server/models/report_agent.js
+++ b/server/models/report_agent.js
@@ -1 +1,71 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportAgent = sequelize.define('ReportAgent', {
+    uniqueid: DataTypes.STRING,
+    calleridnum: DataTypes.STRING,
+    calleridname: DataTypes.STRING,
+    queue: DataTypes.STRING,
+    interface: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    agentcalledAt: DataTypes.DATE,
+    agentconnectAt: DataTypes.DATE,
+    agentcompleteAt: DataTypes.DATE,
+    agentringnoanswerAt: DataTypes.DATE,
+    agentdumpAt: DataTypes.DATE,
+    holdtime: DataTypes.INTEGER,
+    agentcomplete: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentringnoanswer: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentdump: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    talktime: DataTypes.INTEGER,
+    agentacw: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    acwtime: DataTypes.INTEGER,
+    reason: DataTypes.STRING,
+    lastevent: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    channelstate: DataTypes.INTEGER,
+    channelstatedesc: DataTypes.STRING,
+    connectedlinenum: DataTypes.STRING,
+    connectedlinename: DataTypes.STRING,
+    language: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    context: DataTypes.STRING,
+    exten: DataTypes.STRING,
+    priority: DataTypes.STRING,
+    destchannel: DataTypes.STRING,
+    destchannelstate: DataTypes.INTEGER,
+    destchannelstatedesc: DataTypes.STRING,
+    destcalleridnum: DataTypes.STRING,
+    destcalleridname: DataTypes.STRING,
+    destconnectedlinenum: DataTypes.STRING,
+    destconnectedlinename: DataTypes.STRING,
+    destlanguage: DataTypes.STRING,
+    destaccountcode: DataTypes.STRING,
+    destcontext: DataTypes.STRING,
+    destexten: DataTypes.STRING,
+    destpriority: DataTypes.STRING,
+    destuniqueid: DataTypes.STRING
+  }, {
+    tableName: 'report_agent',
+    indexes: [{
+      fields: ['uniqueid', 'destuniqueid']
+    }]
+  });
+
+  return ReportAgent;
+};
diff --git a/server/models/report_call.js b/server/models/report_call.js
index 692f90f..44e7515 100644
--- a/server/models/report_call.js
+++ b/server/models/report_call.js
@@ -1 +1,60 @@
\ No newline at end of file
+/**
+ * CDR Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportCall = sequelize.define('ReportCall', {
+    uniqueid: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    type: {
+      type: DataTypes.ENUM,
+      values: ['inbound', 'internal', 'outbound']
+    },
+    tag: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    source: DataTypes.STRING,
+    destination: DataTypes.STRING,
+    destinationcontext: DataTypes.STRING,
+    callerid: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    destinationchannel: DataTypes.STRING,
+    lastapplication: DataTypes.STRING,
+    lastdata: DataTypes.STRING,
+    starttime: DataTypes.DATE,
+    answertime: {
+      type: DataTypes.DATE,
+      set: function(answertime) {
+        if (answertime !== '') {
+          this.setDataValue('answertime', answertime);
+        } else {
+          this.setDataValue('answertime', null);
+        }
+      }
+    },
+    endtime: DataTypes.DATE,
+    duration: DataTypes.INTEGER,
+    billableseconds: DataTypes.INTEGER,
+    disposition: DataTypes.STRING,
+    amaflags: DataTypes.STRING,
+    userfield: DataTypes.STRING
+  }, {
+    tableName: 'report_call',
+    associate: function(models) {
+      ReportCall.hasMany(models.ReportQueue, {
+        as: 'QueueCalls',
+        foreignKey: 'uniqueid',
+        constraints: false
+      })
+      ReportCall.hasMany(models.ReportAgent, {
+        as: 'ReportAgents',
+        foreignKey: 'uniqueid',
+        constraints: false
+      })
+    }
+  });
+
+  return ReportCall;
+};
diff --git a/server/models/report_chat.js b/server/models/report_chat.js
index 4edada8..8f1febf 100644
--- a/server/models/report_chat.js
+++ b/server/models/report_chat.js
@@ -1 +1,45 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportChat = sequelize.define('ReportChat', {
+
+    uniqueid: DataTypes.STRING,
+    timeslot: DataTypes.INTEGER,
+    websitename: DataTypes.STRING,
+    websiteaddress: DataTypes.STRING,
+    websiteid: DataTypes.INTEGER,
+    application: DataTypes.STRING,
+    agentid: DataTypes.INTEGER,
+    agentname: DataTypes.STRING,
+    visitorid: DataTypes.STRING,
+    visitorname: DataTypes.STRING,
+    visitoremail: DataTypes.STRING,
+    queueid: DataTypes.INTEGER,
+    queuename: DataTypes.STRING,
+    roomid: DataTypes.INTEGER,
+    reason: {
+      type: DataTypes.STRING,
+      defaultValue: 'called'
+    },
+    connectid: {
+      type: DataTypes.STRING,
+      unique: true,
+      set: function(agentconnectedAt) {
+        this.setDataValue('connectid', this.uniqueid);
+        this.setDataValue('reason', 'connect');
+        this.setDataValue('connectedAt', moment().format("YYYY-MM-DD HH:mm:ss"));
+      }
+    },
+    calledAt: DataTypes.DATE,
+    connectedAt: DataTypes.DATE
+  }, {
+    tableName: 'report_chat'
+  });
+
+  return ReportChat;
+};
diff --git a/server/models/report_chat_session.js b/server/models/report_chat_session.js
index e225792..9944aaa 100644
--- a/server/models/report_chat_session.js
+++ b/server/models/report_chat_session.js
@@ -1 +1,35 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportChatSession = sequelize.define('ReportChatSession', {
+    uniqueid: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    websitename: DataTypes.STRING,
+    websiteaddress: DataTypes.STRING,
+    websiteid: DataTypes.INTEGER,
+    application: DataTypes.STRING,
+    visitorid: DataTypes.STRING,
+    visitorname: DataTypes.STRING,
+    visitoremail: DataTypes.STRING,
+    agentid: DataTypes.STRING,
+    agentname: DataTypes.STRING,
+    queueid: DataTypes.INTEGER,
+    queuename: DataTypes.STRING,
+    roomid: DataTypes.INTEGER,
+    joinAt: DataTypes.DATE,
+    leaveAt: DataTypes.DATE,
+    unmanagedAt: DataTypes.DATE,
+    abandonAt: DataTypes.DATE
+  }, {
+    tableName: 'report_chat_session'
+  });
+
+  return ReportChatSession;
+};
diff --git a/server/models/report_field.js b/server/models/report_field.js
index c70df48..208b861 100644
--- a/server/models/report_field.js
+++ b/server/models/report_field.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportField = sequelize.define('ReportField', {
+    field: DataTypes.STRING,
+    alias: DataTypes.STRING,
+    function: DataTypes.STRING,
+    groupBy: DataTypes.BOOLEAN,
+    orderBy: DataTypes.STRING
+  }, {
+    tableName: 'report_fields',
+    associate: function(models) {
+      ReportField.belongsTo(models.Metric);
+      ReportField.addScope('metrics', {
+        include: [models.Metric]
+      });
+    }
+  });
+
+  return ReportField;
+};
diff --git a/server/models/report_integration.js b/server/models/report_integration.js
index 84b9ac7..a285620 100644
--- a/server/models/report_integration.js
+++ b/server/models/report_integration.js
@@ -1 +1,86 @@
\ No newline at end of file
+/**
+ * report_integrations Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportIntegration = sequelize.define('ReportIntegration', {
+    integration: DataTypes.STRING,
+    eventChannel: DataTypes.STRING,
+    exitStatus: DataTypes.STRING,
+    ticketId: DataTypes.STRING,
+    integrationId: DataTypes.INTEGER,
+    contacts: DataTypes.STRING,
+    uniqueid: DataTypes.STRING,
+    calleridnum: DataTypes.STRING,
+    calleridname: DataTypes.STRING,
+    queue: DataTypes.STRING,
+    interface: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    agentcalledAt: DataTypes.DATE,
+    agentconnectAt: DataTypes.DATE,
+    holdtime: DataTypes.INTEGER,
+    agentcomplete: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentcompleteAt: DataTypes.DATE,
+    talktime: DataTypes.INTEGER,
+    agentacw: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    acwtime: DataTypes.INTEGER,
+    reason: DataTypes.STRING,
+    agentringnoanswer: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentringnoanswerAt: DataTypes.DATE,
+    agentdump: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false,
+    },
+    agentdumpAt: DataTypes.DATE,
+    lastevent: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    channelstate: DataTypes.INTEGER,
+    channelstatedesc: DataTypes.STRING,
+    connectedlinenum: DataTypes.STRING,
+    connectedlinename: DataTypes.STRING,
+    language: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    context: DataTypes.STRING,
+    exten: DataTypes.STRING,
+    priority: DataTypes.STRING,
+    destchannel: DataTypes.STRING,
+    destchannelstate: DataTypes.INTEGER,
+    destchannelstatedesc: DataTypes.STRING,
+    destcalleridnum: DataTypes.STRING,
+    destcalleridname: DataTypes.STRING,
+    destconnectedlinenum: DataTypes.STRING,
+    destconnectedlinename: DataTypes.STRING,
+    destlanguage: DataTypes.STRING,
+    destaccountcode: DataTypes.STRING,
+    destcontext: DataTypes.STRING,
+    destexten: DataTypes.STRING,
+    destpriority: DataTypes.STRING,
+    destuniqueid: DataTypes.STRING,
+    messageId: DataTypes.STRING,
+    inReplyTo: DataTypes.STRING,
+    subject: DataTypes.STRING,
+    from: DataTypes.STRING,
+    to: DataTypes.STRING,
+    cc: DataTypes.STRING,
+    attachment: DataTypes.TEXT,
+    html: DataTypes.TEXT,
+    text: DataTypes.TEXT,
+    status: {
+      type: DataTypes.ENUM('SENT', 'SENDING', 'RECEIVED', 'FAILED')
+    }
+  }, {
+    tableName: 'report_integration'
+  });
+
+  return ReportIntegration;
+};
diff --git a/server/models/report_jscripty_input.js b/server/models/report_jscripty_input.js
index bcc9b77..5a3d4f5 100644
--- a/server/models/report_jscripty_input.js
+++ b/server/models/report_jscripty_input.js
@@ -1 +1,12 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('ReportJscriptyInput', {
+    input_id: DataTypes.INTEGER,
+    input_type: DataTypes.STRING,
+    input_label: DataTypes.STRING,
+    input_value: DataTypes.STRING
+  }, {
+    tableName: 'report_jscripty_inputs'
+  });
+};
diff --git a/server/models/report_jscripty_question.js b/server/models/report_jscripty_question.js
index 15f72a6..f6080f4 100644
--- a/server/models/report_jscripty_question.js
+++ b/server/models/report_jscripty_question.js
@@ -1 +1,34 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('ReportJscriptyQuestion', {
+    question_id: DataTypes.INTEGER,
+    question_type: DataTypes.STRING,
+    question_label: DataTypes.STRING,
+    question_text: DataTypes.STRING,
+    question_answer: DataTypes.STRING,
+    question_other_label: DataTypes.STRING,
+    question_other_answer: DataTypes.STRING
+
+  }, {
+    tableName: 'report_jscripty_questions',
+    associate: function(models) {
+      models.ReportJscriptyQuestion.hasMany(models.ReportJscriptyInput, {
+        onDelete: 'cascade',
+        foreignKey: 'questionId'
+
+      });
+      models.ReportJscriptyQuestion.addScope('questionSummary', function(projectId) {
+        return {
+          where: {
+            projectId: projectId
+
+          },
+          attributes: ['question_id', 'question_type', 'question_label', 'question_text', [sequelize.fn('count', sequelize.col('question_id')), 'count']],
+          group: ['question_id', 'question_type', 'question_label', 'question_text']
+        }
+
+      });
+    }
+  });
+};
diff --git a/server/models/report_jscripty_session.js b/server/models/report_jscripty_session.js
index 4fd7ca2..12a8447 100644
--- a/server/models/report_jscripty_session.js
+++ b/server/models/report_jscripty_session.js
@@ -1 +1,51 @@
\ No newline at end of file
+'use strict';
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('ReportJscriptySession', {
+    session_id: {
+      type: DataTypes.INTEGER,
+      primaryKey: true,
+      autoIncrement: true
+    },
+    status: {
+      type: DataTypes.ENUM('started', 'completed', 'incomplete')
+    },
+    last_question_id: DataTypes.INTEGER,
+    completedAt: {
+      type: DataTypes.DATE,
+      get: function() {
+        // 'this' allows you to access attributes of the instance
+        return this.getDataValue('completedAt') ? moment(this.getDataValue('completedAt')).format("MM-DD-YYYY HH:mm") : '';
+      }
+    },
+    createdAt: {
+      type: DataTypes.DATE,
+      get: function() {
+        // 'this' allows you to access attributes of the instance
+        return moment(this.getDataValue('createdAt')).format("MM-DD-YYYY HH:mm");
+      }
+    }
+  }, {
+    tableName: 'report_jscripty_sessions',
+    associate: function(models) {
+      models.ReportJscriptySession.hasMany(models.ReportJscriptyQuestion, {
+        onDelete: 'cascade',
+        foreignKey: 'sessionId'
+      });
+      models.ReportJscriptySession.addScope('questionsAndInput', function(projectId) {
+        return {
+          where: {
+            ProjectId: projectId
+          },
+          include: [{
+            model: models.ReportJscriptyQuestion,
+            include: [{
+              model: models.ReportJscriptyInput
+            }]
+          }]
+        }
+      });
+    }
+  });
+};
diff --git a/server/models/report_mail.js b/server/models/report_mail.js
index 377006b..d4327ea 100644
--- a/server/models/report_mail.js
+++ b/server/models/report_mail.js
@@ -1 +1,43 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportMail = sequelize.define('ReportMail', {
+    uniqueid: DataTypes.STRING,
+    timeslot: DataTypes.INTEGER,
+    subject: DataTypes.STRING,
+    accountid: DataTypes.STRING,
+    accountname: DataTypes.STRING,
+    accountaddress: DataTypes.STRING,
+    application: DataTypes.STRING,
+    memberid: DataTypes.INTEGER,
+    membername: DataTypes.STRING,
+    queue: DataTypes.INTEGER,
+    queuename: DataTypes.STRING,
+    roomid: DataTypes.INTEGER,
+    messageid: DataTypes.INTEGER,
+    reason: {
+      type: DataTypes.STRING,
+      defaultValue: 'called'
+    },
+    connectid: {
+      type: DataTypes.STRING,
+      unique: true,
+      set: function(agentconnectedAt) {
+        this.setDataValue('connectid', this.uniqueid);
+        this.setDataValue('reason', 'connect');
+        this.setDataValue('connectedAt', moment().format("YYYY-MM-DD HH:mm:ss"));
+      }
+    },
+    calledAt: DataTypes.DATE,
+    connectedAt: DataTypes.DATE
+  }, {
+    tableName: 'report_mail'
+  });
+
+  return ReportMail;
+};
diff --git a/server/models/report_mail_session.js b/server/models/report_mail_session.js
index 3e3904d..903631e 100644
--- a/server/models/report_mail_session.js
+++ b/server/models/report_mail_session.js
@@ -1 +1,37 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+  var ReportMailSession = sequelize.define('ReportMailSession', {
+    uniqueid: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    subject: DataTypes.STRING,
+    accountid: DataTypes.STRING,
+    accountname: DataTypes.STRING,
+    accountaddress: DataTypes.STRING,
+    application: DataTypes.STRING,
+    memberid: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    queue: DataTypes.INTEGER,
+    queuename: DataTypes.STRING,
+    roomid: DataTypes.INTEGER,
+    fidelity: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0
+    },
+    holdtime: DataTypes.INTEGER,
+    queuecallerjoinAt: DataTypes.DATE,
+    queuecallerleaveAt: DataTypes.DATE,
+    queuecallerunmanagedAt: DataTypes.DATE,
+    queuecallerabandonedAt: DataTypes.DATE
+  }, {
+    tableName: 'report_mail_session'
+  });
+
+  return ReportMailSession;
+};
diff --git a/server/models/report_member.js b/server/models/report_member.js
index f0ea647..e4aac4e 100644
--- a/server/models/report_member.js
+++ b/server/models/report_member.js
@@ -1 +1,33 @@
\ No newline at end of file
+/**
+ * DIAL Model
+ */
+
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportMember = sequelize.define('ReportMember', {
+    channel: DataTypes.STRING,
+    membername: DataTypes.STRING,
+    interface: DataTypes.STRING,
+    type: {
+      type: DataTypes.STRING,
+      defaultValue: 'DEFAULT PAUSE'
+    },
+    duration: DataTypes.INTEGER,
+    enterAt: DataTypes.DATE,
+    exitAt: DataTypes.DATE,
+    data1: DataTypes.STRING,
+    data2: DataTypes.STRING,
+    data3: DataTypes.STRING,
+    data4: DataTypes.STRING,
+    data5: DataTypes.STRING
+  }, {
+    tableName: 'report_member',
+    // indexes: [{
+    //   fields: ['membername', 'enterdate', 'exitdate']
+    // }]
+  });
+
+  return ReportMember;
+};
diff --git a/server/models/report_queue.js b/server/models/report_queue.js
index 7dceb84..b06b742 100644
--- a/server/models/report_queue.js
+++ b/server/models/report_queue.js
@@ -1 +1,63 @@
\ No newline at end of file
+/**
+ * QueueLog Model
+ */
+
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportQueue = sequelize.define('ReportQueue', {
+    uniqueid: DataTypes.STRING,
+    calleridnum: DataTypes.STRING,
+    calleridname: DataTypes.STRING,
+    queue: DataTypes.STRING,
+    queuecallerjoinAt: DataTypes.DATE,
+    queuecallerleaveAt: {
+      type: DataTypes.DATE,
+      set: function(queuecallerleaveAt) {
+        var a = moment(queuecallerleaveAt);
+        var b = moment(this.getDataValue('queuecallerjoinAt')).format("YYYY-MM-DD HH:mm:ss");
+
+        this.setDataValue('queuecallerleaveAt', queuecallerleaveAt);
+        this.setDataValue('holdtime', a.diff(b, 'seconds'));
+      }
+    },
+    position: DataTypes.INTEGER,
+    count: DataTypes.INTEGER,
+    queuecallerabandon: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    queuecallerabandonAt: DataTypes.DATE,
+    queuecallercomplete: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    queuecallercompleteAt: DataTypes.DATE,
+    queuecallerexit: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    queuecallerexitAt: DataTypes.DATE,
+    queuecallerexitreason: DataTypes.STRING,
+    originalposition: DataTypes.INTEGER,
+    channel: DataTypes.STRING,
+    channelstate: DataTypes.INTEGER,
+    channelstatedesc: DataTypes.STRING,
+    connectedlinenum: DataTypes.STRING,
+    connectedlinename: DataTypes.STRING,
+    language: DataTypes.STRING,
+    accountcode: DataTypes.STRING,
+    context: DataTypes.STRING,
+    exten: DataTypes.STRING,
+    priority: DataTypes.STRING,
+    holdtime: DataTypes.INTEGER
+  }, {
+    tableName: 'report_queue',
+    indexes: [{
+      fields: ['uniqueid']
+    }]
+  });
+
+  return ReportQueue;
+};
diff --git a/server/models/report_square.js b/server/models/report_square.js
index c32ad84..62a8e0c 100644
--- a/server/models/report_square.js
+++ b/server/models/report_square.js
@@ -1 +1,89 @@
\ No newline at end of file
+/**
+ * Context Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportSquare = sequelize.define('ReportSquare', {
+    network: {
+      type: DataTypes.STRING,
+    },
+    network_script: {
+      type: DataTypes.STRING,
+    },
+    request: {
+      type: DataTypes.STRING,
+    },
+    channel: {
+      type: DataTypes.STRING,
+    },
+    language: {
+      type: DataTypes.STRING,
+    },
+    type: {
+      type: DataTypes.STRING,
+    },
+    uniqueid: {
+      type: DataTypes.STRING,
+    },
+    version: {
+      type: DataTypes.STRING,
+    },
+    callerid: {
+      type: DataTypes.STRING,
+    },
+    calleridname: {
+      type: DataTypes.STRING,
+    },
+    callingpres: {
+      type: DataTypes.STRING,
+    },
+    callingani2: {
+      type: DataTypes.STRING,
+    },
+    callington: {
+      type: DataTypes.STRING,
+    },
+    callingtns: {
+      type: DataTypes.STRING,
+    },
+    dnid: {
+      type: DataTypes.STRING,
+    },
+    rdnis: {
+      type: DataTypes.STRING,
+    },
+    context: {
+      type: DataTypes.STRING,
+    },
+    extension: {
+      type: DataTypes.STRING,
+    },
+    priority: {
+      type: DataTypes.STRING,
+    },
+    enhanced: {
+      type: DataTypes.STRING,
+    },
+    accountcode: {
+      type: DataTypes.STRING,
+    },
+    threadid: {
+      type: DataTypes.STRING,
+    },
+    project_name: {
+      type: DataTypes.STRING,
+    },
+    joinAt: {
+      type: DataTypes.DATE,
+    },
+    leaveAt: {
+      type: DataTypes.DATE,
+      unique: true
+    }
+  }, {
+    tableName: 'report_square'
+  });
+
+  return ReportSquare;
+};
diff --git a/server/models/report_square_detail.js b/server/models/report_square_detail.js
index 1303944..b9a2f4c 100644
--- a/server/models/report_square_detail.js
+++ b/server/models/report_square_detail.js
@@ -1 +1,25 @@
\ No newline at end of file
+/**
+ * Context Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportSquareDetail = sequelize.define('ReportSquareDetail', {
+    uniqueid: {
+      type: DataTypes.STRING,
+    },
+    node: {
+      type: DataTypes.STRING,
+    },
+    application: {
+      type: DataTypes.STRING,
+    },
+    data: {
+      type: DataTypes.STRING,
+    }
+  }, {
+    tableName: 'report_square_details'
+  });
+
+  return ReportSquareDetail;
+};
diff --git a/server/models/report_tranfer.js b/server/models/report_tranfer.js
index 3c3554b..f507511 100644
--- a/server/models/report_tranfer.js
+++ b/server/models/report_tranfer.js
@@ -1 +1,25 @@
\ No newline at end of file
+/**
+ * Agent Log Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportTransfer = sequelize.define('ReportTransfer', {
+    type: DataTypes.ENUM('blind', 'attended'),
+    transferercalleridnum: DataTypes.STRING,
+    transferercalleridname: DataTypes.STRING,
+    transfererconnectedlinenum: DataTypes.STRING,
+    transfererconnectedlinename: DataTypes.STRING,
+    transferercontext: DataTypes.STRING,
+    transfererexten: DataTypes.STRING,
+    transfereecontext: DataTypes.STRING,
+    transfereeexten: DataTypes.STRING,
+    transfereruniqueid: DataTypes.STRING,
+    transfererlinkedid: DataTypes.STRING,
+    extension: DataTypes.STRING,
+  }, {
+    tableName: 'report_transfer'
+  });
+
+  return ReportTransfer;
+};
diff --git a/server/models/report_tree.js b/server/models/report_tree.js
index d5cb009..2c14bfe 100644
--- a/server/models/report_tree.js
+++ b/server/models/report_tree.js
@@ -1 +1,12 @@
-var _0x2327=["\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x65\x78\x70\x6F\x72\x74\x73","\x52\x65\x70\x6F\x72\x74\x54\x72\x65\x65","\x54\x45\x58\x54","\x72\x65\x70\x6F\x72\x74\x5F\x74\x72\x65\x65\x73","\x64\x65\x66\x69\x6E\x65"];_0x2327[0];module[_0x2327[1]]=function(_0x2b60x1,_0x2b60x2){var _0x2b60x3=_0x2b60x1[_0x2327[5]](_0x2327[2],{tree:_0x2b60x2[_0x2327[3]]},{tableName:_0x2327[4]});return _0x2b60x3}
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ReportTree = sequelize.define('ReportTree', {
+    tree: DataTypes.TEXT
+  }, {
+    tableName: 'report_trees'
+  });
+
+  return ReportTree;
+};
diff --git a/server/models/salesforce_account.js b/server/models/salesforce_account.js
index 0c712ee..0ea5fa6 100644
--- a/server/models/salesforce_account.js
+++ b/server/models/salesforce_account.js
@@ -1 +1,33 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('SalesforceAccount', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    username: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    remoteUri: DataTypes.STRING,
+    password: DataTypes.STRING,
+    clientId: DataTypes.STRING,
+    clientSecret: DataTypes.STRING,
+    securityToken: DataTypes.STRING
+  }, {
+    tableName: 'salesforce_accounts',
+    associate: function(models) {
+      models.SalesforceAccount.hasMany(models.SalesforceConfiguration, {
+        as: 'Configurations',
+        foreignKey: 'AccountId'
+      });
+      models.SalesforceAccount.addScope('configurations', {
+        include: [{
+          model: models.SalesforceConfiguration,
+          as: 'Configurations',
+          required: false,
+          attributes: ['id', 'name']
+        }]
+      });
+    }
+  });
+};
diff --git a/server/models/salesforce_configuration.js b/server/models/salesforce_configuration.js
index 6eb95ac..6850116 100644
--- a/server/models/salesforce_configuration.js
+++ b/server/models/salesforce_configuration.js
@@ -1 +1,27 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('SalesforceConfiguration', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'salesforce_configurations',
+    associate: function(models) {
+      models.SalesforceConfiguration.belongsTo(models.SalesforceAccount, {
+        foreignKey: 'AccountId'
+      });
+      models.SalesforceConfiguration.hasMany(models.SalesforceField, {
+        as: 'Subject',
+        foreignKey: 'SubjectId'
+      });
+      models.SalesforceConfiguration.hasMany(models.SalesforceField, {
+        as: 'Description',
+        foreignKey: 'DescriptionId'
+      });
+      models.SalesforceConfiguration.hasMany(models.SalesforceField, {
+        as: 'Field',
+        foreignKey: 'FieldId'
+      });
+    }
+  });
+};
diff --git a/server/models/salesforce_field.js b/server/models/salesforce_field.js
index 812f36f..f3a3501 100644
--- a/server/models/salesforce_field.js
+++ b/server/models/salesforce_field.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('SalesforceField', {
+    type: {
+      type: DataTypes.ENUM('string', 'variable', 'key_value'),
+      defaultValue: 'string'
+    },
+    content: DataTypes.STRING,
+    key: DataTypes.STRING,
+    keyType: {
+      type: DataTypes.ENUM('string', 'variable')
+    },
+    keyContent: DataTypes.STRING,
+    idField: DataTypes.STRING
+  }, {
+    tableName: 'salesforce_fields',
+    associate: function(models) {
+      models.SalesforceField.belongsTo(models.Variable);
+    }
+  });
+};
diff --git a/server/models/service.js b/server/models/service.js
index 8e61f66..a8efea2 100644
--- a/server/models/service.js
+++ b/server/models/service.js
@@ -1 +1,42 @@
\ No newline at end of file
+/* jshint indent: 2 */
+
+module.exports = function(sequelize, DataTypes) {
+  var Service = sequelize.define('Service', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    description: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    defaultEntry: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    }
+  }, {
+    tableName: 'services',
+    associate: function(models) {
+      Service.belongsToMany(models.User, {
+        through: models.UserHasService,
+        foreignKey: 'service'
+      });
+      Service.hasMany(models.CustomField, {
+        as: 'CustomFields',
+        required: false
+      });
+      Service.hasMany(models.Contact, {
+        as: 'Contacts',
+        onDelete: 'cascade'
+      });
+      Service.addScope('customFields', {
+        include: [{
+          model: models.CustomField,
+          as: 'CustomFields'
+        }]
+      });
+    }
+  });
+
+  return Service;
+};
diff --git a/server/models/settings.js b/server/models/settings.js
index c61ab93..a7b7931 100644
--- a/server/models/settings.js
+++ b/server/models/settings.js
@@ -1 +1,32 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Settings = sequelize.define('Settings', {
+    license: DataTypes.TEXT,
+    min_internal: {
+      type: DataTypes.INTEGER(11),
+      unique: true,
+      defaultValue: 1000
+    },
+    min_mailbox: {
+      type: DataTypes.INTEGER(11),
+      unique: true,
+      defaultValue: 1000
+    },
+    agi_port: {
+      type: DataTypes.INTEGER(11),
+      defaultValue: 4573
+    },
+    automation_timeout: {
+      type: DataTypes.INTEGER(11),
+      defaultValue: 3600
+    },
+    soundPath: DataTypes.STRING
+  }, {
+    tableName: 'settings'
+  });
+
+  return Settings;
+
+};
diff --git a/server/models/sound.js b/server/models/sound.js
index 49d3b0b..3b5ab98 100644
--- a/server/models/sound.js
+++ b/server/models/sound.js
@@ -1 +1,36 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+	var Sound = sequelize.define('Sound', {
+		name: DataTypes.STRING,
+		save_name: DataTypes.STRING,
+		display_name: {
+			type: DataTypes.STRING,
+			allowNull: false,
+			defaultValue: '',
+			validate: {
+				notEmpty: {
+					msg: "The name cannot be empty!"
+				} // don't allow empty strings
+			}
+		},
+		description: DataTypes.STRING,
+		original_format: DataTypes.STRING,
+		original_duration: DataTypes.FLOAT,
+		original_sampleCount: DataTypes.BIGINT,
+		original_channelCount: DataTypes.INTEGER,
+		original_bitRate: DataTypes.BIGINT,
+		original_sampleRate: DataTypes.INTEGER,
+		converted_format: DataTypes.STRING,
+		converted_duration: DataTypes.FLOAT,
+		converted_sampleCount: DataTypes.BIGINT,
+		converted_channelCount: DataTypes.INTEGER,
+		converted_bitRate: DataTypes.BIGINT,
+		converted_sampleRate: DataTypes.INTEGER
+	}, {
+		tableName: 'sounds'
+	});
+
+	return Sound;
+};
diff --git a/server/models/square_odbc.js b/server/models/square_odbc.js
index be6e2d5..a8a9bf8 100644
--- a/server/models/square_odbc.js
+++ b/server/models/square_odbc.js
@@ -1 +1,24 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var SquareOdbc = sequelize.define('SquareOdbc', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true
+      },
+    },
+    dsn: {
+      type: DataTypes.STRING,
+    },
+    description: DataTypes.STRING
+  }, {
+    tableName: 'square_odbc'
+  });
+
+  return SquareOdbc;
+
+};
diff --git a/server/models/square_project.js b/server/models/square_project.js
index b1d70ea..e9f9ccd 100644
--- a/server/models/square_project.js
+++ b/server/models/square_project.js
@@ -1 +1,29 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var SquareProject = sequelize.define('SquareProject', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true
+      }
+    },
+    description: DataTypes.STRING,
+    preproduction: {
+      type: DataTypes.TEXT,
+      length: 'long'
+    },
+    production: {
+      type: DataTypes.TEXT,
+      length: 'long'
+    }
+  }, {
+    tableName: 'square_projects',
+  });
+
+  return SquareProject;
+
+};
diff --git a/server/models/sugarcrm_account.js b/server/models/sugarcrm_account.js
index c0985c7..1130f64 100644
--- a/server/models/sugarcrm_account.js
+++ b/server/models/sugarcrm_account.js
@@ -1 +1,29 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('SugarcrmAccount', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    username: {
+      type: DataTypes.STRING
+    },
+    remoteUri: DataTypes.STRING,
+    password: DataTypes.STRING
+  }, {
+    tableName: 'sugarcrm_accounts',
+    associate: function(models) {
+      models.SugarcrmAccount.hasMany(models.SugarcrmConfiguration, {
+        as: 'Configurations',
+        foreignKey: 'AccountId'
+      });
+      models.SugarcrmAccount.addScope('configurations', {
+        include: [{
+          model: models.SugarcrmConfiguration,
+          as: 'Configurations',
+          required: false,
+          attributes: ['id', 'name']
+        }]
+      });
+    }
+  });
+};
diff --git a/server/models/sugarcrm_configuration.js b/server/models/sugarcrm_configuration.js
index ba007e2..cae512a 100644
--- a/server/models/sugarcrm_configuration.js
+++ b/server/models/sugarcrm_configuration.js
@@ -1 +1,27 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('SugarcrmConfiguration', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'sugarcrm_configurations',
+    associate: function(models) {
+      models.SugarcrmConfiguration.belongsTo(models.SugarcrmAccount, {
+        foreignKey: 'AccountId'
+      });
+      models.SugarcrmConfiguration.hasMany(models.SugarcrmField, {
+        as: 'Subject',
+        foreignKey: 'SubjectId'
+      });
+      models.SugarcrmConfiguration.hasMany(models.SugarcrmField, {
+        as: 'Description',
+        foreignKey: 'DescriptionId'
+      });
+      models.SugarcrmConfiguration.hasMany(models.SugarcrmField, {
+        as: 'Field',
+        foreignKey: 'FieldId'
+      });
+    }
+  });
+};
diff --git a/server/models/sugarcrm_field.js b/server/models/sugarcrm_field.js
index 76e8cf4..7998058 100644
--- a/server/models/sugarcrm_field.js
+++ b/server/models/sugarcrm_field.js
@@ -1 +1,22 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+  return sequelize.define('SugarcrmField', {
+    type: {
+      type: DataTypes.ENUM('string', 'variable', 'key_value'),
+      defaultValue: 'string'
+    },
+    content: DataTypes.STRING,
+    key: DataTypes.STRING,
+    keyType: {
+      type: DataTypes.ENUM('string', 'variable')
+    },
+    keyContent: DataTypes.STRING,
+    idField: DataTypes.STRING
+  }, {
+    tableName: 'sugarcrm_fields',
+    associate: function(models) {
+      models.SugarcrmField.belongsTo(models.Variable);
+    }
+  });
+};
diff --git a/server/models/tag.js b/server/models/tag.js
index 0411a94..ff7b5ca 100644
--- a/server/models/tag.js
+++ b/server/models/tag.js
@@ -1 +1,35 @@
\ No newline at end of file
+/**
+ * tag Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Tag = sequelize.define('Tag', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true,
+      }
+    },
+    description: DataTypes.STRING
+  }, {
+    tableName: 'tags',
+    associate: function(models) {
+      Tag.belongsToMany(models.ZendeskConfiguration, {
+        through: 'zendesk_configuration_has_tags'
+      });
+      Tag.addScope('configuration', {
+        include: [{
+          all: true
+        }]
+      });
+      // Tag.belongsToMany(models.Contact, {
+      //   through: 'contact_tags'
+      // });
+    }
+  });
+
+  return Tag;
+};
diff --git a/server/models/team.js b/server/models/team.js
index db65271..e2d6f73 100644
--- a/server/models/team.js
+++ b/server/models/team.js
@@ -1 +1,42 @@
\ No newline at end of file
+/**
+ * User Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Team = sequelize.define('Team', {
+    name: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: true
+    },
+    description: {
+      type: DataTypes.STRING
+    },
+    defaultEntry: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: 0
+    }
+  }, {
+    tableName: 'teams',
+    associate: function(models) {
+      Team.belongsToMany(models.User, {
+        through: models.UserHasTeam
+      });
+      Team.addScope('default', {
+        include: [{
+          model: models.User,
+          where: {
+            role: 'agent'
+          },
+          required: false,
+          attributes: ['id', 'name', 'fullname', 'email', 'internal']
+        }]
+      });
+    }
+  });
+
+  return Team;
+};
diff --git a/server/models/telephone.js b/server/models/telephone.js
index 17f875c..105d888 100644
--- a/server/models/telephone.js
+++ b/server/models/telephone.js
@@ -1 +1,507 @@
\ No newline at end of file
+'use strict';
+
+var crypto = require('crypto');
+var md5 = require('md5');
+var _ = require('lodash');
+
+module.exports = function(sequelize, DataTypes) {
+  var Telephone = sequelize.define('Telephone', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      validate: {
+        notEmpty: true,
+        is: /^[A-Za-z0-9\.\_]+$/i
+      },
+      set: function(name) {
+        this.setDataValue('name', name);
+        this.setDataValue('defaultuser', name);
+      }
+    },
+    password: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      validate: {
+        notEmpty: true
+      },
+      set: function(password) {
+        this.salt = this.makeSalt();
+        this.setDataValue('password', this.encryptPassword(password));
+        this.setDataValue('md5secret', this.md5Password(this.name + ':asterisk:' + password));
+      }
+    },
+    internal: {
+      type: DataTypes.INTEGER(11),
+      unique: true,
+      set: function(internal) {
+        this.setDataValue('internal', internal);
+        this.setDataValue('accountcode', internal);
+      }
+    },
+    ipaddr: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    port: {
+      type: DataTypes.INTEGER(5),
+      allowNull: true,
+    },
+    regseconds: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    defaultuser: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fullcontact: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    regserver: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    useragent: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    lastms: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    host: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'dynamic'
+    },
+    type: {
+      type: DataTypes.ENUM('friend', 'user', 'peer'),
+      allowNull: true,
+      defaultValue: 'friend'
+    },
+    context: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'from-sip'
+    },
+    permit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    deny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    secret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    md5secret: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    remotesecret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    transport: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'udp'
+    },
+    dtmfmode: {
+      type: DataTypes.ENUM('rfc2833', 'info', 'shortinfo', 'inband',
+        'auto'),
+      allowNull: true,
+      defaultValue: 'rfc2833'
+    },
+    directmedia: {
+      type: DataTypes.ENUM('yes', 'no', 'nonat', 'update'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    nat: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'force_rport,comedia'
+    },
+    callgroup: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    pickupgroup: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    language: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'en'
+    },
+    disallow: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'all'
+    },
+    allow: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'alaw;ulaw;gsm'
+    },
+    insecure: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'port,invite'
+    },
+    trustrpid: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    progressinband: {
+      type: DataTypes.ENUM('yes', 'no', 'never'),
+      allowNull: true,
+    },
+    promiscredir: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    useclientcode: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    accountcode: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    setvar: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callerid: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: '"" <>'
+    },
+    amaflags: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callcounter: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    busylevel: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    allowoverlap: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    allowsubscribe: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    videosupport: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    maxcallbitrate: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rfc2833compensate: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    mailbox: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    "session-timers": {
+      type: DataTypes.ENUM('accept', 'refuse', 'originate'),
+      allowNull: true,
+    },
+    "session-expires": {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    "session-minse": {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    "session-refresher": {
+      type: DataTypes.ENUM('uac', 'uas'),
+      allowNull: true,
+    },
+    t38pt_usertpsource: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    regexten: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fromdomain: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fromuser: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    qualify: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    defaultip: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    rtptimeout: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rtpholdtimeout: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    sendrpid: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    outboundproxy: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callbackextension: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    timert1: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    timerb: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    qualifyfreq: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    constantssrc: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    contactpermit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    contactdeny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    usereqphone: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    textsupport: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    faxdetect: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    buggymwi: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    auth: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fullname: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    trunkname: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    cid_number: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callingpres: {
+      type: DataTypes.ENUM('ALLOWED_NOT_SCREENED',
+        'ALLOWED_PASSED_SCREEN', 'ALLOWED_FAILED_SCREEN', 'ALLOWED',
+        'PROHIB_NOT_SCREENED', 'PROHIB_PASSED_SCREEN',
+        'PROHIB_FAILED_SCREEN', 'PROHIB'),
+      allowNull: true,
+    },
+    mohinterpret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    mohsuggest: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    parkinglot: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    hasvoicemail: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    subscribemwi: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    vmexten: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    description: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    autoframing: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    limitonpeers: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    rtpkeepalive: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    "call-limit": {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: null
+    },
+    g726nonstandard: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    ignoresdpversion: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    allowtransfer: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    dynamic: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    encryption: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    registry: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    }
+  }, {
+    tableName: 'users',
+    defaultScope: {
+      where: {
+        role: 'telephone'
+      }
+    },
+    instanceMethods: {
+      /**
+       * Authenticate - check if the passwords are the same
+       *
+       * @param {String} plainText
+       *        {function} callBack
+       * @api public
+       */
+      authenticate: function(plainText) {
+        return this.encryptPassword(plainText) === this.password;
+      },
+      /**
+       * Make salt
+       *
+       * @return {String}
+       * @api public
+       */
+      makeSalt: function() {
+        return crypto.randomBytes(16).toString('base64');
+      },
+      /**
+       * Encrypt password
+       *
+       * @param {String} password
+       * @return {String}
+       * @api public
+       */
+      encryptPassword: function(password) {
+        if (!password || !this.salt) return '';
+        var salt = new Buffer(this.salt, 'base64');
+        return crypto.pbkdf2Sync(password, salt, 10000, 64).toString(
+          'base64');
+      },
+      /**
+       * md5 password
+       *
+       * @param {String} password
+       * @return {String}
+       * @api public
+       */
+      md5Password: function(password) {
+        if (!password) return '';
+        return md5(password);
+      }
+    },
+    associate: function(models) {
+      // BELOGNS TO MANY
+      // Telephone.hasMany(models.ChatMessage);
+      // Telephone.hasMany(models.Contact);
+      // Telephone.belongsToMany(models.Module, {
+      //   through: 'user_has_modules'
+      // });
+      // Telephone.belongsToMany(models.Channel, {
+      //   through: 'user_has_channels'
+      // });
+      // Telephone.belongsToMany(models.Team, {
+      //   through: models.UserHasTeam
+      // });
+      // Telephone.belongsToMany(models.ChatRoom, {
+      //   through: models.UserHasChatRoom
+      // });
+      // Telephone.belongsToMany(models.MailQueue, {
+      //   through: models.UserHasMailQueue
+      // });
+      // Telephone.belongsToMany(models.ChatQueue, {
+      //   through: models.UserHasChatQueue
+      // });
+      // Telephone.belongsToMany(models.VoiceQueue, {
+      //   through: models.UserHasVoiceQueue
+      // });
+      // Telephone.hasMany(models.VoiceExtension, {
+      //   foreignKey: 'UserId',
+      //   as: 'UserExtensions',
+      //   onDelete: 'cascade'
+      // });
+    }
+  });
+
+  return Telephone;
+}
diff --git a/server/models/tools_action.js b/server/models/tools_action.js
index cc635cf..9097c07 100644
--- a/server/models/tools_action.js
+++ b/server/models/tools_action.js
@@ -1 +1,32 @@
\ No newline at end of file
+/**
+ * Tools Actions Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ToolsAction = sequelize.define('ToolsAction', {
+    action: {
+      type: DataTypes.STRING
+    },
+    data1: {
+      type: DataTypes.STRING
+    },
+    data2: {
+      type: DataTypes.STRING
+    },
+    data3: {
+      type: DataTypes.STRING
+    },
+    data4: {
+      type: DataTypes.STRING
+    },
+    data5: {
+      type: DataTypes.STRING
+    }
+  }, {
+    tableName: 'tools_actions',
+    associate: function(models) {}
+  });
+
+  return ToolsAction;
+};
diff --git a/server/models/tools_condition.js b/server/models/tools_condition.js
index 67bb878..cb0b603 100644
--- a/server/models/tools_condition.js
+++ b/server/models/tools_condition.js
@@ -1 +1,18 @@
\ No newline at end of file
+/**
+ * Tools Conditions Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ToolsCondition = sequelize.define('ToolsCondition', {
+    field: DataTypes.STRING,
+    operator: DataTypes.STRING,
+    value: DataTypes.STRING
+  }, {
+    tableName: 'tools_conditions'
+  });
+
+  return ToolsCondition;
+};
diff --git a/server/models/trigger.js b/server/models/trigger.js
index d065bfc..2953319 100644
--- a/server/models/trigger.js
+++ b/server/models/trigger.js
@@ -1 +1,60 @@
\ No newline at end of file
+/**
+ * Trigger Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Trigger = sequelize.define('Trigger', {
+    name: DataTypes.STRING,
+    channel: DataTypes.STRING,
+    description: DataTypes.STRING,
+    status: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    }
+  }, {
+    tableName: 'triggers',
+    associate: function(models) {
+      Trigger.hasMany(models.ToolsCondition, {
+        as: {
+          plural: 'All',
+          singular: 'OneAll'
+        },
+        foreignKey: 'TriggerAllId',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      Trigger.hasMany(models.ToolsCondition, {
+        as: {
+          plural: 'Any',
+          singular: 'OneAny'
+        },
+        foreignKey: 'TriggerAnyId',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      Trigger.hasMany(models.ToolsAction, {
+        as: 'Actions',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      Trigger.addScope('all', {
+        include: [{
+          model: models.ToolsCondition,
+          as: 'All',
+          attributes: ['field', 'operator', 'value']
+        }, {
+          model: models.ToolsCondition,
+          as: 'Any',
+          attributes: ['field', 'operator', 'value']
+        }, {
+          model: models.ToolsAction,
+          as: 'Actions',
+          attributes: ['action', 'data1', 'data2', 'data3', 'data4', 'data5']
+        }]
+      });
+    }
+  });
+
+  return Trigger;
+};
diff --git a/server/models/trunk.js b/server/models/trunk.js
index 6a48f2c..e9ff649 100644
--- a/server/models/trunk.js
+++ b/server/models/trunk.js
@@ -1 +1,458 @@
\ No newline at end of file
+'use strict';
+
+var crypto = require('crypto');
+var md5 = require('md5');
+var _ = require('lodash');
+
+module.exports = function(sequelize, DataTypes) {
+  var Trunk = sequelize.define('Trunk', {
+    name: {
+      type: DataTypes.STRING,
+      primaryKey: true
+    },
+    type: {
+      type: DataTypes.ENUM('friend', 'user', 'peer'),
+      allowNull: true,
+      defaultValue: 'friend'
+    },
+    context: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'from-sip'
+    },
+    callingpres: {
+      type: DataTypes.ENUM('ALLOWED_NOT_SCREENED',
+        'ALLOWED_PASSED_SCREEN', 'ALLOWED_FAILED_SCREEN', 'ALLOWED',
+        'PROHIB_NOT_SCREENED', 'PROHIB_PASSED_SCREEN',
+        'PROHIB_FAILED_SCREEN', 'PROHIB'),
+      allowNull: true,
+    },
+    permit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    deny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    secret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    md5secret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    remotesecret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    transport: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'udp'
+    },
+    dtmfmode: {
+      type: DataTypes.ENUM('rfc2833', 'info', 'shortinfo', 'inband',
+        'auto'),
+      allowNull: true,
+      defaultValue: 'rfc2833'
+    },
+    directmedia: {
+      type: DataTypes.ENUM('yes', 'no', 'nonat', 'update', 'outgoing'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    directrtpsetup: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    directmediapermit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    directmediadeny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    nat: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'force_rport,comedia'
+    },
+    callgroup: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    namedcallgroup: { //We are in named call groups engineering,sales,netgroup,protgroup
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    pickupgroup: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    namedpickupgroup: { //We can do call pick-p for named call group sales
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    language: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'en'
+    },
+    tonezone: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    disallow: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'all'
+    },
+    allow: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      defaultValue: 'alaw;ulaw;gsm'
+    },
+    autoframing: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    insecure: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'port,invite'
+    },
+    trustrpid: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    trust_id_outbound: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    progressinband: {
+      type: DataTypes.ENUM('yes', 'no', 'never'),
+      allowNull: true,
+    },
+    promiscredir: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    useclientcode: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    accountcode: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    setvar: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callerid: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: '"" <>'
+    },
+    amaflags: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callcounter: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    busylevel: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    allowoverlap: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    allowsubscribe: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    allowtransfer: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    ignoresdpversion: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    subscribecontext: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    template: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    videosupport: {
+      type: DataTypes.ENUM('yes', 'no', 'always'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    maxcallbitrate: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rfc2833compensate: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    mailbox: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    session_timers: {
+      type: DataTypes.ENUM('accept', 'refuse', 'originate'),
+      allowNull: true,
+    },
+    session_expires: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    session_minse: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    session_refresher: {
+      type: DataTypes.ENUM('uac', 'uas'),
+      allowNull: true,
+      defaultValue: 'uas'
+    },
+    t38pt_usertpsource: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    regexten: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fromdomain: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fromuser: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    host: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'dynamic'
+    },
+    port: {
+      type: DataTypes.INTEGER(5),
+      allowNull: true,
+    },
+    qualify: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    keepalive: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    defaultip: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    defaultuser: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    rtptimeout: { // Terminate call if 60 seconds of no RTP or RTCP activity on the audio channel  when we're not on hold.
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rtpholdtimeout: { // Terminate call if 300 seconds of no RTP or RTCP activity on the audio channel when we're on hold (must be > rtptimeout)
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rtpkeepalive: { // Send keepalives in the RTP stream to keep NAT open (default is off - zero)
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    sendrpid: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    outboundproxy: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callbackextension: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    timert1: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    timerb: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    qualifyfreq: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    contactpermit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    contactdeny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    contactacl: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    unsolicited_mailbox: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    use_q850_reason: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    maxforwards: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    encryption: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    avpf: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    force_avp: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    icesupport: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    dtlsenable: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    dtlsverify: {
+      type: DataTypes.ENUM('yes', 'no', 'fingerprint', 'certificate'),
+      allowNull: true
+    },
+    dtlsrekey: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    dtlscertfile: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlsprivatekey: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlscipher: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlscafile: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlscapath: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlssetup: {
+      type: DataTypes.ENUM('active', 'passive', 'actpass'),
+      allowNull: true
+    },
+    dtlsfingerprint: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    usereqphone: { //This provider requires ";user=phone" on URI
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    recordonfeature: { //Feature to use when INFO with Record: on is received.
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    recordofffeature: { //Feature to use when INFO with Record: off is received.
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    call_limit: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: null
+    },
+    registertrying: { //Send a 100 Trying when the device registers.
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    subscribemwi: { //Only send notifications if this phone subscribes for mailbox notification
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    vmexten: { // dialplan extension to reach mailbox. defaults to global vmexten which defaults to "asterisk"
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    mohinterpret: { // This option specifies a preference for which music on hold class this channel should listen to when put on hold
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    mohsuggest: { //  This option specifies which music on hold class to suggest to the peer channel when this channel places the peer on hold.
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    parkinglot: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    description: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    canreinvite: {
+      type: DataTypes.ENUM('yes', 'no', 'nonat', 'update', 'update,nonat'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    registry: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    otherFields: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    }
+  }, {
+    tableName: 'trunks'
+  });
+
+  return Trunk;
+}
diff --git a/server/models/user.js b/server/models/user.js
index bfe6046..8c46445 100644
--- a/server/models/user.js
+++ b/server/models/user.js
@@ -1 +1,846 @@
\ No newline at end of file
+'use strict';
+
+var crypto = require('crypto');
+var md5 = require('md5');
+var _ = require('lodash');
+var moment = require('moment');
+var VoiceQueue = require('.').VoiceQueue;
+
+module.exports = function(sequelize, DataTypes) {
+  var User = sequelize.define('User', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      validate: {
+        notEmpty: true,
+        is: /^[A-Za-z0-9\.\_]+$/i
+      },
+      set: function(name) {
+        this.setDataValue('name', name);
+        this.setDataValue('defaultuser', name);
+      }
+    },
+    fullname: {
+      type: DataTypes.STRING,
+      allowNull: false,
+    },
+    email: {
+      type: DataTypes.STRING,
+      unique: true,
+      set: function(email) {
+        if (email) {
+          this.setDataValue('email', email.toLowerCase());
+        }
+      },
+      defaultValue: null
+    },
+    role: {
+      type: DataTypes.ENUM('admin', 'user', 'agent', 'telephone')
+    },
+    password: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      validate: {
+        notEmpty: true
+      },
+      set: function(password) {
+        this.salt = this.makeSalt();
+        this.setDataValue('password', this.encryptPassword(password));
+        this.setDataValue('md5secret', this.md5Password(this.name + ':asterisk:' + password));
+      }
+    },
+    provider: {
+      type: DataTypes.STRING,
+      defaultValue: 'local'
+    },
+    internal: {
+      type: DataTypes.INTEGER(11),
+      unique: true,
+      set: function(internal) {
+        this.setDataValue('internal', internal);
+        this.setDataValue('accountcode', internal);
+      }
+    },
+    salt: {
+      type: DataTypes.STRING
+    },
+    phone: {
+      type: DataTypes.STRING
+    },
+    mobile: {
+      type: DataTypes.STRING
+    },
+    address: {
+      type: DataTypes.STRING
+    },
+    zipcode: {
+      type: DataTypes.STRING
+    },
+    userpic: {
+      type: DataTypes.STRING
+    },
+    city: {
+      type: DataTypes.STRING
+    },
+    country: {
+      type: DataTypes.STRING
+    },
+    online: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    lastLoginAt: {
+      type: DataTypes.DATE
+    },
+    status: {
+      type: DataTypes.STRING,
+      defaultValue: 'UNKNOWN'
+    },
+    statusAt: {
+      type: DataTypes.DATE
+    },
+    voicePause: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    chatPause: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    mailPause: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    faxPause: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    pauseType: {
+      type: DataTypes.STRING,
+      defaultValue: 'Default Pause'
+    },
+    lastPauseAt: {
+      type: DataTypes.DATE
+    },
+    chatCapacity: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0
+    },
+    mailCapacity: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0
+    },
+    faxCapacity: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0
+    },
+    phoneBarAutoAnswer: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    phoneBarEnableSettings: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    phoneBarUnconditionalNumber: {
+      type: DataTypes.STRING,
+      get: function() {
+        if (this.getDataValue('phoneBarUnconditional')) {
+          return this.getDataValue('phoneBarUnconditionalNumber');
+        }
+        return null;
+      }
+    },
+    phoneBarNoReplyNumber: {
+      type: DataTypes.STRING,
+      get: function() {
+        if (this.getDataValue('phoneBarNoReply')) {
+          return this.getDataValue('phoneBarNoReplyNumber');
+        }
+        return null;
+      }
+    },
+    phoneBarBusyNumber: {
+      type: DataTypes.STRING,
+      get: function() {
+        if (this.getDataValue('phoneBarBusy')) {
+          return this.getDataValue('phoneBarBusyNumber');
+        }
+        return null;
+      }
+    },
+    phoneBarUnconditional: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    phoneBarNoReply: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    phoneBarBusy: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    phoneBarPort: {
+      type: DataTypes.INTEGER(5),
+      allowNull: true,
+    },
+    chanspy: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    description: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    ipaddr: { //REALTIME ASTERISK
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    port: { //REALTIME ASTERISK
+      type: DataTypes.INTEGER(5),
+      allowNull: true,
+    },
+    regseconds: { //REALTIME ASTERISK
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    fullcontact: { //REALTIME ASTERISK
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    regserver: { //REALTIME ASTERISK
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    useragent: { //REALTIME ASTERISK
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    lastms: { //REALTIME ASTERISK
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    type: {
+      type: DataTypes.ENUM('friend', 'user', 'peer'),
+      allowNull: true,
+      defaultValue: 'friend'
+    },
+    context: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'from-sip'
+    },
+    callingpres: {
+      type: DataTypes.ENUM('ALLOWED_NOT_SCREENED',
+        'ALLOWED_PASSED_SCREEN', 'ALLOWED_FAILED_SCREEN', 'ALLOWED',
+        'PROHIB_NOT_SCREENED', 'PROHIB_PASSED_SCREEN',
+        'PROHIB_FAILED_SCREEN', 'PROHIB'),
+      allowNull: true,
+    },
+    permit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    deny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    secret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    md5secret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    remotesecret: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    transport: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'udp'
+    },
+    dtmfmode: {
+      type: DataTypes.ENUM('rfc2833', 'info', 'shortinfo', 'inband',
+        'auto'),
+      allowNull: true,
+      defaultValue: 'rfc2833'
+    },
+    directmedia: {
+      type: DataTypes.ENUM('yes', 'no', 'nonat', 'update', 'outgoing'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    directrtpsetup: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    directmediapermit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    directmediadeny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    nat: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'force_rport,comedia'
+    },
+    callgroup: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    namedcallgroup: { //We are in named call groups engineering,sales,netgroup,protgroup
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    pickupgroup: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    namedpickupgroup: { //We can do call pick-p for named call group sales
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    language: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'en'
+    },
+    tonezone: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    allow: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'alaw;ulaw;gsm'
+    },
+    disallow: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    autoframing: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    insecure: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'port,invite'
+    },
+    trustrpid: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    trust_id_outbound: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    progressinband: {
+      type: DataTypes.ENUM('yes', 'no', 'never'),
+      allowNull: true,
+    },
+    promiscredir: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    useclientcode: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    accountcode: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    setvar: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callerid: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: '"" <>'
+    },
+    amaflags: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callcounter: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    busylevel: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    allowoverlap: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    allowsubscribe: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    allowtransfer: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    ignoresdpversion: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    subscribecontext: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    template: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    videosupport: {
+      type: DataTypes.ENUM('yes', 'no', 'always'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    maxcallbitrate: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rfc2833compensate: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    mailbox: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    session_timers: {
+      type: DataTypes.ENUM('accept', 'refuse', 'originate'),
+      allowNull: true,
+    },
+    session_expires: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    session_minse: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    session_refresher: {
+      type: DataTypes.ENUM('uac', 'uas'),
+      allowNull: true,
+      defaultValue: 'uas'
+    },
+    t38pt_usertpsource: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    regexten: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fromdomain: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    fromuser: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    host: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'dynamic'
+    },
+    qualify: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    keepalive: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    defaultip: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    defaultuser: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    rtptimeout: { // Terminate call if 60 seconds of no RTP or RTCP activity on the audio channel  when we're not on hold.
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rtpholdtimeout: { // Terminate call if 300 seconds of no RTP or RTCP activity on the audio channel when we're on hold (must be > rtptimeout)
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    rtpkeepalive: { // Send keepalives in the RTP stream to keep NAT open (default is off - zero)
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    sendrpid: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    outboundproxy: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    callbackextension: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    timert1: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    timerb: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    qualifyfreq: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    contactpermit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    contactdeny: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    contactacl: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    unsolicited_mailbox: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    use_q850_reason: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    maxforwards: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    encryption: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    avpf: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    force_avp: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    icesupport: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    dtlsenable: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true
+    },
+    dtlsverify: {
+      type: DataTypes.ENUM('yes', 'no', 'fingerprint', 'certificate'),
+      allowNull: true
+    },
+    dtlsrekey: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    dtlscertfile: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlsprivatekey: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlscipher: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlscafile: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlscapath: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    dtlssetup: {
+      type: DataTypes.ENUM('active', 'passive', 'actpass'),
+      allowNull: true
+    },
+    dtlsfingerprint: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    usereqphone: { //This provider requires ";user=phone" on URI
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    recordonfeature: { //Feature to use when INFO with Record: on is received.
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    recordofffeature: { //Feature to use when INFO with Record: off is received.
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    call_limit: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: null
+    },
+    registertrying: { //Send a 100 Trying when the device registers.
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    subscribemwi: { //Only send notifications if this phone subscribes for mailbox notification
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    vmexten: { // dialplan extension to reach mailbox. defaults to global vmexten which defaults to "asterisk"
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    mohinterpret: { // This option specifies a preference for which music on hold class this channel should listen to when put on hold
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    mohsuggest: { //  This option specifies which music on hold class to suggest to the peer channel when this channel places the peer on hold.
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    parkinglot: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    canreinvite: {
+      type: DataTypes.ENUM('yes', 'no', 'nonat', 'update', 'update,nonat'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+  }, {
+    tableName: 'users',
+    instanceMethods: {
+      /**
+       * Authenticate - check if the passwords are the same
+       *
+       * @param {String} plainText
+       *        {function} callBack
+       * @api public
+       */
+      authenticate: function(plainText) {
+        return this.encryptPassword(plainText) === this.password;
+      },
+      /**
+       * Make salt
+       *
+       * @return {String}
+       * @api public
+       */
+      makeSalt: function() {
+        return crypto.randomBytes(16).toString('base64');
+      },
+      /**
+       * Encrypt password
+       *
+       * @param {String} password
+       * @return {String}
+       * @api public
+       */
+      encryptPassword: function(password) {
+        if (!password || !this.salt) return '';
+        var salt = new Buffer(this.salt, 'base64');
+        return crypto.pbkdf2Sync(password, salt, 10000, 64).toString(
+          'base64');
+      },
+      /**
+       * md5 password
+       *
+       * @param {String} password
+       * @return {String}
+       * @api public
+       */
+      md5Password: function(password) {
+        if (!password) return '';
+        return md5(password);
+      }
+    },
+    associate: function(models) {
+      // BELOGNS TO MANY
+      User.hasMany(models.ChatMessage);
+      User.hasMany(models.MailMessage);
+      User.hasMany(models.Contact);
+      User.hasMany(models.Action);
+      User.belongsToMany(models.Module, {
+        through: 'user_has_modules'
+      });
+      User.belongsToMany(models.Channel, {
+        through: 'user_has_channels'
+      });
+      User.belongsToMany(models.MailRoom, {
+        through: 'user_has_mail_rooms'
+      });
+      User.belongsToMany(models.Team, {
+        through: models.UserHasTeam
+      });
+      User.belongsToMany(models.ChatRoom, {
+        through: models.UserHasChatRoom
+      });
+      User.belongsToMany(models.MailQueue, {
+        through: models.UserHasMailQueue,
+        required: false
+      });
+      User.belongsToMany(models.FaxQueue, {
+        through: models.UserHasFaxQueue,
+        required: false
+      });
+      User.belongsToMany(models.ChatQueue, {
+        through: models.UserHasChatQueue,
+        required: false
+      });
+      User.belongsToMany(models.VoiceQueue, {
+        through: models.UserHasVoiceQueue,
+        required: false
+      });
+      User.belongsToMany(models.Service, {
+        through: models.UserHasService
+      });
+      User.hasMany(models.VoiceExtension, {
+        foreignKey: 'UserId',
+        as: 'UserExtensions',
+        onDelete: 'cascade',
+        hooks: true
+      });
+
+      // SCOPES MANAGEMENT
+      User.addScope('user', {
+        where: {
+          role: {
+            $in: ['admin', 'user']
+          },
+        }
+      });
+      User.addScope('queues', {
+        include: [models.VoiceQueue, models.ChatQueue, models.MailQueue, models.FaxQueue]
+      });
+      User.addScope('telephone', {
+        where: {
+          role: 'telephone'
+        }
+      });
+      User.addScope('checkPauseStatus', function(query) {
+        var scope = {
+          where: {}
+        };
+        if (query.voicePause) {
+          scope.where.voicePause = (query.voicePause === 'true') ? true : false;
+          delete query.voicePause;
+        } else if (query.faxPause) {
+          scope.where.faxPause = (query.faxPause === 'true') ? true : false;
+          delete query.faxPause;
+        } else if (query.chatPause) {
+          scope.where.chatPause = (query.chatPause === 'true') ? true : false;
+          delete query.chatPause;
+        } else if (query.mailPause) {
+          scope.where.mailPause = (query.mailPause === 'true') ? true : false;
+          delete query.mailPause;
+        }
+        return scope;
+      });
+      User.addScope('checkOnlineStatus', function(query) {
+        var scope = {
+          where: {}
+        };
+        if (query.online) {
+          scope.where.online = (query.online === 'true') ? true : false;
+          delete query.online;
+        }
+        return scope;
+      });
+      User.addScope('checkSipStatus', function(query) {
+        var scope = {
+          where: {}
+        };
+        if (query.status) {
+          scope.where.status = query.status;
+          delete query.status;
+        }
+        return scope;
+      });
+      User.addScope('agent', {
+        where: {
+          role: 'agent'
+        },
+        attributes: ['id',
+          'name',
+          'email',
+          'internal',
+          'fullname',
+          'accountcode',
+          'transport',
+          'host',
+          'nat',
+          'type',
+          'allow',
+          'chatCapacity',
+          'mailCapacity',
+          'faxCapacity',
+          'online',
+          'lastLoginAt',
+          'phoneBarAutoAnswer',
+          'phoneBarEnableSettings',
+          'phoneBarUnconditional',
+          'phoneBarNoReply',
+          'phoneBarBusy',
+          'phoneBarUnconditionalNumber',
+          'phoneBarNoReplyNumber',
+          'phoneBarBusyNumber',
+          'chanspy',
+          'voicePause',
+          'mailPause',
+          'faxPause',
+          'chatPause',
+          'pauseType',
+          'lastPauseAt',
+          'status',
+          'statusAt',
+          'useragent',
+          'ipaddr',
+          'fullcontact',
+          'port',
+          'lastms',
+          'description'
+        ]
+      });
+    }
+  });
+
+  return User;
+}
diff --git a/server/models/user_has_chat_queue.js b/server/models/user_has_chat_queue.js
index 94e42d9..6479a3d 100644
--- a/server/models/user_has_chat_queue.js
+++ b/server/models/user_has_chat_queue.js
@@ -1 +1,22 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var UserHasChatQueue = sequelize.define('UserHasChatQueue', {
+		penalty: {
+			type: DataTypes.INTEGER,
+			defaultValue: 0
+		},
+		queue: {
+			type: DataTypes.STRING
+		},
+	}, {
+		tableName: 'user_has_chat_queues'
+	});
+
+	return UserHasChatQueue;
+};
diff --git a/server/models/user_has_chat_room.js b/server/models/user_has_chat_room.js
index d805255..277debd 100644
--- a/server/models/user_has_chat_room.js
+++ b/server/models/user_has_chat_room.js
@@ -1 +1,19 @@
\ No newline at end of file
+
+/**
+	* Chat Website Model
+	*/
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var UserHasChatRoom = sequelize.define('UserHasChatRoom', {
+			LastMessageRead: { type: DataTypes.INTEGER, defaultValue: 0 },
+			Hide: { type: DataTypes.BOOLEAN, defaultValue: false }
+		}, {
+			tableName: 'user_has_chat_rooms'
+		}
+	);
+
+	return UserHasChatRoom;
+};
diff --git a/server/models/user_has_fax_queue.js b/server/models/user_has_fax_queue.js
index 35d679b..3bcb67d 100644
--- a/server/models/user_has_fax_queue.js
+++ b/server/models/user_has_fax_queue.js
@@ -1 +1,22 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var UserHasFaxQueue = sequelize.define('UserHasFaxQueue', {
+		penalty: {
+			type: DataTypes.INTEGER,
+			defaultValue: 0
+		},
+		queue: {
+			type: DataTypes.STRING
+		}
+	}, {
+		tableName: 'user_has_fax_queues'
+	});
+
+	return UserHasFaxQueue;
+};
diff --git a/server/models/user_has_mail_queue.js b/server/models/user_has_mail_queue.js
index 690c5ad..c6358cb 100644
--- a/server/models/user_has_mail_queue.js
+++ b/server/models/user_has_mail_queue.js
@@ -1 +1,22 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var UserHasMailQueue = sequelize.define('UserHasMailQueue', {
+		penalty: {
+			type: DataTypes.INTEGER,
+			defaultValue: 0
+		},
+		queue: {
+			type: DataTypes.STRING
+		}
+	}, {
+		tableName: 'user_has_mail_queues'
+	});
+
+	return UserHasMailQueue;
+};
diff --git a/server/models/user_has_service.js b/server/models/user_has_service.js
index af555a2..a3ff23d 100644
--- a/server/models/user_has_service.js
+++ b/server/models/user_has_service.js
@@ -1 +1,21 @@
\ No newline at end of file
+/**
+ * 
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+	var UserHasService = sequelize.define('UserHasService', {
+		membername: {
+			type: DataTypes.STRING
+		}
+	}, {
+		tableName: 'user_has_services',
+		indexes: [{
+			name: 'membername',
+			fields: ['membername']
+		}]
+	});
+
+	return UserHasService;
+};
diff --git a/server/models/user_has_team.js b/server/models/user_has_team.js
index 7ae191e..a0c38b6 100644
--- a/server/models/user_has_team.js
+++ b/server/models/user_has_team.js
@@ -1 +1,14 @@
-var _0xc1fb=["\x63\x72\x79\x70\x74\x6F","\x65\x78\x70\x6F\x72\x74\x73","\x55\x73\x65\x72\x48\x61\x73\x54\x65\x61\x6D","\x75\x73\x65\x72\x5F\x68\x61\x73\x5F\x74\x65\x61\x6D\x73","\x64\x65\x66\x69\x6E\x65"];var crypto=require(_0xc1fb[0]);module[_0xc1fb[1]]=function(_0x5570x2,_0x5570x3){var _0x5570x4=_0x5570x2[_0xc1fb[4]](_0xc1fb[2],{},{tableName:_0xc1fb[3]});return _0x5570x4}
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+
+	var UserHasTeam = sequelize.define('UserHasTeam', {}, {
+		tableName: 'user_has_teams'
+	});
+
+	return UserHasTeam;
+};
diff --git a/server/models/user_has_voice_queue.js b/server/models/user_has_voice_queue.js
index 45caf6a..24791d4 100644
--- a/server/models/user_has_voice_queue.js
+++ b/server/models/user_has_voice_queue.js
@@ -1 +1,117 @@
\ No newline at end of file
+/**
+ * Chat Website Model
+ */
+
+var crypto = require('crypto');
+
+module.exports = function(sequelize, DataTypes) {
+	var UserHasVoiceQueue = sequelize.define('UserHasVoiceQueue', {
+		logged: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false
+		},
+		loggedAt: {
+			type: DataTypes.DATE
+		},
+		ringinuse: {
+			type: DataTypes.BOOLEAN
+		},
+		penalty: {
+			type: DataTypes.INTEGER,
+			defaultValue: 0
+		},
+		stateinterface: {
+			type: DataTypes.STRING
+		},
+		queue: {
+			type: DataTypes.STRING
+		},
+		membername: {
+			type: DataTypes.STRING
+		},
+		interface: {
+			type: DataTypes.STRING
+		},
+		membership: {
+			type: DataTypes.STRING
+		},
+		callstaken: {
+			type: DataTypes.INTEGER
+		},
+		lastcall: {
+			type: DataTypes.DATE,
+			set: function(lastcall) {
+				if (lastcall === '0') {
+					this.setDataValue('lastcall', null);
+				} else {
+					this.setDataValue('lastcall', sequelize.fn('FROM_UNIXTIME', lastcall, '%Y-%m-%d %H:%i:%s'));
+				}
+			}
+		},
+		status: {
+			type: DataTypes.INTEGER,
+			set: function(status) {
+				this.setDataValue('status', status);
+				var detail = 'UNKNOWN';
+				switch (status) {
+					case '0':
+						detail = 'UNKNOWN';
+						break;
+					case '1':
+						detail = 'NOT_INUSE';
+						break;
+					case '2':
+						detail = 'INUSE';
+						break;
+					case '3':
+						detail = 'BUSY';
+						break;
+					case '4':
+						detail = 'INVALID';
+						break;
+					case '5':
+						detail = 'UNAVAILABLE';
+						break;
+					case '6':
+						detail = 'RINGING';
+						break;
+					case '7':
+						detail = 'RINGINUSE';
+						break;
+					case '8':
+						detail = 'ONHOLD';
+						break;
+				}
+				this.setDataValue('statusdesc', detail);
+				this.setDataValue('statusAt', sequelize.fn('NOW'))
+			},
+		},
+		statusdesc: {
+			type: DataTypes.STRING
+		},
+		statusAt: {
+			type: DataTypes.DATE
+		},
+		paused: {
+			type: DataTypes.BOOLEAN,
+			defaultValue: false,
+			set: function(paused) {
+				this.setDataValue('paused', parseInt(paused, 10) ? true : false);
+			}
+		},
+		pausedAt: {
+			type: DataTypes.DATE
+		},
+		reason: {
+			type: DataTypes.STRING
+		}
+	}, {
+		tableName: 'user_has_voice_queues',
+		indexes: [{
+			name: 'membername',
+			fields: ['membername']
+		}]
+	});
+
+	return UserHasVoiceQueue;
+};
diff --git a/server/models/variable.js b/server/models/variable.js
index cab796d..68778bc 100644
--- a/server/models/variable.js
+++ b/server/models/variable.js
@@ -1 +1,33 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var Variable = sequelize.define('Variable', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true,
+      allowNull: false,
+      validate: {
+        notEmpty: true,
+      },
+    },
+    description: DataTypes.STRING
+  }, {
+    tableName: 'variables',
+    associate: function(models) {
+      Variable.hasMany(models.ZendeskField);
+      Variable.hasMany(models.DeskField);
+      Variable.hasMany(models.SalesforceField);
+      Variable.hasMany(models.FreshdeskField);
+      Variable.hasMany(models.SugarcrmField);
+      Variable.addScope('fields', {
+        include: [{
+          all: true
+        }]
+      });
+    }
+  });
+
+  return Variable;
+
+};
diff --git a/server/models/voice_context.js b/server/models/voice_context.js
index 617c1f8..e81e9ab 100644
--- a/server/models/voice_context.js
+++ b/server/models/voice_context.js
@@ -1 +1,32 @@
\ No newline at end of file
+/**
+ * Context Model
+ */
+
+module.exports = function(sequelize, DataTypes) {
+
+  var VoiceContext = sequelize.define('VoiceContext', {
+    name: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    description: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    defaultEntry: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: 0
+    }
+  }, {
+    tableName: 'voice_contexts',
+    associate: function(models) {
+      VoiceContext.hasOne(models.VoiceExtension, {
+        onDelete: 'cascade',
+        hooks: true
+      });
+    }
+
+  });
+
+  return VoiceContext;
+};
diff --git a/server/models/voice_extension.js b/server/models/voice_extension.js
index 96e154e..6d42a6e 100644
--- a/server/models/voice_extension.js
+++ b/server/models/voice_extension.js
@@ -1 +1,112 @@
\ No newline at end of file
+/* jshint indent: 2 */
+var util = require('util');
+var reverseMustache = require('reverse-mustache');
+
+module.exports = function(sequelize, DataTypes) {
+  var VoiceExtension = sequelize.define('VoiceExtension', {
+    context: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: 'compositeIndex',
+      validate: {
+        notEmpty: true,
+        is: /[A-Za-z0-9\.\_]+$/i
+      },
+    },
+    exten: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: 'compositeIndex'
+    },
+    priority: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      defaultValue: '1',
+      unique: 'compositeIndex'
+    },
+    tag: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      defaultValue: '--'
+    },
+    app: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      defaultValue: 'NoOp'
+    },
+    appdata: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      get: function() {
+        // 'this' allows you to access attributes of the instance
+        if (!this.getDataValue('id')) {
+          if (this.getDataValue('app') === 'Dial' && this.getDataValue('type') === 'outbound') {
+            var appdata = reverseMustache({
+              template: 'SIP/{{trunk}}/{{prefix}}${EXTEN:{{cutdigits}}}',
+              content: this.getDataValue('appdata')
+            });
+            return [appdata.trunk, this.getDataValue('callerID') || '', appdata.cutdigits, appdata.prefix];
+          } else {
+            return this.getDataValue('appdata').split(',');
+          }
+        } else {
+          return this.getDataValue('appdata');
+        }
+      },
+    },
+    type: {
+      type: DataTypes.ENUM('inbound', 'outbound', 'internal', 'inbound-fax', 'outbound-fax', 'system', 'any'),
+      allowNull: false,
+    },
+    description: {
+      type: DataTypes.STRING,
+    },
+    interval: {
+      type: DataTypes.STRING,
+    },
+    IntervalId: {
+      type: DataTypes.INTEGER,
+    },
+    isApp: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    },
+    callerID: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    record: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: false
+    }
+  }, {
+    tableName: 'voice_extensions',
+    associate: function(models) {
+      VoiceExtension.hasMany(models.VoiceExtension, {
+        as: 'Applications',
+        onDelete: 'cascade',
+        hooks: true
+      });
+      // SCOPES MANAGEMENT
+      VoiceExtension.addScope('application', {
+        include: [{
+          model: models.VoiceExtension,
+          as: 'Applications',
+          required: false,
+          where: {
+            isApp: true
+          },
+          attributes: ['app', 'interval', 'IntervalId', 'callerID', 'appdata', 'type']
+        }]
+      });
+
+      VoiceExtension.addScope('routes', {
+        where: {
+          VoiceExtensionId: null
+        }
+      });
+    }
+  });
+
+  return VoiceExtension;
+};
diff --git a/server/models/voice_musiconhold.js b/server/models/voice_musiconhold.js
index 35999cb..c405ca3 100644
--- a/server/models/voice_musiconhold.js
+++ b/server/models/voice_musiconhold.js
@@ -1 +1,71 @@
\ No newline at end of file
+/* jshint indent: 2 */
+
+module.exports = function(sequelize, DataTypes) {
+  var VoiceMusicOnHold = sequelize.define('VoiceMusicOnHold', {
+    name: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: true
+    },
+    mode: {
+      type: DataTypes.ENUM('custom', 'files', 'mp3nb', 'quietmp3nb', 'quietmp3', 'mp3'),
+      allowNull: true,
+      defaultValue: 'files'
+    },
+    directory: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    application: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    digit: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    sort: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'alpha'
+    },
+    format: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'wav'
+    },
+    stamp: {
+      type: 'TIMESTAMP'
+    },
+    defaultEntry: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: 0
+    }
+  }, {
+    tableName: 'voice_musiconhold'
+  });
+
+  VoiceMusicOnHold.beforeUpdate(function(moh) {
+    if (moh.changed('name')) {
+      throw new Error({
+        message: "You can't modify a moh class name"
+      });
+    }
+    if (moh.defaultEntry) {
+      throw new Error({
+        message: "You can't modify a default moh class"
+      });
+    }
+  });
+
+
+  VoiceMusicOnHold.beforeDelete(function(moh) {
+    if (moh.defaultEntry) {
+      throw new Error({
+        message: "You can't delete a default moh class"
+      });
+    }
+  });
+
+  return VoiceMusicOnHold;
+};
diff --git a/server/models/voice_queue.js b/server/models/voice_queue.js
index 80f405b..71d35c8 100644
--- a/server/models/voice_queue.js
+++ b/server/models/voice_queue.js
@@ -1 +1,305 @@
\ No newline at end of file
+/* jshint indent: 2 */
+
+module.exports = function(sequelize, DataTypes) {
+  var VoiceQueue = sequelize.define('VoiceQueue', {
+    name: {
+      type: DataTypes.STRING,
+      primaryKey: true,
+      unique: true,
+      validate: {
+        notEmpty: true,
+        is: /^[A-Za-z0-9\.\_]+$/i
+      },
+    },
+    musiconhold: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'default'
+    },
+    announce: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: ''
+    },
+    strategy: {
+      type: DataTypes.ENUM('ringall', 'leastrecent', 'fewestcalls', 'random', 'rrmemory', 'linear', 'wrandom', 'rrordered'),
+      allowNull: false,
+      defaultValue: 'ringall'
+    },
+    servicelevel: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    context: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    penaltymemberslimit: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    timeout: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    retry: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    timeoutpriority: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    weight: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    wrapuptime: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    autofill: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    autopause: {
+      type: DataTypes.ENUM('yes', 'no', 'all'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    autopausedelay: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    autopausebusy: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    autopauseunavail: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    maxlen: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    setinterfacevar: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    setqueueentryvar: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defautValue: 'no'
+    },
+    setqueuevar: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    membermacro: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    membergosub: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    announce_frequency: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    min_announce_frequency: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    periodic_announce_frequency: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    random_periodic_announce: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    relative_periodic_announce: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    announce_holdtime: {
+      type: DataTypes.ENUM('yes', 'no', 'once'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    announce_position: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    announce_to_first_user: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+    },
+    announce_position_limit: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+    },
+    announce_round_seconds: {
+      type: DataTypes.ENUM('0', '5', '10', '15', '20', '30'),
+      allowNull: true,
+      defaultValue: '0'
+    },
+    monitor_format: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: ''
+    },
+    monitor_type: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    queue_youarenext: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_thereare: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_callswaiting: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_holdtime: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_minute: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_minutes: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_seconds: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_thankyou: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: null
+    },
+    queue_reporthold: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    queue_quantity1: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    queue_quantity2: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    queue_periodic_announce: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    queue_less_than: {
+      type: DataTypes.STRING,
+      allowNull: true
+    },
+    periodic_announce: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: ''
+    },
+    joinempty: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'yes'
+    },
+    leavewhenempty: {
+      type: DataTypes.STRING,
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    reportholdtime: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    ringinuse: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    memberdelay: {
+      type: DataTypes.INTEGER(11),
+      allowNull: true,
+      defaultValue: 0
+    },
+    timeoutrestart: {
+      type: DataTypes.ENUM('yes', 'no'),
+      allowNull: true,
+      defaultValue: 'no'
+    },
+    defaultrule: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    description: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    acw: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: 0
+    },
+    acwTimeout: {
+      type: DataTypes.INTEGER(11),
+      defaultValue: 10
+    }
+  }, {
+    tableName: 'voice_queues',
+    associate: function(models) {
+      VoiceQueue.belongsToMany(models.User, {
+        through: models.UserHasVoiceQueue,
+        foreignKey: 'queue',
+        required: false
+      });
+    }
+  });
+
+  VoiceQueue.beforeUpdate(function(updatedQueue) {
+    if (updatedQueue.changed('name')) {
+      throw new Error("You can't modify a queue name");
+    }
+  });
+
+  return VoiceQueue;
+};
diff --git a/server/models/voice_recording.js b/server/models/voice_recording.js
index 8df02f2..736aa24 100644
--- a/server/models/voice_recording.js
+++ b/server/models/voice_recording.js
@@ -1 +1,75 @@
\ No newline at end of file
+/* jshint indent: 2 */
+var moment = require('moment');
+
+module.exports = function(sequelize, DataTypes) {
+  var VoiceRecording = sequelize.define('VoiceRecording', {
+    uniqueid: {
+      type: DataTypes.STRING,
+      primaryKey: true
+    },
+    channel: {
+      type: DataTypes.STRING
+    },
+    calleridnum: {
+      type: DataTypes.STRING
+    },
+    calleridname: {
+      type: DataTypes.STRING
+    },
+    connectedlinenum: {
+      type: DataTypes.STRING
+    },
+    connectedlinename: {
+      type: DataTypes.STRING
+    },
+    language: {
+      type: DataTypes.STRING
+    },
+    accountcode: {
+      type: DataTypes.STRING
+    },
+    context: {
+      type: DataTypes.STRING
+    },
+    exten: {
+      type: DataTypes.STRING
+    },
+    value: {
+      type: DataTypes.STRING
+    },
+    type: {
+      type: DataTypes.STRING
+    },
+    rating: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0,
+      validate: {
+        max: 5,
+        min: 0
+      }
+    },
+    createdAt: {
+      type: DataTypes.DATE,
+      get: function() {
+        // 'this' allows you to access attributes of the instance
+        return moment(this.getDataValue('agentconnectAt')).format("MM-DD-YYYY HH:mm");
+      }
+    },
+    updatedAt: {
+      type: DataTypes.DATE,
+      get: function() {
+        // 'this' allows you to access attributes of the instance
+        return moment(this.getDataValue('agentconnectAt')).format("MM-DD-YYYY HH:mm");
+      }
+    }
+  }, {
+    tableName: 'voice_recordings',
+    associate: function(models) {
+      // VoiceRecording.belongsTo(models.ReportCall, {
+      //   foreignKey: 'uniqueid'
+      // });
+    }
+  });
+
+  return VoiceRecording;
+};
diff --git a/server/models/voice_voicemail.js b/server/models/voice_voicemail.js
index 1de62e3..f814f4b 100644
--- a/server/models/voice_voicemail.js
+++ b/server/models/voice_voicemail.js
@@ -1 +1,144 @@
\ No newline at end of file
+/* jshint indent: 2 */
+
+module.exports = function(sequelize, DataTypes) {
+  var VoiceVoicemail = sequelize.define('VoiceVoicemail', {
+    uniqueid: {
+      type: DataTypes.INTEGER(4),
+      primaryKey: true,
+      allowNull: false,
+      autoIncrement: true
+    },
+    customer_id: {
+      type: DataTypes.STRING,
+    },
+    context: {
+      type: DataTypes.STRING,
+      defaultValue: 'from-voicemail',
+      unique: 'contest_mailbox'
+    },
+    mailbox: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+      allowNull: false,
+      unique: 'contest_mailbox'
+    },
+    password: {
+      type: DataTypes.STRING,
+    },
+    fullname: {
+      type: DataTypes.STRING,
+    },
+    email: {
+      type: DataTypes.STRING,
+    },
+    pager: {
+      type: DataTypes.STRING,
+    },
+    tz: {
+      type: DataTypes.STRING,
+      defaultValue: 'central'
+    },
+    attach: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'yes',
+      allowNull: false
+    },
+    saycid: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'yes',
+      allowNull: false
+    },
+    dialout: {
+      type: DataTypes.STRING,
+    },
+    callback: {
+      type: DataTypes.STRING,
+    },
+    review: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    operator: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    envelope: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    sayduration: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    saydurationm: {
+      type: 'TINYINT(4)',
+      defaultValue: 1,
+      allowNull: false
+    },
+    sendvoicemail: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    'delete': {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    nextaftercmd: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'yes',
+      allowNull: false
+    },
+    forcename: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    forcegreetings: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'no',
+      allowNull: false
+    },
+    hidefromdir: {
+      type: DataTypes.ENUM('yes', 'no'),
+      defaultValue: 'yes',
+      allowNull: false
+    },
+    stamp: {
+      type: 'TIMESTAMP'
+    },
+    emailsubject: {
+      type: DataTypes.STRING,
+    },
+    emailbody: {
+      type: DataTypes.STRING,
+    },
+    name: {
+      type: DataTypes.STRING,
+    },
+    maxsecs: {
+      type: DataTypes.INTEGER(255),
+      defaultValue: 180,
+      allowNull: false
+    },
+    maxmsg: {
+      type: DataTypes.INTEGER(255),
+      defaultValue: 100,
+      allowNull: false
+    }
+
+  }, {
+    tableName: 'voice_voicemail',
+    indexes: [{
+      name: 'mailbox_context',
+      fields: ['mailbox', 'context']
+    }]
+  });
+
+  return VoiceVoicemail;
+};
diff --git a/server/models/voice_voicemail_messages.js b/server/models/voice_voicemail_messages.js
index 89e0ae6..fbfc691 100644
--- a/server/models/voice_voicemail_messages.js
+++ b/server/models/voice_voicemail_messages.js
@@ -1 +1,83 @@
\ No newline at end of file
+/* jshint indent: 2 */
+
+module.exports = function(sequelize, DataTypes) {
+  var VoiceVoicemailMessages = sequelize.define('VoiceVoicemailMessages', {
+    msgnum: {
+      type: DataTypes.INTEGER(11),
+      defaultValue: 0,
+      allowNull: false
+    },
+    dir: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    context: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    macrocontext: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    callerid: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    origtime: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    duration: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    mailboxuser: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    mailboxcontext: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    recording: {
+      type: DataTypes.BLOB('long'),
+      defaultValue: null
+    },
+    flag: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    msg_id: {
+      type: DataTypes.STRING,
+      defaultValue: ''
+    },
+    stamp: {
+      type: 'TIMESTAMP',
+      allowNull: false
+    }
+  }, {
+    tableName: 'voice_voicemail_messages',
+    associate: function(models) {
+      VoiceVoicemailMessages.addScope('mailbox', function(query) {
+        var scope = {
+          where: {}
+        };
+        if (query.context && query.mailbox) {
+          where: {
+            scope.where.mailboxcontext = query.context;
+            scope.where.mailboxuser = query.mailbox;
+          }
+          delete query.context;
+          delete query.mailbox;
+        }
+        return scope;
+      });
+    },
+    indexes: [{
+      name: 'dir',
+      fields: ['dir']
+    }]
+  });
+
+  return VoiceVoicemailMessages;
+};
diff --git a/server/models/zendesk_account.js b/server/models/zendesk_account.js
index 93b5851..899cf38 100644
--- a/server/models/zendesk_account.js
+++ b/server/models/zendesk_account.js
@@ -1 +1,42 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ZendeskAccount = sequelize.define('ZendeskAccount', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING,
+    username: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    password: DataTypes.STRING,
+    token: DataTypes.STRING,
+    remoteUri: {
+      type: DataTypes.STRING,
+      unique: true
+    },
+    authType: {
+      type: DataTypes.ENUM('password', 'token'),
+      defaultValue: 'password'
+    }
+  }, {
+    tableName: 'zendesk_accounts',
+    associate: function(models) {
+      ZendeskAccount.hasMany(models.ZendeskConfiguration, {
+        as: 'Configurations',
+        foreignKey: 'AccountId'
+      });
+      ZendeskAccount.addScope('configurations', {
+        include: [{
+          model: models.ZendeskConfiguration,
+          as: 'Configurations',
+          required: false,
+          attributes: ['id', 'name']
+        }]
+      });
+    }
+  });
+
+  return ZendeskAccount;
+
+};
diff --git a/server/models/zendesk_configuration.js b/server/models/zendesk_configuration.js
index 5d26e27..9363273 100644
--- a/server/models/zendesk_configuration.js
+++ b/server/models/zendesk_configuration.js
@@ -1 +1,44 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ZendeskConfiguration = sequelize.define('ZendeskConfiguration', {
+    name: DataTypes.STRING,
+    description: DataTypes.STRING
+  }, {
+    tableName: 'zendesk_configurations',
+    associate: function(models) {
+      ZendeskConfiguration.belongsTo(models.ZendeskAccount, {
+        foreignKey: 'AccountId'
+      });
+      ZendeskConfiguration.addScope('account', function(AccountId) {
+        return {
+          where: {
+            AccountId: AccountId
+          },
+          include: [{
+            all: true
+          }]
+        }
+      });
+      ZendeskConfiguration.hasMany(models.ZendeskField, {
+        as: 'Subject',
+        foreignKey: 'SubjectId'
+      });
+      ZendeskConfiguration.hasMany(models.ZendeskField, {
+        as: 'Description',
+        foreignKey: 'DescriptionId'
+      });
+      ZendeskConfiguration.hasMany(models.ZendeskField, {
+        as: 'Field',
+        foreignKey: 'FieldId'
+      });
+      ZendeskConfiguration.belongsToMany(models.Tag, {
+        through: 'zendesk_configuration_has_tags'
+      });
+    }
+  });
+
+  return ZendeskConfiguration;
+
+};
diff --git a/server/models/zendesk_field.js b/server/models/zendesk_field.js
index 2a24ea8..278c7fa 100644
--- a/server/models/zendesk_field.js
+++ b/server/models/zendesk_field.js
@@ -1 +1,32 @@
\ No newline at end of file
+'use strict';
+
+module.exports = function(sequelize, DataTypes) {
+
+  var ZendeskField = sequelize.define('ZendeskField', {
+    type: {
+      type: DataTypes.ENUM('string', 'variable', 'customVariable', 'keyValue'),
+      defaultValue: 'string'
+    },
+    content: DataTypes.STRING,
+    key: DataTypes.STRING,
+    keyType: {
+      type: DataTypes.ENUM('string', 'variable', 'customVariable')
+    },
+    keyContent: DataTypes.STRING,
+    idField: DataTypes.INTEGER,
+    nameField: DataTypes.STRING,
+    customField: {
+      type: DataTypes.BOOLEAN,
+      defaultValue: true
+    },
+    variableName: DataTypes.STRING
+  }, {
+    tableName: 'zendesk_fields',
+    associate: function(models) {
+      ZendeskField.belongsTo(models.Variable);
+    }
+  });
+
+  return ZendeskField;
+
+};
diff --git a/server/routes.js b/server/routes.js
index b2b3373..2b92984 100644
--- a/server/routes.js
+++ b/server/routes.js
@@ -1 +1,149 @@
\ No newline at end of file
+/**
+ * Main application routes
+ */
+
+'use strict';
+
+var config = require('./config/environment');
+var errors = require('./components/errors');
+var path = require('path');
+var moment = require('moment');
+
+module.exports = function(app) {
+  var env = app.get('env');
+
+  // Insert routes below
+  app.use('/api/chat/proactives', require('./api/chat_proactive_action'));
+
+  app.use('/api/chat/websites/fields', require('./api/chat_websites_field'));
+  app.use('/api/answers', require('./api/canned_answer'));
+  app.use('/api/actions', require('./api/action'));
+  app.use('/api/pauses', require('./api/pause'));
+  app.use('/api/voice/recordings', require('./api/voice_recording'));
+  app.use('/api/jscripty/projects', require('./api/jscripty_project'));
+  app.use('/api/telephones', require('./api/telephone'));
+  app.use('/api/licenses', require('./api/license'));
+  app.use('/api/sugarcrm/fields', require('./api/sugarcrm_field'));
+  app.use('/api/sugarcrm/configurations', require('./api/sugarcrm_configuration'));
+  app.use('/api/sugarcrm/accounts', require('./api/sugarcrm_account'));
+  app.use('/api/freshdesk/fields', require('./api/freshdesk_field'));
+  app.use('/api/freshdesk/configurations', require('./api/freshdesk_configuration'));
+  app.use('/api/freshdesk/accounts', require('./api/freshdesk_account'));
+  app.use('/api/salesforce/fields', require('./api/salesforce_field'));
+  app.use('/api/salesforce/configurations', require('./api/salesforce_configuration'));
+  app.use('/api/salesforce/accounts', require('./api/salesforce_account'));
+  app.use('/api/desk/fields', require('./api/desk_field'));
+  app.use('/api/desk/configurations', require('./api/desk_configuration'));
+  app.use('/api/desk/accounts', require('./api/desk_account'));
+  app.use('/api/analytics/reports/default', require('./api/default_report'));
+  app.use('/api/analytics/reports/custom', require('./api/custom_report'));
+  app.use('/api/analytics/reports/trees', require('./api/report_tree'));
+  app.use('/api/analytics/metrics', require('./api/metric'));
+  app.use('/api/analytics/extracted', require('./api/extracted_report'));
+  app.use('/api/updates', require('./api/update'));
+  app.use('/api/report/chat/sessions', require('./api/report_chat_session'));
+  app.use('/api/zendesk/fields', require('./api/zendesk_field'));
+  app.use('/api/report/chats', require('./api/report_chat'));
+  app.use('/api/zendesk/configurations', require('./api/zendesk_configuration'));
+  app.use('/api/motionbar', require('./api/motionbar'));
+  app.use('/api/report/mail/sessions', require('./api/report_mail_session'));
+  app.use('/api/report/mails', require('./api/report_mail'));
+  app.use('/api/widgets', require('./api/widget'));
+  app.use('/api/zendesk/accounts', require('./api/zendesk_account'));
+  app.use('/api/report/queues', require('./api/report_queue'));
+  app.use('/api/report/integrations', require('./api/report_integration'));
+  app.use('/api/report/agents', require('./api/report_agent'));
+  app.use('/api/dashboards', require('./api/dashboard'));
+  app.use('/api/settings', require('./api/setting'));
+  app.use('/api/triggers', require('./api/trigger'));
+  app.use('/api/automations', require('./api/automation'));
+  app.use('/api/mail/templates', require('./api/mail_template'));
+  app.use('/api/ami', require('./api/ami'));
+  app.use('/api/integrations', require('./api/integration'));
+  app.use('/api/voice/musiconholds', require('./api/voice_musiconhold'));
+  app.use('/api/voice/voicemails/messages', require('./api/voice_voicemail_messages'));
+  app.use('/api/voice/voicemails', require('./api/voice_voicemail'));
+  app.use('/api/contactmanager/companies', require('./api/company'));
+  app.use('/api/contactmanager/services', require('./api/service'));
+  app.use('/api/contactmanager/user_has_services', require('./api/user_has_service'));
+  app.use('/api/contactmanager/contacts', require('./api/contact_manager'));
+  app.use('/api/intervals', require('./api/interval'));
+  app.use('/api/voice/extensions', require('./api/voice_extension'));
+  app.use('/api/voice/contexts', require('./api/voice_context'));
+  app.use('/api/voice/user_has_queues', require('./api/user_has_voice_queue'));
+  app.use('/api/voice/queues', require('./api/voice_queue'));
+  app.use('/api/trunks', require('./api/trunk'));
+  app.use('/api/chat/applications', require('./api/chat_application'));
+  app.use('/api/chat/user_has_queues', require('./api/user_has_chat_queue'));
+  app.use('/api/square/odbc', require('./api/square_odbc'));
+  app.use('/api/fax/applications', require('./api/fax_application'));
+  app.use('/api/fax/user_has_queues', require('./api/user_has_fax_queue'));
+  app.use('/api/fax/queues', require('./api/fax_queue'));
+  app.use('/api/mail/attachments', require('./api/mail_attachment'));
+  app.use('/api/user_has_teams', require('./api/user_has_team'));
+  app.use('/api/variables', require('./api/variable'));
+  app.use('/api/chanspy', require('./api/chanspy'));
+  app.use('/api/tags', require('./api/tag'));
+  app.use('/api/square/projects', require('./api/square_project'));
+  app.use('/api/chat/queues', require('./api/chat_queue'));
+  app.use('/api/fax/messages', require('./api/fax_message'));
+  app.use('/api/fax/rooms', require('./api/fax_room'));
+  app.use('/api/fax/accounts', require('./api/fax_account'));
+  app.use('/api/agents', require('./api/agent'));
+  app.use('/api/chat/enquiries', require('./api/chat_enquiry'));
+  app.use('/api/chat/messages', require('./api/chat_message'));
+  app.use('/api/chat/rooms', require('./api/chat_room'));
+  app.use('/api/chat/visitors', require('./api/chat_visitor'));
+  app.use('/api/chat/websites', require('./api/chat_website'));
+  app.use('/api/chat/queues', require('./api/chat_queue'));
+  app.use('/api/mail/accounts', require('./api/mail_account'));
+  app.use('/api/mail/messages', require('./api/mail_message'));
+  app.use('/api/mail/rooms', require('./api/mail_room'));
+  app.use('/api/mail/servers/in', require('./api/mail_server_in'));
+  app.use('/api/mail/servers/out', require('./api/mail_server_out'));
+  app.use('/api/mail/queues', require('./api/mail_queue'));
+  app.use('/api/mail/user_has_queues', require('./api/user_has_mail_queue'));
+  app.use('/api/mail/applications', require('./api/mail_application'));
+  app.use('/api/teams', require('./api/team'));
+  app.use('/api/sounds', require('./api/sound'));
+  app.use('/api/users', require('./api/user'));
+  app.use('/api/chat/user_has_rooms', require('./api/user_has_chat_room'));
+  app.use('/api/xchatty', require('./api/xchatty'));
+  app.use('/auth', require('./auth'));
+
+  // All undefined asset or api routes should return a 404
+  app.route('/:url(api|auth|components|app|bower_components|assets)/*').get(
+    errors[404]);
+
+  // All other routes should redirect to the index.html
+  app.all("/*", function(req, res, next) {
+
+    var options = {};
+
+    if ('production' === env) {
+      options.root = path.join(config.root, 'public');
+    }
+
+    if ('development' === env || 'test' === env) {
+      options.root = path.join(config.root, 'client');
+    }
+
+    res.sendFile('index.html', options);
+  });
+
+  // app
+  //   .route('/*')
+  //   .get(function(req, res) {
+  //
+  //     var options = {
+  //       root: config.root,
+  //       dotfiles: 'deny',
+  //       headers: {
+  //         'x-timestamp': Date.now(),
+  //         'x-sent': true
+  //       }
+  //     };
+  //
+  //     res.sendFile('index.html', options);
+  //   });
+};
diff --git a/server/utils/report_build.js b/server/utils/report_build.js
index 52c14d8..5a75ac6 100644
--- a/server/utils/report_build.js
+++ b/server/utils/report_build.js
@@ -1 +1,87 @@
\ No newline at end of file
+var _ = require('lodash');
+var util = require('util');
+
+exports.buildReport = function(report, query) {
+  var fields = [],
+    groupBy = [],
+    orderBy = [];
+  _.forEach(report.Fields, function(elem) {
+    var field = '';
+    if (elem.function) {
+      switch (elem.function) {
+        case 'COUNT DISTINCT':
+          field = util.format('COUNT(DISTINCT %s)', elem.field);
+          break;
+        case 'GROUP_CONCAT ASC':
+          field = util.format('GROUP_CONCAT(%s ORDER BY %s ASC)', elem.field, elem.field);
+          break;
+        case 'GROUP_CONCAT DESC':
+          field = util.format('GROUP_CONCAT(%s ORDER BY %s DESC)', elem.field, elem.field);
+          break;
+        default:
+          field = util.format('%s(%s)', elem.function, elem.field);
+      }
+    } else {
+      field = elem.field;
+    }
+    fields.push(util.format('%s AS \'%s\'', field, elem.alias));
+    if (elem.groupBy) {
+      groupBy.push(elem.field);
+    }
+    if (elem.orderBy) {
+      orderBy.push(util.format('%s %s', elem.field, elem.orderBy));
+    }
+  });
+
+  var conditions = report.conditions ? getConditions(JSON.parse(report.conditions).group) : '()';
+  var resultQuery = 'SELECT';
+  // query += fields.length ? ' ' + fields.join(',') : ' *';//right if you want to select all fields also
+  resultQuery += ' ' + fields.join(','); //generate a wrong query if there isn' t any field
+  resultQuery += ' FROM ' + report.table;
+  resultQuery += conditions !== '()' ? ' WHERE ' + conditions : '';
+  var startDate, endDate;
+  var otherConditions = [];
+  if (query) {
+    _.forOwn(query, function(value, key) {
+      switch (key) {
+        case 'startDate':
+          otherConditions.push('createdAt >= \'' + value + '\'');
+          break;
+        case 'endDate':
+          otherConditions.push('createdAt <= \'' + value + '\'');
+          break;
+        case 'offset':
+        case 'limit':
+          break;
+        default:
+          var column = _.find(report.Fields, {
+            alias: key
+          });
+          if (column) {
+            otherConditions.push(column.field + ' LIKE \'%' + value + '%\'');
+          } else {
+            otherConditions.push(key + ' LIKE \'%' + value + '%\'');
+          }
+      }
+    });
+  }
+  if (otherConditions.length) {
+    resultQuery += ((conditions !== '()') ? ' AND ' : ' WHERE ') + otherConditions.join(' AND ');
+  }
+  resultQuery += groupBy.length ? ' GROUP BY ' + groupBy.join(',') : '';
+  resultQuery += orderBy.length ? ' ORDER BY ' + orderBy.join(',') : '';
+
+  return resultQuery;
+}
+
+function getConditions(group) {
+
+  if (!group) return "()";
+  for (var str = "(", i = 0; i < group.rules.length; i++) {
+    i > 0 && (str += " " + group.operator + " ");
+    str += group.rules[i].group ?
+      getConditions(group.rules[i].group) :
+      group.rules[i].field + " " + group.rules[i].condition + " '" + (group.rules[i].condition === 'LIKE' ? "%" + group.rules[i].value + "%" : group.rules[i].value) + "'";
+  }
+  return str + ')';
+}
diff --git a/server/utils/report_extraction.js b/server/utils/report_extraction.js
index ba472f7..0d973b7 100644
--- a/server/utils/report_extraction.js
+++ b/server/utils/report_extraction.js
@@ -1 +1,197 @@
\ No newline at end of file
+var csv = require('to-csv');
+var pdfMake = require('pdfmake');
+var _ = require('lodash');
+var util = require('util');
+var ExtractedReport = require('../models').ExtractedReport;
+var sequelize = require('../models').sequelize;
+var fs = require('fs');
+var moment = require('moment');
+var sr = require('simple-random');
+var path = require('path');
+var config = require('../config/environment');
+var reportBuildUtils = require('./report_build');
+var fonts = {
+  Roboto: {
+    normal: path.join(config.fonts, 'Roboto-Regular.ttf'),
+    bold: path.join(config.fonts, 'Roboto-Medium.ttf'),
+    italics: path.join(config.fonts, 'Roboto-Italic.ttf'),
+    bolditalics: path.join(config.fonts, 'Roboto-Italic.ttf')
+  }
+};
+
+exports.extractPDF = function(report, query, extractedId) {
+  // console.log('--loaded external extractPDF function, calling general...');
+  general(report, query, extractedId, 'pdf');
+}
+
+exports.extractCSV = function(report, query, extractedId) {
+  // console.log('--loaded external extractCSV function, calling general...');
+  general(report, query, extractedId, 'csv');
+}
+
+function general(report, query, extractedId, type) {
+  // console.log('--general function, calling extract function...');
+  extract(report, query, extractedId, type);
+}
+
+function extract(report, dataQuery, extractedId, type) {
+  // console.log('--extract function, building query...');
+  var query = reportBuildUtils.buildReport(report.dataValues, dataQuery);
+  // console.log('--the query is :', query);
+  // console.log('--extracting the report...');
+  sequelize.query(query, {
+      type: sequelize.QueryTypes.SELECT
+    })
+    .then(function(res) {
+      if (res) {
+        if (res.length) {
+          // console.log('--report extracted, creating output as: ' + type);
+          switch (type) {
+            case 'pdf':
+              createPdf(res, report, dataQuery, extractedId);
+              break;
+            case 'csv':
+              createCsv(res, report, dataQuery, extractedId);
+              break;
+            default:
+              createCsv(res, report, dataQuery, extractedId);
+          }
+        } else {
+          updateExtracted(extractedId, null, 'empty');
+        }
+      } else {
+        // console.log('--error executing the report query');
+        updateExtracted(extractedId, null, 'query extraction error');
+
+      }
+    })
+    .catch(function(err) {
+      console.log(err);
+      updateExtracted(extractedId, null, 'query error');
+    });
+}
+
+function createPdf(res, report, dataQuery, id) {
+  // console.log('--pdf function, creating pdf output...');
+  var filename = sr({
+    length: 20,
+    prefix: "report_" + report.name + '_',
+    suffix: ".pdf"
+  });
+  var tableHeader = [];
+  _.forEach(report.Fields, function(elem) {
+    tableHeader.push({
+      text: elem.alias,
+      style: 'tableHeader'
+    });
+  });
+  var tableBody = [tableHeader];
+  _.forEach(res, function(elem) {
+    var row = [];
+    _.forOwn(elem, function(value, key) {
+      row.push(value instanceof Date ? moment(value).format("MM-DD-YYYY HH:mm").toString() : value);
+    });
+    tableBody.push(row);
+  });
+  var pdfContent = {
+    content: [{
+        text: report.name,
+        style: 'header'
+      },
+      dataQuery.startDate ? moment(dataQuery.startDate).format("MM-DD-YYYY HH:mm").toString() : '' + ' - ' + dataQuery.endDate ? moment(dataQuery.endDate).format("MM-DD-YYYY HH:mm").toString() :
+      '', {
+        style: 'tableExample',
+        table: {
+          headerRows: 1,
+          // keepWithHeaderRows: 1,
+          // dontBreakRows: true,
+          body: tableBody
+        }
+      }
+    ],
+    styles: {
+      header: {
+        fontSize: 18,
+        bold: true,
+        margin: [0, 0, 0, 10]
+      },
+      subheader: {
+        fontSize: 16,
+        bold: true,
+        margin: [0, 10, 0, 5]
+      },
+      tableExample: {
+        margin: [0, 5, 0, 15]
+      },
+      tableHeader: {
+        bold: true,
+        fontSize: 13,
+        color: 'black'
+      }
+    }
+  };
+
+  var printer = new pdfMake(fonts);
+  var pdfDoc = printer.createPdfKitDocument(pdfContent);
+  if (pdfDoc) {
+    // pdfDoc is a stream so you can pipe it to the file system
+    var writeStream = fs.createWriteStream(config.reports + filename);
+    writeStream.on('error', function() {
+      // console.log('--pdf write error');
+      updateExtracted(id, null, 'pdf write error')
+    });
+    writeStream.on('finish', function() {
+      // console.log('--pdf write finished');
+      updateExtracted(id, filename, 'completed')
+    });
+    pdfDoc.pipe(writeStream);
+    pdfDoc.end();
+  } else {
+    // console.log('--pdf creation error');
+    updateExtracted(id, null, 'pdf creation error')
+  }
+}
+
+function createCsv(res, report, dataQuery, id) {
+  // console.log(res);
+  // console.log('--csv function, creating pdf output...');
+  var filename = sr({
+    length: 20,
+    prefix: "report_" + report.name + '_',
+    suffix: ".csv"
+  });
+  var csv_report = csv(res);
+  if (csv_report) {
+    fs.writeFile(config.reports + filename, csv_report, function(err) {
+      if (err) {
+        // console.log('--csv write error');
+        console.log(err);
+        updateExtracted(id, null, 'csv write error')
+      } else {
+        // console.log('--csv write completed');
+        updateExtracted(id, filename, 'completed')
+      }
+    });
+  } else {
+    // console.log('--csv creation error');
+    updateExtracted(id, null, 'csv creation error')
+  }
+}
+
+function updateExtracted(id, filename, status) {
+  // console.log('--updating the extracted report db row..');
+  ExtractedReport.update({
+      status: status,
+      savename: filename
+    }, {
+      where: {
+        id: id
+      }
+    })
+    .then(function() {
+      console.log('--Finished extracting report');
+    })
+    .catch(function(err) {
+      console.log('--Finished extracting report with error : ' + err);
+    });
+}
diff --git a/server/utils/voice_extension.js b/server/utils/voice_extension.js
index 531a9b7..210cfd3 100644
--- a/server/utils/voice_extension.js
+++ b/server/utils/voice_extension.js
@@ -1 +1,214 @@
\ No newline at end of file
+var _ = require('lodash');
+var util = require('util');
+var Interval = require('../models').Interval;
+var sequelize = require('../models').sequelize;
+var VoiceExtension = require('../models').VoiceExtension;
+
+exports.createApps = createApplications;
+
+exports.routeUpdate = function(req, t) {
+  var sequelize = require('../models').sequelize;
+  var VoiceExtension = require('../models').VoiceExtension;
+  if (req.body.id) {
+    delete req.body.id;
+  }
+  return VoiceExtension
+    .update(req.body, {
+      where: {
+        id: req.params.id
+      },
+      transaction: t
+    })
+    .then(function(route) {
+      if (req.body.Applications) {
+        req.body.id = req.params.id;
+        return VoiceExtension
+          .destroy({
+            where: {
+              VoiceExtensionId: req.params.id
+            },
+            transaction: t
+          });
+      }
+    })
+    .then(function() {
+      if (req.body.Applications && Array.isArray(req.body.Applications) && req.body.Applications.length) {
+        return req.body;
+      }
+    })
+    .then(createApplications(req.body.Applications, t));
+}
+
+
+function createApplications(applications, t) {
+  var Interval = require('../models').Interval;
+  var VoiceExtension = require('../models').VoiceExtension;
+  return function(route) {
+    if (route) {
+      return Interval
+        .findAll() //find all existing intervals
+        .then(function(intervals) {
+          var priority = 2;
+          var routeDefault = {
+            exten: route.exten,
+            type: route.type,
+            context: route.context,
+            VoiceExtensionId: route.id
+          };
+          var bulkApps = [];
+          var applicationLength = 6; //update in case of adding other rows in the extension
+          var firstRows = [];
+          firstRows.push(_.merge({
+              app: 'Set',
+              appdata: util.format('CDR(type)=%s', route.type),
+              priority: priority
+            },
+            routeDefault));
+          priority++;
+          if (route.type === 'outbound') {
+            firstRows.push(_.merge({
+                app: 'Set',
+                appdata: util.format('CDR(tag)=%s', route.tag || '--'),
+                priority: priority
+              },
+              routeDefault));
+            priority++;
+          }
+          bulkApps.push(VoiceExtension.bulkCreate(firstRows, {
+            transaction: t
+          }));
+          applications.forEach(function(application) {
+            var appIntervals = [];
+            var extension = [];
+            if (application.IntervalId) { //if the interval for the app is chosen by a list we search for the subintervals
+              var foundIntervals = _.filter(intervals, {
+                IntervalId: application.IntervalId
+              });
+              appIntervals = foundIntervals.length ? _.map(foundIntervals, 'interval') : ['*,*,*,*']; //if the interval list has no subintervals the interval is always
+              var parentInterval = _.find(intervals, {
+                id: application.IntervalId
+              });
+              application.interval = parentInterval ? parentInterval.name : '*,*,*,*';
+            } else { //otherwise i will have the interval directly from the app, i still use an array to keep the creation logic the same
+              appIntervals = [application.interval];
+            }
+            var intLength = appIntervals.length;
+            appIntervals.forEach(function(elem, index) {
+              extension.push(_.merge({
+                  app: 'GotoIfTime',
+                  appdata: util.format('%s?%s,${EXTEN},%s:%s,${EXTEN},%s', elem, route.context, priority + (intLength - index), route.context, ((index + 1) < intLength) ? priority +
+                    1 : priority +
+                    applicationLength),
+                  priority: priority
+                },
+                routeDefault));
+              priority++;
+            });
+            if (application.app === 'Dial' && application.appdata[1] && route.type === 'outbound') {
+              extension.push(_.merge({
+                  app: 'Set',
+                  appdata: util.format('CALLERID(all)=%s', application.appdata[1]),
+                  priority: priority
+                },
+                routeDefault));
+            } else {
+              extension.push(_.merge({
+                  app: 'NoOP',
+                  appdata: 'Skipped CallerID Set',
+                  priority: priority
+                },
+                routeDefault));
+            }
+            priority++;
+            if (application.app === 'Dial' && route.record && route.type === 'outbound') {
+              extension.push(_.merge({
+                  app: 'MixMonitor',
+                  appdata: '${UNIQUEID}.wav,ab',
+                  priority: priority
+                },
+                routeDefault));
+            } else {
+              extension.push(_.merge({
+                  app: 'NoOP',
+                  appdata: 'Skipped MixMonitor',
+                  priority: priority
+                },
+                routeDefault));
+            }
+            priority++;
+            if (application.app === 'Queue') {
+              extension.push(_.merge({
+                  app: 'Answer',
+                  appdata: '',
+                  priority: priority
+                },
+                routeDefault));
+            } else {
+              extension.push(_.merge({
+                  app: 'NoOP',
+                  appdata: 'Skipped Answer',
+                  priority: priority
+                },
+                routeDefault));
+            }
+            priority++;
+            if (application.app === 'Dial' && route.type === 'outbound') {
+              extension.push(_.merge({
+                  app: application.app,
+                  appdata: util.format('SIP/%s/%s${EXTEN:%d}', application.appdata[0], application.appdata[3] || '', application.appdata[2] || 0),
+                  priority: priority,
+                  IntervalId: application.IntervalId || null,
+                  interval: application.interval || '*,*,*,*',
+                  isApp: true,
+                  callerID: application.appdata[1] || null
+                },
+                routeDefault));
+            } else {
+              extension.push(_.merge({
+                  app: application.app,
+                  appdata: application.appdata.join(','),
+                  priority: priority,
+                  IntervalId: application.IntervalId,
+                  interval: application.interval || '*,*,*,*',
+                  isApp: true
+                },
+                routeDefault));
+            }
+            priority++;
+            if (application.app === 'Dial' && route.record && route.type === 'outbound') {
+              extension.push(_.merge({
+                  app: 'StopMixMonitor',
+                  appdata: '',
+                  priority: priority
+                },
+                routeDefault));
+            } else {
+              extension.push(_.merge({
+                  app: 'NoOP',
+                  appdata: 'Skipped StopMixMonitor',
+                  priority: priority
+                },
+                routeDefault));
+            }
+            priority++;
+            extension.push(_.merge({
+                app: 'NoOP',
+                appdata: application.app + ' Application End',
+                priority: priority
+              },
+              routeDefault));
+            priority++;
+            bulkApps.push(VoiceExtension.bulkCreate(extension, {
+              transaction: t
+            }));
+          });
+          return bulkApps;
+        })
+        .all();
+    }
+  }
+}
+
+function handleError(res, err) {
+  return res.status(500).send(err);
+}