v;v++)mxEdgeStyle.limits[v][1]=
-n[v][0]-A[v],mxEdgeStyle.limits[v][2]=n[v][1]-A[v],mxEdgeStyle.limits[v][4]=n[v][0]+n[v][2]+A[v],mxEdgeStyle.limits[v][8]=n[v][1]+n[v][3]+A[v];A=n[0][1]+n[0][3]/2;p=n[1][1]+n[1][3]/2;v=n[0][0]+n[0][2]/2-(n[1][0]+n[1][2]/2);B=A-p;A=0;0>v?A=0>B?2:1:0>=B&&(A=3,0==v&&(A=2));p=null;null!=b&&(p=l);b=[[.5,.5],[.5,.5]];for(v=0;2>v;v++)null!=p&&(b[v][0]=(p.x-n[v][0])/n[v][2],1>=Math.abs(p.x-n[v][0])?a[v]=mxConstants.DIRECTION_MASK_WEST:1>=Math.abs(p.x-n[v][0]-n[v][2])&&(a[v]=mxConstants.DIRECTION_MASK_EAST),
-b[v][1]=(p.y-n[v][1])/n[v][3],1>=Math.abs(p.y-n[v][1])?a[v]=mxConstants.DIRECTION_MASK_NORTH:1>=Math.abs(p.y-n[v][1]-n[v][3])&&(a[v]=mxConstants.DIRECTION_MASK_SOUTH)),p=null,null!=c&&(p=m);v=n[0][1]-(n[1][1]+n[1][3]);m=n[0][0]-(n[1][0]+n[1][2]);p=n[1][1]-(n[0][1]+n[0][3]);q=n[1][0]-(n[0][0]+n[0][2]);mxEdgeStyle.vertexSeperations[1]=Math.max(m-z,0);mxEdgeStyle.vertexSeperations[2]=Math.max(v-z,0);mxEdgeStyle.vertexSeperations[4]=Math.max(p-z,0);mxEdgeStyle.vertexSeperations[3]=Math.max(q-z,0);z=[];
-c=[];l=[];c[0]=m>=q?mxConstants.DIRECTION_MASK_WEST:mxConstants.DIRECTION_MASK_EAST;l[0]=v>=p?mxConstants.DIRECTION_MASK_NORTH:mxConstants.DIRECTION_MASK_SOUTH;c[1]=mxUtils.reversePortConstraints(c[0]);l[1]=mxUtils.reversePortConstraints(l[0]);m=m>=q?m:q;p=v>=p?v:p;q=[[0,0],[0,0]];r=!1;for(v=0;2>v;v++)0==a[v]&&(0==(c[v]&d[v])&&(c[v]=mxUtils.reversePortConstraints(c[v])),0==(l[v]&d[v])&&(l[v]=mxUtils.reversePortConstraints(l[v])),q[v][0]=l[v],q[v][1]=c[v]);0v;v++)0==a[v]&&(0==(q[v][0]&d[v])&&(q[v][0]=q[v][1]),z[v]=q[v][0]&d[v],z[v]|=(q[v][1]&d[v])<<8,z[v]|=(q[1-v][v]&d[v])<<16,z[v]|=(q[1-v][1-v]&d[v])<<24,0==(z[v]&15)&&(z[v]<<=8),0==(z[v]&3840)&&(z[v]=z[v]&15|z[v]>>8),0==(z[v]&
-983040)&&(z[v]=z[v]&65535|(z[v]&251658240)>>8),a[v]=z[v]&15,d[v]==mxConstants.DIRECTION_MASK_WEST||d[v]==mxConstants.DIRECTION_MASK_NORTH||d[v]==mxConstants.DIRECTION_MASK_EAST||d[v]==mxConstants.DIRECTION_MASK_SOUTH)&&(a[v]=d[v]);d=a[0]==mxConstants.DIRECTION_MASK_EAST?3:a[0];z=a[1]==mxConstants.DIRECTION_MASK_EAST?3:a[1];d-=A;z-=A;1>d&&(d+=4);1>z&&(z+=4);d=mxEdgeStyle.routePatterns[d-1][z-1];mxEdgeStyle.wayPoints1[0][0]=n[0][0];mxEdgeStyle.wayPoints1[0][1]=n[0][1];switch(a[0]){case mxConstants.DIRECTION_MASK_WEST:mxEdgeStyle.wayPoints1[0][0]-=
-f;mxEdgeStyle.wayPoints1[0][1]+=b[0][1]*n[0][3];break;case mxConstants.DIRECTION_MASK_SOUTH:mxEdgeStyle.wayPoints1[0][0]+=b[0][0]*n[0][2];mxEdgeStyle.wayPoints1[0][1]+=n[0][3]+f;break;case mxConstants.DIRECTION_MASK_EAST:mxEdgeStyle.wayPoints1[0][0]+=n[0][2]+f;mxEdgeStyle.wayPoints1[0][1]+=b[0][1]*n[0][3];break;case mxConstants.DIRECTION_MASK_NORTH:mxEdgeStyle.wayPoints1[0][0]+=b[0][0]*n[0][2],mxEdgeStyle.wayPoints1[0][1]-=f}f=0;c=z=0<(a[0]&(mxConstants.DIRECTION_MASK_EAST|mxConstants.DIRECTION_MASK_WEST))?
-0:1;for(v=0;v>5,p<<=A,15>=4),q=0<(d[v]&mxEdgeStyle.CENTER_MASK),(u||t)&&9>p?(r=u?0:1,p=q&&0==l?n[r][0]+b[r][0]*n[r][2]:q?n[r][1]+b[r][1]*
-n[r][3]:mxEdgeStyle.limits[r][p],0==l?(p=(p-mxEdgeStyle.wayPoints1[f][0])*m[0],0
e&&(e+=4);1>a&&(a+=4);b=routePatterns[e-1][a-1];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);mxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON,mxPerimeter.HexagonPerimeter);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.states=null;mxGraphView.prototype.updateStyle=!1;mxGraphView.prototype.lastNode=null;mxGraphView.prototype.lastHtmlNode=null;mxGraphView.prototype.lastForegroundNode=null;mxGraphView.prototype.lastForegroundHtmlNode=null;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
-c.length||null==c[0]||null==c[c.length-1])?this.clear(a.cell,!0):(this.updateEdgeBounds(a),this.updateEdgeLabelOffset(a)))};
-mxGraphView.prototype.updateVertexLabelOffset=function(a){var b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER);if(b==mxConstants.ALIGN_LEFT)b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null),b=null!=b?b*this.scale:a.width,a.absoluteOffset.x-=b;else if(b==mxConstants.ALIGN_RIGHT)a.absoluteOffset.x+=a.width;else if(b==mxConstants.ALIGN_CENTER&&(b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null),null!=b)){var c=mxUtils.getValue(a.style,mxConstants.STYLE_ALIGN,
-mxConstants.ALIGN_CENTER),d=0;c==mxConstants.ALIGN_CENTER?d=.5:c==mxConstants.ALIGN_RIGHT&&(d=1);0!=d&&(a.absoluteOffset.x-=(b*this.scale-a.width)*d)}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.resetValidationState=function(){this.lastForegroundHtmlNode=this.lastForegroundNode=this.lastHtmlNode=this.lastNode=null};
-mxGraphView.prototype.stateValidated=function(a){var b=this.graph.getModel().isEdge(a.cell)&&this.graph.keepEdgesInForeground||this.graph.getModel().isVertex(a.cell)&&this.graph.keepEdgesInBackground;a=this.graph.cellRenderer.insertStateAfter(a,b?this.lastForegroundNode||this.lastNode:this.lastNode,b?this.lastForegroundHtmlNode||this.lastHtmlNode:this.lastHtmlNode);b?(this.lastForegroundHtmlNode=a[1],this.lastForegroundNode=a[0]):(this.lastHtmlNode=a[1],this.lastNode=a[0])};
-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){a.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(a,b,c,d),c)};
-mxGraphView.prototype.getFixedTerminalPoint=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)))}return e};
-mxGraphView.prototype.updateBoundsFromStencil=function(a){var b=null;if(null!=a&&null!=a.shape&&null!=a.shape.stencil&&"fixed"==a.shape.stencil.aspect){var b=mxRectangle.fromRectangle(a),c=a.shape.stencil.computeAspect(a.style,a.x,a.y,a.width,a.height);a.setRect(c.x,c.y,a.shape.stencil.w0*c.width,a.shape.stencil.h0*c.height)}return b};
-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);var g=this.updateBoundsFromStencil(c),k=this.updateBoundsFromStencil(d);f(a,c,d,b,e);null!=g&&c.setRect(g.x,g.y,g.width,g.height);null!=k&&d.setRect(k.x,k.y,k.width,k.height)}else if(null!=b)for(f=0;fb.length)||mxUtils.getValue(a.style,mxConstants.STYLE_ORTHOGONAL_LOOP,!1)&&(null!=e&&null!=e.point||null!=f&&null!=f.point)?!1:null!=c&&c==d};
-mxGraphView.prototype.getEdgeStyle=function(a,b,c,d){a=this.isLoopStyleEnabled(a,b,c,d)?mxUtils.getValue(a.style,mxConstants.STYLE_LOOP,this.graph.defaultLoopStyle):mxUtils.getValue(a.style,mxConstants.STYLE_NOEDGESTYLE,!1)?null:a.style[mxConstants.STYLE_EDGE];"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){a.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(a,b,c,d),d)};
-mxGraphView.prototype.getFloatingTerminalPoint=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 k=Math.cos(-c),l=Math.sin(-c),e=mxUtils.getRotatedPoint(e,k,l,g);k=parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING]||0);k+=parseFloat(a.style[d?mxConstants.STYLE_SOURCE_PERIMETER_SPACING:mxConstants.STYLE_TARGET_PERIMETER_SPACING]||
-0);a=this.getPerimeterPoint(b,e,0==c&&f,k);0!=c&&(k=Math.cos(c),l=Math.sin(c),a=mxUtils.getRotatedPoint(a,k,l,g));return a};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=Math.round(k+g)&&l=f?0:f*f/(a*a+m*m));a>e&&(a=e);e=Math.sqrt(mxUtils.ptSegDistSq(g.x,g.y,k.x,k.y,b,c));-1==mxUtils.relativeCcw(g.x,g.y,k.x,k.y,b,c)&&(e=-e);return new mxPoint((d/2-p-a)/d*-2,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&&0c&&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){mxClient.IS_TOUCH&&(mxEvent.addListener(b,"gesturestart",mxUtils.bind(this,function(b){a.fireGestureEvent(b);mxEvent.consume(b)})),mxEvent.addListener(b,"gesturechange",mxUtils.bind(this,function(b){a.fireGestureEvent(b);mxEvent.consume(b)})),mxEvent.addListener(b,"gestureend",mxUtils.bind(this,function(b){a.fireGestureEvent(b);mxEvent.consume(b)})));mxEvent.addGestureListeners(b,mxUtils.bind(this,function(b){!this.isContainerEvent(b)||
-(mxClient.IS_IE||mxClient.IS_IE11||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){this.isContainerEvent(b)&&a.dblClick(b)}));
-var c=function(c){var d=null;mxClient.IS_TOUCH&&(d=mxEvent.getClientX(c),c=mxEvent.getClientY(c),c=mxUtils.convertPoint(b,d,c),d=a.view.getState(a.getCellAt(c.x,c.y)));return d};a.addMouseListener({mouseDown:function(b,c){a.popupMenuHandler.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&&null!=a.container&&!this.isContainerEvent(b)&&
-"none"!=a.container.style.display&&"hidden"!=a.container.style.visibility&&!mxEvent.isConsumed(b)&&a.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,c(b)))});this.endHandler=mxUtils.bind(this,function(b){this.captureDocumentGesture&&a.isMouseDown&&null!=a.container&&!this.isContainerEvent(b)&&"none"!=a.container.style.display&&"hidden"!=a.container.style.visibility&&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.canvas.style.overflow="hidden",this.backgroundPane=this.createHtmlPane("1px","1px"),this.drawPane=this.createHtmlPane("1px","1px"),this.overlayPane=this.createHtmlPane("1px","1px"),this.decoratorPane=this.createHtmlPane("1px","1px"),this.canvas.appendChild(this.backgroundPane),this.canvas.appendChild(this.drawPane),this.canvas.appendChild(this.overlayPane),this.canvas.appendChild(this.decoratorPane),
-a.appendChild(this.canvas),this.updateContainerStyle(a),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'+mxClient.VML_PREFIX+":group>")};
-mxGraph.prototype.createHandlers=function(){this.tooltipHandler=this.createTooltipHandler();this.tooltipHandler.setEnabled(!1);this.selectionCellsHandler=this.createSelectionCellsHandler();this.connectionHandler=this.createConnectionHandler();this.connectionHandler.setEnabled(!1);this.graphHandler=this.createGraphHandler();this.panningHandler=this.createPanningHandler();this.panningHandler.panningEnabled=!1;this.popupMenuHandler=this.createPopupMenuHandler()};
-mxGraph.prototype.createTooltipHandler=function(){return new mxTooltipHandler(this)};mxGraph.prototype.createSelectionCellsHandler=function(){return new mxSelectionCellsHandler(this)};mxGraph.prototype.createConnectionHandler=function(){return new mxConnectionHandler(this)};mxGraph.prototype.createGraphHandler=function(){return new mxGraphHandler(this)};mxGraph.prototype.createPanningHandler=function(){return new mxPanningHandler(this)};mxGraph.prototype.createPopupMenuHandler=function(){return new mxPopupMenuHandler(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 mxUtils.indexOf(b,e)&&b.push(e)}}return this.getModel().getTopmostCells(b)};
-mxGraph.prototype.graphModelChanged=function(a){for(var b=0;b"+b+""),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!=b&&mxEvent.isMultiTouchEvent(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),this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,"cell",a,"event",b))))};mxGraph.prototype.getEditingValue=function(a,b){return this.convertValueToString(a)};
-mxGraph.prototype.stopEditing=function(a){this.cellEditor.stopEditing(a);this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED,"cancel",a))};mxGraph.prototype.labelChanged=function(a,b,c){this.model.beginUpdate();try{var d=a.value;this.cellLabelChanged(a,b,this.isAutoSizeCell(a));this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,"cell",a,"value",b,"old",d,"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.fireEvent(new mxEventObject(mxEvent.ESCAPE,"event",a))};
-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);if(this.isEnabled()&&!mxEvent.isConsumed(b)&&!d.isConsumed())if(null!=c){if(this.isTransparentClickEvent(b)){var e=!1;a=this.getCellAt(a.graphX,a.graphY,null,null,null,mxUtils.bind(this,function(a){a=this.isCellSelected(a.cell);e=e||a;return!e||a}));null!=a&&(c=a)}this.selectCellForEvent(c,b)}else 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.isEditing(b)||(this.startEditingAtCell(b,a),mxEvent.consume(a))};
-mxGraph.prototype.tapAndHold=function(a){var b=a.getEvent(),c=new mxEventObject(mxEvent.TAP_AND_HOLD,"event",b,"cell",a.getCell());this.fireEvent(c);c.isConsumed()&&(this.panningHandler.panningTrigger=!1);this.isEnabled()&&!mxEvent.isConsumed(b)&&!c.isConsumed()&&this.connectionHandler.isEnabled()&&(b=this.view.getState(this.connectionHandler.marker.getCell(a)),null!=b&&(this.connectionHandler.marker.currentColor=this.connectionHandler.marker.validColor,this.connectionHandler.marker.markedState=b,
-this.connectionHandler.marker.mark(),this.connectionHandler.first=new mxPoint(a.getGraphX(),a.getGraphY()),this.connectionHandler.edgeState=this.connectionHandler.createEdgeState(a),this.connectionHandler.previous=b,this.connectionHandler.fireEvent(new mxEventObject(mxEvent.START,"state",this.connectionHandler.previous))))};
-mxGraph.prototype.scrollPointToVisible=function(a,b,c,d){if(this.timerAutoScroll||!this.ignoreScrollbars&&!mxUtils.hasScrollbars(this.container))this.allowAutoPanning&&!this.panningHandler.isActive()&&(null==this.panningManager&&(this.panningManager=this.createPanningManager()),this.panningManager.panTo(a+this.panDx,b+this.panDy));else{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(fthis.minPageBreakDist)?Math.ceil(d.height/f.height)+1:0,k=a?Math.ceil(d.width/f.width)+1:0,l=(k-1)*f.width,m=(g-1)*f.height;null==this.horizontalPageBreaks&&0this.model.getChildCount(b)&&c--;this.model.add(b,a[l],c+l);this.autoSizeCellsOnAdd&&this.autoSizeCell(a[l],!0);(null==k||k)&&this.isExtendParentsOnAdd(a[l])&&this.isExtendParent(a[l])&&this.extendParent(a[l]);(null==g||g)&&this.constrainChild(a[l]);null!=d&&this.cellConnected(a[l],d,!0);null!=e&&this.cellConnected(a[l],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.autoSizeCell=function(a,b){if(null!=b?b:1)for(var c=this.model.getChildCount(a),d=0;d"),e=mxUtils.getSizeForString(f,e,d[mxConstants.STYLE_FONTFAMILY]),c=e.width+b,a=e.height+a,mxUtils.getValue(d,mxConstants.STYLE_HORIZONTAL,!0)||(d=a,a=c,c=d),this.gridEnabled&&(c=this.snap(c+this.gridSize/2),a=this.snap(a+this.gridSize/2)),b=new mxRectangle(0,0,c,a)):(d=4*this.gridSize,b=new mxRectangle(0,0,d,d))}}return b};mxGraph.prototype.resizeCell=function(a,b,c){return this.resizeCells([a],[b],c)[0]};
-mxGraph.prototype.resizeCells=function(a,b,c){c=null!=c?c:this.isRecursiveResize();this.model.beginUpdate();try{this.cellsResized(a,b,c),this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,"cells",a,"bounds",b))}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.cellsResized=function(a,b,c){c=null!=c?c:!1;if(null!=a&&null!=b&&a.length==b.length){this.model.beginUpdate();try{for(var d=0;de.width&&(f=c.width-e.width,c.width-=f);d.x+d.width>e.x+e.width&&(f-=d.x+d.width-e.x-e.width-f);g=0;c.height>e.height&&(g=c.height-e.height,c.height-=g);d.y+d.height>
-e.y+e.height&&(g-=d.y+d.height-e.y-e.height-g);d.xf&&(n=0),b>g&&(p=0),this.view.setTranslate(Math.floor(n/2-k.x),Math.floor(p/2-k.y)),this.container.scrollLeft=(a-f)/
-2,this.container.scrollTop=(b-g)/2):this.view.setTranslate(a?Math.floor(l.x-k.x*m+n*c/m):l.x,b?Math.floor(l.y-k.y*m+p*d/m):l.y)};
-mxGraph.prototype.zoom=function(a,b){b=null!=b?b:this.centerZoom;var c=Math.round(this.view.scale*a*100)/100,d=this.view.getState(this.getSelectionCell());a=c/this.view.scale;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(d=mxUtils.hasScrollbars(this.container),b&&!d){var d=this.container.offsetWidth,e=this.container.offsetHeight;if(1b?(b=a.height/b,c=(b-a.height)/2,a.height=b,a.y-=Math.min(a.y,c),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,a.x-=Math.min(a.x,c),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.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),d=new mxPoint(this.view.translate.x,this.view.translate.y),this.scrollRectToVisible(c)&&(c=new mxPoint(this.view.translate.x,this.view.translate.y),this.view.translate.x=
-d.x,this.view.translate.y=d.y,this.view.setTranslate(c.x,c.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);0g+c&&(this.view.translate.x-=(a.x+e-c-g)/l,b=!0);a.y+f>k+d&&(this.view.translate.y-=(a.y+f-d-k)/l,b=!0);a.x")):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 0f.max||bf.max||c")),null==e&&null!=a.overlays&&a.overlays.visit(function(a,c){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){return null!=a&&null!=a.getTooltip?a.getTooltip():this.convertValueToString(a)};mxGraph.prototype.getLinkForCell=function(a){return null};mxGraph.prototype.getCursorForMouseEvent=function(a){return this.getCursorForCell(a.getCell())};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.isCellRotatable=function(a){var b=this.view.getState(a);return 0!=(null!=b?b.style:this.getCellStyle(a))[mxConstants.STYLE_ROTATABLE]};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"!=mxUtils.getValue(b,mxConstants.STYLE_RESIZABLE,"1")};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(a){return this.extendParentsOnAdd};mxGraph.prototype.setExtendParentsOnAdd=function(a){this.extendParentsOnAdd=a};mxGraph.prototype.isExtendParentsOnMove=function(){return this.extendParentsOnMove};mxGraph.prototype.setExtendParentsOnMove=function(a){this.extendParentsOnMove=a};mxGraph.prototype.isRecursiveResize=function(a){return this.recursiveResize};
-mxGraph.prototype.setRecursiveResize=function(a){this.recursiveResize=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.isConstrainRelativeChildren=function(){return this.constrainRelativeChildren};
-mxGraph.prototype.setConstrainRelativeChildren=function(a){this.constrainRelativeChildren=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 0mxUtils.indexOf(a,g);)g=this.model.getParent(g);return this.model.isLayer(c)||null!=g?null:c};mxGraph.prototype.getDefaultParent=function(){var a=this.getCurrentRoot();null==a&&(a=this.defaultParent,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;ea.width*e||0a.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&&q.y+q.height<=l&&q.y>=b&&q.x+q.width<=k?f.push(p):this.getCells(a,
-b,c,d,p,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),k=0;k=a)&&(!e||m.y>=b)&&f.push(l)}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,k=0,l=0;lk&&(k=n,g=m)}}0==d.length&&null!=g&&d.push(g)}return d};
-mxGraph.prototype.traverse=function(a,b,c,d,e,f){if(null!=c&&null!=a&&(b=null!=b?b:!0,f=null!=f?f:!1,e=e||new mxDictionary,!e.get(a)&&(e.put(a,!0),d=c(a,d),null==d||d))&&(d=this.model.getEdgeCount(a),0b?f-1:b)),this.setSelectionCell(a)):this.getCurrentRoot()!=d&&this.setSelectionCell(d)};mxGraph.prototype.selectAll=function(a,b){a=a||this.getDefaultParent();var c=b?this.model.filterDescendants(function(b){return b!=a},a):this.model.getChildren(a);null!=c&&this.setSelectionCells(c)};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(this.model.getParent(c))||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;if(null!=a)if(this.model.isEdge(a.cell))var b=a.getVisibleTerminalState(!0),c=a.getVisibleTerminalState(!1),d=this.getCellGeometry(a.cell),b=this.view.getEdgeStyle(a,null!=d?d.points:null,b,c),b=this.createEdgeHandler(a,b);else b=this.createVertexHandler(a);return b};mxGraph.prototype.createVertexHandler=function(a){return new mxVertexHandler(a)};
-mxGraph.prototype.createEdgeHandler=function(a,b){return b==mxEdgeStyle.Loop||b==mxEdgeStyle.ElbowConnector||b==mxEdgeStyle.SideToSide||b==mxEdgeStyle.TopToBottom?this.createElbowEdgeHandler(a):b==mxEdgeStyle.SegmentConnector||b==mxEdgeStyle.OrthConnector?this.createEdgeSegmentHandler(a):new mxEdgeHandler(a)};mxGraph.prototype.createEdgeSegmentHandler=function(a){return new mxEdgeSegmentHandler(a)};mxGraph.prototype.createElbowEdgeHandler=function(a){return new mxElbowEdgeHandler(a)};
-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;bthis.doubleClickCounter){if(this.doubleClickCounter++,d=!1,a==mxEvent.MOUSE_UP?b.getCell()==this.lastTouchCell&&null!=this.lastTouchCell&&(this.lastTouchTime=0,d=this.lastTouchCell,this.lastTouchCell=null,mxClient.IS_QUIRKS&&b.getSource().fireEvent("ondblclick"),this.dblClick(b.getEvent(),
-d),d=!0):(this.fireDoubleClick=!0,this.lastTouchTime=0),!mxClient.IS_QUIRKS||d){mxEvent.consume(b.getEvent());return}}else{if(null==this.lastTouchEvent||this.lastTouchEvent!=b.getEvent())this.lastTouchCell=b.getCell(),this.lastTouchX=b.getX(),this.lastTouchY=b.getY(),this.lastTouchTime=d,this.lastTouchEvent=b.getEvent(),this.doubleClickCounter=0}else if((this.isMouseDown||a==mxEvent.MOUSE_UP)&&this.fireDoubleClick){this.fireDoubleClick=!1;d=this.lastTouchCell;this.lastTouchCell=null;this.isMouseDown=
-!1;(null!=d||(mxEvent.isTouchEvent(b.getEvent())||mxEvent.isPenEvent(b.getEvent()))&&(mxClient.IS_GC||mxClient.IS_SF))&&Math.abs(this.lastTouchX-b.getX())c.x&&(f-=c.x);0>c.y&&(g-=c.y);if(b.translate.x!=f||b.translate.y!=g)b.translate.x=f,b.translate.y=g,a=!0;var c=b.translate,d=this.source.getView().scale,f=d/b.scale,g=1/b.scale,k=this.source.container;this.bounds=new mxRectangle((c.x-e.x-this.source.panDx)/g,(c.y-e.y-this.source.panDy)/g,k.clientWidth/f,k.clientHeight/f);this.bounds.x+=this.source.container.scrollLeft*b.scale/d;this.bounds.y+=this.source.container.scrollTop*b.scale/d;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;b=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!=b.x||c.y!=b.y||c.width!=b.width||c.height!=b.height)this.sizer.bounds=b,"hidden"!=this.sizer.node.style.visibility&&this.sizer.redraw();a&&this.outline.view.revalidate()}}};
-mxOutline.prototype.mouseDown=function(a,b){if(this.enabled&&this.showViewport){var c=mxEvent.isMouseEvent(b.getEvent())?0:this.source.tolerance,c=this.source.allowHandleBoundsCheck&&(mxClient.IS_IE||0=this.max)||!this.source&&(0==this.max||f>=this.max))&&(g+=this.countError+"\n"),null!=this.validNeighbors&&null!=this.typeError&&0mxUtils.indexOf(a,f)&&(f=this.getLayout(f),null!=f&&f.moveCell(a[e],c.x,c.y))}};
-mxLayoutManager.prototype.getCellsForChanges=function(a){for(var b=new mxDictionary,c=[],d=0;df||Math.abs(d)>f){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()),
-f=!0;if(null!=this.guide&&this.useGuidesForEvent(b))d=this.guide.move(this.bounds,new mxPoint(e,d),g),f=!1,e=d.x,d=d.y;else if(g)var k=c.getView().translate,l=c.getView().scale,g=this.bounds.x-(c.snap(this.bounds.x/l-k.x)+k.x)*l,k=this.bounds.y-(c.snap(this.bounds.y/l-k.y)+k.y)*l,d=this.snap(new mxPoint(e,d)),e=d.x-g,d=d.y-k;null!=this.guide&&f&&this.guide.hide();c.isConstrainedEvent(b.getEvent())&&(Math.abs(e)>Math.abs(d)?d=0:e=0);this.currentDx=e;this.currentDy=d;this.updatePreviewShape();f=null;
-d=b.getCell();g=c.isCloneEvent(b.getEvent())&&c.isCellsCloneable()&&this.isCloneEnabled();c.isDropEnabled()&&this.highlightEnabled&&(f=c.getDropTarget(this.cells,b.getEvent(),d,g));e=c.getView().getState(f);k=!1;null==e||c.model.getParent(this.cell)==f&&!g?(this.target=null,this.connectOnDrop&&null!=d&&1==this.cells.length&&c.getModel().isVertex(d)&&c.isCellConnectable(d)&&(e=c.getView().getState(d),null!=e&&(c=null==c.getEdgeValidationError(null,this.cell,d)?mxConstants.VALID_COLOR:mxConstants.INVALID_CONNECT_TARGET_COLOR,
-this.setHighlightColor(c),k=!0))):(this.target!=f&&(this.target=f,this.setHighlightColor(mxConstants.DROP_TARGET_COLOR)),k=!0);null!=e&&k?this.highlight.highlight(e):this.highlight.hide()}this.updateHint(b);this.consumeMouseEvent(mxEvent.MOUSE_MOVE,b);mxEvent.consume(b.getEvent())}else!this.isMoveEnabled()&&!this.isCloneEnabled()||!this.updateCursor||b.isConsumed()||null==b.getState()&&null==b.sourceState||c.isMouseDown||(e=c.getCursorForMouseEvent(b),null==e&&c.isEnabled()&&c.isCellMovable(b.getCell())&&
-(e=c.getModel().isEdge(b.getCell())?mxConstants.CURSOR_MOVABLE_EDGE:mxConstants.CURSOR_MOVABLE_VERTEX),null!=e&&null!=b.sourceState&&b.sourceState.setCursor(e))};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=b.getCell();if(this.connectOnDrop&&null==this.target&&null!=d&&c.getModel().isVertex(d)&&c.isCellConnectable(d)&&c.isEdgeValid(null,this.cell,d))c.connectionHandler.connect(this.cell,d,b.getEvent());else{var d=c.isCloneEvent(b.getEvent())&&c.isCellsCloneable()&&this.isCloneEnabled(),e=c.getView().scale,f=this.roundLength(this.currentDx/
-e),e=this.roundLength(this.currentDy/e),g=this.target;c.isSplitEnabled()&&c.isSplitTarget(g,this.cells,b.getEvent())?c.splitEdge(g,this.cells,null,f,e):this.moveCells(this.cells,f,e,d,this.target,b.getEvent())}}else this.isSelectEnabled()&&this.delayedSelection&&null!=this.cell&&this.selectDelayed(b)}this.cellWasClicked&&this.consumeMouseEvent(mxEvent.MOUSE_UP,b);this.reset()};
-mxGraphHandler.prototype.selectDelayed=function(a){this.graph.isCellSelected(this.cell)&&this.graph.popupMenuHandler.isPopupTrigger(a)||this.graph.selectCellForEvent(this.cell,a.getEvent())};mxGraphHandler.prototype.reset=function(){this.destroyShapes();this.removeHint();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),null!=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!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());d=d&&!this.graph.isCellLocked(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);null!=this.escapeHandler&&(this.graph.removeListener(this.escapeHandler),this.escapeHandler=null);this.destroyShapes();this.removeHint()};
-function mxPanningHandler(a){null!=a&&(this.graph=a,this.graph.addMouseListener(this),this.forcePanningHandler=mxUtils.bind(this,function(a,c){var b=c.getProperty("eventName"),e=c.getProperty("event");b==mxEvent.MOUSE_DOWN&&this.isForcePanningEvent(e)&&(this.start(e),this.active=!0,this.fireEvent(new mxEventObject(mxEvent.PAN_START,"event",e)),e.consume())}),this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT,this.forcePanningHandler),this.gestureHandler=mxUtils.bind(this,function(a,c){if(this.isPinchEnabled()){var b=
-c.getProperty("event");mxEvent.isConsumed(b)||"gesturestart"!=b.type?"gestureend"==b.type&&null!=this.initialScale&&(this.initialScale=null):(this.initialScale=this.graph.view.scale,this.active||null==this.mouseDownEvent||(this.start(this.mouseDownEvent),this.mouseDownEvent=null));if(null!=this.initialScale){var e=Math.round(this.initialScale*b.scale*100)/100;null!=this.minScale&&(e=Math.max(this.minScale,e));null!=this.maxScale&&(e=Math.min(this.maxScale,e));this.graph.view.scale!=e&&(this.graph.zoomTo(e),
-mxEvent.consume(b))}}}),this.graph.addListener(mxEvent.GESTURE,this.gestureHandler),this.mouseUpListener=mxUtils.bind(this,function(){this.active&&this.reset()}),mxEvent.addListener(document,"mouseup",this.mouseUpListener))}mxPanningHandler.prototype=new mxEventSource;mxPanningHandler.prototype.constructor=mxPanningHandler;mxPanningHandler.prototype.graph=null;mxPanningHandler.prototype.useLeftButtonForPanning=!1;mxPanningHandler.prototype.usePopupTrigger=!0;
-mxPanningHandler.prototype.ignoreCell=!1;mxPanningHandler.prototype.previewEnabled=!0;mxPanningHandler.prototype.useGrid=!1;mxPanningHandler.prototype.panningEnabled=!0;mxPanningHandler.prototype.pinchEnabled=!0;mxPanningHandler.prototype.maxScale=8;mxPanningHandler.prototype.minScale=.01;mxPanningHandler.prototype.dx=null;mxPanningHandler.prototype.dy=null;mxPanningHandler.prototype.startX=0;mxPanningHandler.prototype.startY=0;
-mxPanningHandler.prototype.isActive=function(){return this.active||null!=this.initialScale};mxPanningHandler.prototype.isPanningEnabled=function(){return this.panningEnabled};mxPanningHandler.prototype.setPanningEnabled=function(a){this.panningEnabled=a};mxPanningHandler.prototype.isPinchEnabled=function(){return this.pinchEnabled};mxPanningHandler.prototype.setPinchEnabled=function(a){this.pinchEnabled=a};
-mxPanningHandler.prototype.isPanningTrigger=function(a){var b=a.getEvent();return this.useLeftButtonForPanning&&null==a.getState()&&mxEvent.isLeftMouseButton(b)||mxEvent.isControlDown(b)&&mxEvent.isShiftDown(b)||this.usePopupTrigger&&mxEvent.isPopupTrigger(b)};mxPanningHandler.prototype.isForcePanningEvent=function(a){return this.ignoreCell||mxEvent.isMultiTouchEvent(a.getEvent())};
-mxPanningHandler.prototype.mouseDown=function(a,b){this.mouseDownEvent=b;!b.isConsumed()&&this.isPanningEnabled()&&!this.active&&this.isPanningTrigger(b)&&(this.start(b),this.consumePanningTrigger(b))};mxPanningHandler.prototype.start=function(a){this.dx0=-this.graph.container.scrollLeft;this.dy0=-this.graph.container.scrollTop;this.startX=a.getX();this.startY=a.getY();this.dy=this.dx=null;this.panningTrigger=!0};mxPanningHandler.prototype.consumePanningTrigger=function(a){a.consume()};
-mxPanningHandler.prototype.mouseMove=function(a,b){this.dx=b.getX()-this.startX;this.dy=b.getY()-this.startY;if(this.active)this.previewEnabled&&(this.useGrid&&(this.dx=this.graph.snap(this.dx),this.dy=this.graph.snap(this.dy)),this.graph.panGraph(this.dx+this.dx0,this.dy+this.dy0)),this.fireEvent(new mxEventObject(mxEvent.PAN,"event",b));else if(this.panningTrigger){var c=this.active;this.active=Math.abs(this.dx)>this.graph.tolerance||Math.abs(this.dy)>this.graph.tolerance;!c&&this.active&&this.fireEvent(new mxEventObject(mxEvent.PAN_START,
-"event",b))}(this.active||this.panningTrigger)&&b.consume()};mxPanningHandler.prototype.mouseUp=function(a,b){if(this.active){if(null!=this.dx&&null!=this.dy){if(!this.graph.useScrollbarsForPanning||!mxUtils.hasScrollbars(this.graph.container)){var c=this.graph.getView().scale,d=this.graph.getView().translate;this.graph.panGraph(0,0);this.panGraph(d.x+this.dx/c,d.y+this.dy/c)}b.consume()}this.fireEvent(new mxEventObject(mxEvent.PAN_END,"event",b))}this.reset()};
-mxPanningHandler.prototype.reset=function(){this.panningTrigger=!1;this.mouseDownEvent=null;this.active=!1;this.dy=this.dx=null};mxPanningHandler.prototype.panGraph=function(a,b){this.graph.getView().setTranslate(a,b)};mxPanningHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.forcePanningHandler);this.graph.removeListener(this.gestureHandler);mxEvent.removeListener(document,"mouseup",this.mouseUpListener)};
-function mxPopupMenuHandler(a,b){null!=a&&(this.graph=a,this.factoryMethod=b,this.graph.addMouseListener(this),this.gestureHandler=mxUtils.bind(this,function(a,b){this.inTolerance=!1}),this.graph.addListener(mxEvent.GESTURE,this.gestureHandler),this.init())}mxPopupMenuHandler.prototype=new mxPopupMenu;mxPopupMenuHandler.prototype.constructor=mxPopupMenuHandler;mxPopupMenuHandler.prototype.graph=null;mxPopupMenuHandler.prototype.selectOnPopup=!0;
-mxPopupMenuHandler.prototype.clearSelectionOnBackground=!0;mxPopupMenuHandler.prototype.triggerX=null;mxPopupMenuHandler.prototype.triggerY=null;mxPopupMenuHandler.prototype.screenX=null;mxPopupMenuHandler.prototype.screenY=null;mxPopupMenuHandler.prototype.init=function(){mxPopupMenu.prototype.init.apply(this);mxEvent.addGestureListeners(this.div,mxUtils.bind(this,function(a){this.graph.tooltipHandler.hide()}))};mxPopupMenuHandler.prototype.isSelectOnPopup=function(a){return this.selectOnPopup};
-mxPopupMenuHandler.prototype.mouseDown=function(a,b){this.isEnabled()&&!mxEvent.isMultiTouchEvent(b.getEvent())&&(this.hideMenu(),this.triggerX=b.getGraphX(),this.triggerY=b.getGraphY(),this.screenX=mxEvent.getMainEvent(b.getEvent()).screenX,this.screenY=mxEvent.getMainEvent(b.getEvent()).screenY,this.popupTrigger=this.isPopupTrigger(b),this.inTolerance=!0)};
-mxPopupMenuHandler.prototype.mouseMove=function(a,b){this.inTolerance&&null!=this.screenX&&null!=this.screenY&&(Math.abs(mxEvent.getMainEvent(b.getEvent()).screenX-this.screenX)>this.graph.tolerance||Math.abs(mxEvent.getMainEvent(b.getEvent()).screenY-this.screenY)>this.graph.tolerance)&&(this.inTolerance=!1)};
-mxPopupMenuHandler.prototype.mouseUp=function(a,b){if(this.popupTrigger&&this.inTolerance&&null!=this.triggerX&&null!=this.triggerY){var c=this.getCellForPopupEvent(b);this.graph.isEnabled()&&this.isSelectOnPopup(b)&&null!=c&&!this.graph.isCellSelected(c)?this.graph.setSelectionCell(c):this.clearSelectionOnBackground&&null==c&&this.graph.clearSelection();this.graph.tooltipHandler.hide();var d=mxUtils.getScrollOrigin();this.popup(b.getX()+d.x+1,b.getY()+d.y+1,c,b.getEvent());b.consume()}this.inTolerance=
-this.popupTrigger=!1};mxPopupMenuHandler.prototype.getCellForPopupEvent=function(a){return a.getCell()};mxPopupMenuHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.gestureHandler);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;this.isEnabled()&&(b=this.getState(a),this.setCurrentState(b,a));return b};mxCellMarker.prototype.setCurrentState=function(a,b,c){var d=null!=a?this.isValidState(a):!1;c=null!=c?c:this.getMarkerColor(b.getEvent(),a,d);this.validState=d?a:null;if(a!=this.markedState||c!=this.currentColor)this.currentColor=c,null!=a&&null!=this.currentColor?(this.markedState=a,this.mark()):null!=this.markedState&&(this.markedState=null,this.unmark())};
-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(),c=this.getCell(a),b=this.getStateToMark(b.getState(c));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;cthis.graph.tolerance||g>this.graph.tolerance)&&(this.shape=this.createShape(),
-null!=this.edgeState&&this.shape.apply(this.edgeState),this.updateCurrentState(b,c));null!=this.shape&&(null!=this.edgeState?this.shape.points=this.edgeState.absolutePoints:(c=[e],null!=this.waypoints&&(c=c.concat(this.waypoints)),c.push(d),this.shape.points=c),this.drawPreview());null!=this.cursor&&(this.graph.container.style.cursor=this.cursor);mxEvent.consume(b.getEvent());b.consume()}else this.isEnabled()&&this.graph.isEnabled()?this.previous!=this.currentState&&null==this.edgeState?(this.destroyIcons(),
-null!=this.currentState&&null==this.error&&null==this.constraintHandler.currentConstraint&&(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():this.constraintHandler.reset();if(!this.graph.isMouseDown&&null!=this.currentState&&null!=this.icons){c=!1;d=b.getSource();for(e=
-0;ethis.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&&null!=this.currentState&&(d=this.currentState.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),0g||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);var a=this.sharedDiv;mxClient.IS_SVG&&(!mxClient.IS_IE||10<=document.documentMode)&&this.fadeOut&&(this.sharedDiv=null);return a};mxRubberband.prototype.isActive=function(a,b){return null!=this.div&&"none"!=this.div.style.display};
-mxRubberband.prototype.mouseUp=function(a,b){var c=this.isActive();this.reset();c&&(this.execute(b.getEvent()),b.consume())};mxRubberband.prototype.execute=function(a){var b=new mxRectangle(this.x,this.y,this.width,this.height);this.graph.selectRegion(b,a)};
-mxRubberband.prototype.reset=function(){if(null!=this.div)if(mxClient.IS_SVG&&(!mxClient.IS_IE||10<=document.documentMode)&&this.fadeOut){var a=this.div;mxUtils.setPrefixedStyle(a.style,"transition","all 0.2s linear");a.style.pointerEvents="none";a.style.opacity=0;window.setTimeout(function(){a.parentNode.removeChild(a)},200)}else this.div.parentNode.removeChild(this.div);mxEvent.removeGestureListeners(document,null,this.dragHandler,this.dropHandler);this.dropHandler=this.dragHandler=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.forceRubberbandHandler),this.graph.removeListener(this.panHandler),this.reset(),null!=this.sharedDiv&&(this.sharedDiv=null))};function mxHandle(a,b,c){this.graph=a.view.graph;this.state=a;this.cursor=null!=b?b:this.cursor;this.image=null!=c?c:this.image;this.init()}mxHandle.prototype.cursor="default";mxHandle.prototype.image=null;
-mxHandle.prototype.ignoreGrid=!1;mxHandle.prototype.getPosition=function(a){};mxHandle.prototype.setPosition=function(a,b,c){};mxHandle.prototype.execute=function(){};mxHandle.prototype.copyStyle=function(a){this.graph.setCellStyles(a,this.state.style[a],[this.state.cell])};
-mxHandle.prototype.processEvent=function(a){var b=this.graph.view.scale,c=this.graph.view.translate,c=new mxPoint(a.getGraphX()/b-c.x,a.getGraphY()/b-c.y);null!=this.shape&&null!=this.shape.bounds&&(c.x-=this.shape.bounds.width/b/4,c.y-=this.shape.bounds.height/b/4);var b=-mxUtils.toRadians(this.getRotation()),d=-mxUtils.toRadians(this.getTotalRotation())-b,c=this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(c,b),this.ignoreGrid||!this.graph.isGridEnabledEvent(a.getEvent())),d));this.setPosition(this.state.getPaintBounds(),
-c,a);this.positionChanged();this.redraw()};mxHandle.prototype.positionChanged=function(){null!=this.state.text&&this.state.text.apply(this.state);null!=this.state.shape&&this.state.shape.apply(this.state);this.graph.cellRenderer.redraw(this.state,!0)};mxHandle.prototype.getRotation=function(){return null!=this.state.shape?this.state.shape.getRotation():0};mxHandle.prototype.getTotalRotation=function(){return null!=this.state.shape?this.state.shape.getShapeRotation():0};
-mxHandle.prototype.init=function(){var a=this.isHtmlRequired();null!=this.image?(this.shape=new mxImageShape(new mxRectangle(0,0,this.image.width,this.image.height),this.image.src),this.shape.preserveImageAspect=!1):this.shape=this.createShape(a);this.initShape(a)};mxHandle.prototype.createShape=function(a){a=new mxRectangle(0,0,mxConstants.HANDLE_SIZE,mxConstants.HANDLE_SIZE);return new mxRectangleShape(a,mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};
-mxHandle.prototype.initShape=function(a){a&&this.shape.isHtmlAllowed()?(this.shape.dialect=mxConstants.DIALECT_STRICTHTML,this.shape.init(this.graph.container)):(this.shape.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_MIXEDHTML:mxConstants.DIALECT_SVG,null!=this.cursor&&this.shape.init(this.graph.getView().getOverlayPane()));mxEvent.redirectMouseEvents(this.shape.node,this.graph,this.state);this.shape.node.style.cursor=this.cursor};
-mxHandle.prototype.redraw=function(){if(null!=this.shape&&null!=this.state.shape){var a=this.getPosition(this.state.getPaintBounds());if(null!=a){var b=mxUtils.toRadians(this.getTotalRotation()),a=this.rotatePoint(this.flipPoint(a),b),b=this.graph.view.scale,c=this.graph.view.translate;this.shape.bounds.x=Math.floor((a.x+c.x)*b-this.shape.bounds.width/2);this.shape.bounds.y=Math.floor((a.y+c.y)*b-this.shape.bounds.height/2);this.shape.redraw()}}};
-mxHandle.prototype.isHtmlRequired=function(){return null!=this.state.text&&this.state.text.node.parentNode==this.graph.container};mxHandle.prototype.rotatePoint=function(a,b){var c=this.state.getCellBounds(),c=new mxPoint(c.getCenterX(),c.getCenterY());return mxUtils.getRotatedPoint(a,Math.cos(b),Math.sin(b),c)};
-mxHandle.prototype.flipPoint=function(a){if(null!=this.state.shape){var b=this.state.getCellBounds();this.state.shape.flipH&&(a.x=2*b.x+b.width-a.x);this.state.shape.flipV&&(a.y=2*b.y+b.height-a.y)}return a};mxHandle.prototype.snapPoint=function(a,b){b||(a.x=this.graph.snap(a.x),a.y=this.graph.snap(a.y));return a};mxHandle.prototype.setVisible=function(a){null!=this.shape&&null!=this.shape.node&&(this.shape.node.style.display=a?"":"none")};
-mxHandle.prototype.reset=function(){this.setVisible(!0);this.state.style=this.graph.getCellStyle(this.state.cell);this.positionChanged()};mxHandle.prototype.destroy=function(){null!=this.shape&&(this.shape.destroy(),this.shape=null)};
-function mxVertexHandler(a){null!=a&&(this.state=a,this.init(),this.escapeHandler=mxUtils.bind(this,function(a,c){this.livePreview&&null!=this.index&&(this.state.view.graph.cellRenderer.redraw(this.state,!0),this.state.view.invalidate(this.state.cell),this.state.invalid=!1,this.state.view.validate());this.reset()}),this.state.view.graph.addListener(mxEvent.ESCAPE,this.escapeHandler))}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.parentHighlightEnabled=!1;mxVertexHandler.prototype.rotationRaster=!0;mxVertexHandler.prototype.rotationCursor="crosshair";mxVertexHandler.prototype.livePreview=!1;mxVertexHandler.prototype.manageSizers=!1;mxVertexHandler.prototype.constrainGroupByChildren=!1;
-mxVertexHandler.prototype.rotationHandleVSpacing=-16;mxVertexHandler.prototype.horizontalOffset=0;mxVertexHandler.prototype.verticalOffset=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.rotation=
-Number(this.state.style[mxConstants.STYLE_ROTATION]||"0");this.selectionBorder.init(this.graph.getView().getOverlayPane());mxEvent.redirectMouseEvents(this.selectionBorder.node,this.graph,this.state);this.graph.isCellMovable(this.state.cell)&&this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);if(0>=mxGraphHandler.prototype.maxCells||this.graph.getSelectionCount()this.state.width&&2>this.state.height&&(this.labelShape=this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,mxEvent.LABEL_HANDLE,
-null,mxConstants.LABEL_HANDLE_FILLCOLOR),this.sizers.push(this.labelShape))}this.isRotationHandleVisible()&&(this.rotationShape=this.createSizer(this.rotationCursor,mxEvent.ROTATION_HANDLE,mxConstants.HANDLE_SIZE+3,mxConstants.HANDLE_FILLCOLOR),this.sizers.push(this.rotationShape));this.customHandles=this.createCustomHandles();this.redraw();this.constrainGroupByChildren&&this.updateMinBounds()};
-mxVertexHandler.prototype.isRotationHandleVisible=function(){return this.graph.isEnabled()&&this.rotationEnabled&&this.graph.isCellRotatable(this.state.cell)&&(0>=mxGraphHandler.prototype.maxCells||this.graph.getSelectionCount()this.graph.tolerance||Math.abs(a.getGraphY()-this.startY)>this.graph.tolerance)&&(this.inTolerance=!1)};mxVertexHandler.prototype.updateHint=function(a){};mxVertexHandler.prototype.removeHint=function(){};mxVertexHandler.prototype.roundAngle=function(a){return Math.round(10*a)/10};
-mxVertexHandler.prototype.roundLength=function(a){return Math.round(a)};
-mxVertexHandler.prototype.mouseMove=function(a,b){b.isConsumed()||null==this.index?this.graph.isMouseDown||null==this.getHandleForEvent(b)||b.consume(!1):(this.checkTolerance(b),this.inTolerance||(this.index<=mxEvent.CUSTOM_HANDLE?null!=this.customHandles&&(this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].processEvent(b),this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].active=!0):this.index==mxEvent.LABEL_HANDLE?this.moveLabel(b):this.index==mxEvent.ROTATION_HANDLE?this.rotateVertex(b):this.resizeVertex(b),
-this.updateHint(b)),b.consume())};mxVertexHandler.prototype.moveLabel=function(a){var b=new mxPoint(a.getGraphX(),a.getGraphY()),c=this.graph.view.translate,d=this.graph.view.scale;this.graph.isGridEnabledEvent(a.getEvent())&&(b.x=(this.graph.snap(b.x/d-c.x)+c.x)*d,b.y=(this.graph.snap(b.y/d-c.y)+c.y)*d);this.moveSizerTo(this.sizers[null!=this.rotationShape?this.sizers.length-2:this.sizers.length-1],b.x,b.y)};
-mxVertexHandler.prototype.rotateVertex=function(a){var b=new mxPoint(a.getGraphX(),a.getGraphY()),c=this.state.x+this.state.width/2-b.x,d=this.state.y+this.state.height/2-b.y;this.currentAlpha=0!=c?180*Math.atan(d/c)/Math.PI+90:0>d?180:0;0k.x+k.width&&(this.unscaledBounds.width-=this.unscaledBounds.x+this.unscaledBounds.width-k.x-k.width),this.unscaledBounds.y+this.unscaledBounds.height>
-k.y+k.height&&(this.unscaledBounds.height-=this.unscaledBounds.y+this.unscaledBounds.height-k.y-k.height)));this.bounds=new mxRectangle((null!=this.parentState?this.parentState.x:e.x*f)+this.unscaledBounds.x*f,(null!=this.parentState?this.parentState.y:e.y*f)+this.unscaledBounds.y*f,this.unscaledBounds.width*f,this.unscaledBounds.height*f);g.relative&&null!=this.parentState&&(this.bounds.x+=this.state.x-this.parentState.x,this.bounds.y+=this.state.y-this.parentState.y);g=Math.cos(c);k=Math.sin(c);
-c=new mxPoint(this.bounds.getCenterX(),this.bounds.getCenterY());l=c.x-b.x;d=c.y-b.y;b=g*l-k*d-l;c=k*l+g*d-d;l=this.bounds.x-this.state.x;d=this.bounds.y-this.state.y;e=g*l-k*d;g=k*l+g*d;this.bounds.x+=b;this.bounds.y+=c;this.unscaledBounds.x=this.roundLength(this.unscaledBounds.x+b/f);this.unscaledBounds.y=this.roundLength(this.unscaledBounds.y+c/f);this.unscaledBounds.width=this.roundLength(this.unscaledBounds.width);this.unscaledBounds.height=this.roundLength(this.unscaledBounds.height);this.graph.isCellCollapsed(this.state.cell)||
-0==b&&0==c?this.childOffsetY=this.childOffsetX=0:(this.childOffsetX=this.state.x-this.bounds.x+e,this.childOffsetY=this.state.y-this.bounds.y+g);this.livePreview&&this.updateLivePreview(a);null!=this.preview&&this.drawPreview()};
-mxVertexHandler.prototype.updateLivePreview=function(a){var b=this.graph.view.scale,c=this.graph.view.translate;a=this.state.clone();this.state.x=this.bounds.x;this.state.y=this.bounds.y;this.state.origin=new mxPoint(this.state.x/b-c.x,this.state.y/b-c.y);this.state.width=this.bounds.width;this.state.height=this.bounds.height;this.state.unscaledWidth=null;b=this.state.absoluteOffset;new mxPoint(b.x,b.y);this.state.absoluteOffset.x=0;this.state.absoluteOffset.y=0;b=this.graph.getCellGeometry(this.state.cell);
-null!=b&&(c=b.offset||this.EMPTY_POINT,null==c||b.relative||(this.state.absoluteOffset.x=this.state.view.scale*c.x,this.state.absoluteOffset.y=this.state.view.scale*c.y),this.state.view.updateVertexLabelOffset(this.state));this.state.view.graph.cellRenderer.redraw(this.state,!0);this.state.view.invalidate(this.state.cell);this.state.invalid=!1;this.state.view.validate();this.redrawHandles();this.state.setState(a)};
-mxVertexHandler.prototype.mouseUp=function(a,b){if(null!=this.index&&null!=this.state){var c=new mxPoint(b.getGraphX(),b.getGraphY());this.graph.getModel().beginUpdate();try{if(this.index<=mxEvent.CUSTOM_HANDLE)null!=this.customHandles&&(this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].active=!1,this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].execute());else if(this.index==mxEvent.ROTATION_HANDLE)if(null!=this.currentAlpha){var d=this.currentAlpha-(this.state.style[mxConstants.STYLE_ROTATION]||
-0);0!=d&&this.rotateCell(this.state.cell,d)}else this.rotateClick();else{var e=this.graph.isGridEnabledEvent(b.getEvent()),f=mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION]||"0"),g=Math.cos(-f),k=Math.sin(-f),l=c.x-this.startX,m=c.y-this.startY,c=k*l+g*m,l=g*l-k*m,m=c,n=this.graph.view.scale,p=this.isRecursiveResize(this.state,b);this.resizeCell(this.state.cell,this.roundLength(l/n),this.roundLength(m/n),this.index,e,this.isConstrainedEvent(b),p)}}finally{this.graph.getModel().endUpdate()}b.consume();
-this.reset()}};mxVertexHandler.prototype.isRecursiveResize=function(a,b){return this.graph.isRecursiveResize(this.state)};mxVertexHandler.prototype.rotateClick=function(){};
-mxVertexHandler.prototype.rotateCell=function(a,b,c){if(0!=b){var d=this.graph.getModel();if(d.isVertex(a)||d.isEdge(a)){if(!d.isEdge(a)){var e=this.graph.view.getState(a),e=null!=e?e.style:this.graph.getCellStyle(a);null!=e&&this.graph.setCellStyles(mxConstants.STYLE_ROTATION,(e[mxConstants.STYLE_ROTATION]||0)+b,[a])}e=this.graph.getCellGeometry(a);if(null!=e){var f=this.graph.getCellGeometry(c);null==f||d.isEdge(c)||(e=e.clone(),e.rotate(b,new mxPoint(f.width/2,f.height/2)),d.setGeometry(a,e));
-if(d.isVertex(a)&&!e.relative||d.isEdge(a))for(c=d.getChildCount(a),e=0;ed&&(a+=c,e&&(a=this.graph.snap(a/f)*f));if(0==d||3==d||5==d)p+=b,e&&(p=this.graph.snap(p/f)*f);else if(2==d||4==d||7==d)q+=b,e&&(q=this.graph.snap(q/
-f)*f);e=q-p;c=r-a;k&&(k=this.graph.getCellGeometry(this.state.cell),null!=k&&(k=k.width/k.height,1==d||2==d||7==d||6==d?e=c*k:c=e/k,0==d&&(p=q-e,a=r-c)));l&&(e+=e-m,c+=c-n,p+=t-(p+e/2),a+=u-(a+c/2));0>e&&(p+=e,e=Math.abs(e));0>c&&(a+=c,c=Math.abs(c));d=new mxRectangle(p+g.x*f,a+g.y*f,e,c);null!=this.minBounds&&(d.width=Math.max(d.width,this.minBounds.x*f+this.minBounds.width*f+Math.max(0,this.x0*f-d.x)),d.height=Math.max(d.height,this.minBounds.y*f+this.minBounds.height*f+Math.max(0,this.y0*f-d.y)));
-return d};mxVertexHandler.prototype.redraw=function(){this.selectionBounds=this.getSelectionBounds(this.state);this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);this.redrawHandles();this.drawPreview()};
-mxVertexHandler.prototype.getHandlePadding=function(){var a=new mxPoint(0,0),b=this.tolerance;null!=this.sizers&&0=mxGraphHandler.prototype.maxCells)this.bends=this.createBends(),this.isVirtualBendsEnabled()&&(this.virtualBends=this.createVirtualBends());this.label=new mxPoint(this.state.absoluteOffset.x,this.state.absoluteOffset.y);this.labelShape=this.createLabelHandleShape();this.initBend(this.labelShape);this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE);this.customHandles=this.createCustomHandles();this.redraw()};mxEdgeHandler.prototype.createCustomHandles=function(){return null};
-mxEdgeHandler.prototype.isVirtualBendsEnabled=function(a){return this.virtualBendsEnabled&&(null==this.state.style[mxConstants.STYLE_EDGE]||this.state.style[mxConstants.STYLE_EDGE]==mxConstants.NONE||1==this.state.style[mxConstants.STYLE_NOEDGESTYLE])&&"arrow"!=mxUtils.getValue(this.state.style,mxConstants.STYLE_SHAPE,null)};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.createParentHighlightShape=function(a){a=new mxRectangleShape(a,null,this.getSelectionColor());a.strokewidth=this.getSelectionStrokeWidth();a.isDashed=this.isSelectionDashed();return a};mxEdgeHandler.prototype.createSelectionShape=function(a){a=new this.state.shape.constructor;a.outline=!0;a.apply(this.state);a.isDashed=this.isSelectionDashed();a.stroke=this.getSelectionColor();a.isShadow=!1;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.getCellAt=function(a,b){return this.outlineConnect?null:this.graph.getCellAt(a,b)};
-mxEdgeHandler.prototype.createMarker=function(){var a=new mxCellMarker(this.graph),b=this;a.getCell=function(a){var c=mxCellMarker.prototype.getCell.apply(this,arguments);c!=b.state.cell&&null!=c||null==b.currentPoint||(c=b.graph.getCellAt(b.currentPoint.x,b.currentPoint.y));if(null!=c&&!this.graph.isCellConnectable(c)){var e=this.graph.getModel().getParent(c);this.graph.getModel().isVertex(e)&&this.graph.isCellConnectable(e)&&(c=e)}e=b.graph.getModel();if(this.graph.isSwimlane(c)&&null!=b.currentPoint&&
-this.graph.hitsSwimlaneContent(c,b.currentPoint.x,b.currentPoint.y)||!b.isConnectableCell(c)||c==b.state.cell||null!=c&&!b.graph.connectableEdges&&e.isEdge(c)||e.isAncestor(b.state.cell,c))c=null;this.graph.isCellConnectable(c)||(c=null);return c};a.isValidState=function(a){var c=b.graph.getModel(),c=b.graph.view.getTerminalPort(a,b.graph.view.getState(c.getTerminal(b.state.cell,!b.isSource)),!b.isSource),c=null!=c?c.cell:null;b.error=b.validateConnection(b.isSource?a.cell:c,b.isSource?c: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
-mxEvent.VIRTUAL_HANDLE&&null!=this.customHandles)for(c=0;cmxEvent.VIRTUAL_HANDLE&&(c[this.index-1]=d)}return null!=e?e:c};
-mxEdgeHandler.prototype.isOutlineConnectEvent=function(a){var b=mxUtils.getOffset(this.graph.container),c=a.getEvent(),d=mxEvent.getClientX(c),c=mxEvent.getClientY(c),e=document.documentElement,f=this.currentPoint.x-this.graph.container.scrollLeft+b.x-((window.pageXOffset||e.scrollLeft)-(e.clientLeft||0)),b=this.currentPoint.y-this.graph.container.scrollTop+b.y-((window.pageYOffset||e.scrollTop)-(e.clientTop||0));return this.outlineConnect&&!mxEvent.isShiftDown(a.getEvent())&&(a.isSource(this.marker.highlight.shape)||
-mxEvent.isAltDown(a.getEvent())&&null!=a.getState()||this.marker.highlight.isHighlightAt(d,c)||(f!=d||b!=c)&&null==a.getState()&&this.marker.highlight.isHighlightAt(f,b))};
-mxEdgeHandler.prototype.updatePreviewState=function(a,b,c,d,e){var f=this.isSource?c:this.state.getVisibleTerminalState(!0),g=this.isTarget?c:this.state.getVisibleTerminalState(!1),k=this.graph.getConnectionConstraint(a,f,!0),l=this.graph.getConnectionConstraint(a,g,!1),m=this.constraintHandler.currentConstraint;null==m&&e&&(null!=c?(d.isSource(this.marker.highlight.shape)&&(b=new mxPoint(d.getGraphX(),d.getGraphY())),m=this.graph.getOutlineConstraint(b,c,d),this.constraintHandler.setFocus(d,c,this.isSource),
-this.constraintHandler.currentConstraint=m,this.constraintHandler.currentPoint=b):m=new mxConnectionConstraint);if(this.outlineConnect&&null!=this.marker.highlight&&null!=this.marker.highlight.shape){var n=this.graph.view.scale;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus?(this.marker.highlight.shape.stroke=e?mxConstants.OUTLINE_HIGHLIGHT_COLOR:"transparent",this.marker.highlight.shape.strokewidth=mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH/n/n,this.marker.highlight.repaint()):
-this.marker.hasValidState()&&(this.marker.highlight.shape.stroke=this.marker.getValidState()==d.getState()?mxConstants.DEFAULT_VALID_COLOR:"transparent",this.marker.highlight.shape.strokewidth=mxConstants.HIGHLIGHT_STROKEWIDTH/n/n,this.marker.highlight.repaint())}this.isSource?k=m:this.isTarget&&(l=m);if(this.isSource||this.isTarget)null!=m&&null!=m.point?(a.style[this.isSource?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X]=m.point.x,a.style[this.isSource?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y]=
-m.point.y):(delete a.style[this.isSource?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X],delete a.style[this.isSource?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y]);a.setVisibleTerminalState(f,!0);a.setVisibleTerminalState(g,!1);this.isSource&&null==f||a.view.updateFixedTerminalPoint(a,f,!0,k);this.isTarget&&null==g||a.view.updateFixedTerminalPoint(a,g,!1,l);(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,f,g);a.view.updateFloatingTerminalPoints(a,f,g)};
-mxEdgeHandler.prototype.mouseMove=function(a,b){if(null!=this.index&&null!=this.marker){this.currentPoint=this.getPointForEvent(b);this.error=null;!this.graph.isIgnoreTerminalEvent(b.getEvent())&&mxEvent.isShiftDown(b.getEvent())&&null!=this.snapPoint&&(Math.abs(this.snapPoint.x-this.currentPoint.x)mxEvent.VIRTUAL_HANDLE)null!=
-this.customHandles&&this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].processEvent(b);else if(this.isLabel)this.label.x=this.currentPoint.x,this.label.y=this.currentPoint.y;else{this.points=this.getPreviewPoints(this.currentPoint,b);var c=this.isSource||this.isTarget?this.getPreviewTerminalState(b):null;if(null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentPoint)this.currentPoint=this.constraintHandler.currentPoint.clone();
-else if(this.outlineConnect){var d=this.isSource||this.isTarget?this.isOutlineConnectEvent(b):!1;d?c=this.marker.highlight.state:null!=c&&c!=b.getState()&&null!=this.marker.highlight.shape&&(this.marker.highlight.shape.stroke="transparent",this.marker.highlight.repaint(),c=null)}null!=c&&this.graph.isCellLocked(c.cell)&&(c=null,this.marker.reset());var e=this.clonePreviewState(this.currentPoint,null!=c?c.cell:null);this.updatePreviewState(e,this.currentPoint,c,b,d);this.setPreviewColor(null==this.error?
-this.marker.validColor:this.marker.invalidColor);this.abspoints=e.absolutePoints;this.active=!0}this.updateHint(b,this.currentPoint);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){var d=!this.graph.isIgnoreTerminalEvent(b.getEvent())&&this.graph.isCloneEvent(b.getEvent())&&this.cloneEnabled&&this.graph.isCellsCloneable();if(null!=this.error)0mxEvent.VIRTUAL_HANDLE){if(null!=this.customHandles){var e=this.graph.getModel();
-e.beginUpdate();try{this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].execute()}finally{e.endUpdate()}}}else if(this.isLabel)this.moveLabel(this.state,this.label.x,this.label.y);else if(this.isSource||this.isTarget){var f=null;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&(f=this.constraintHandler.currentFocus.cell);null==f&&this.marker.hasValidState()&&null!=this.marker.highlight&&null!=this.marker.highlight.shape&&"transparent"!=this.marker.highlight.shape.stroke&&
-"white"!=this.marker.highlight.shape.stroke&&(f=this.marker.validState.cell);if(null!=f){var e=this.graph.getModel(),g=e.getParent(c);e.beginUpdate();try{if(d){var k=e.getGeometry(c),d=this.graph.cloneCells([c])[0];e.add(g,d,e.getChildCount(g));null!=k&&(k=k.clone(),e.setGeometry(d,k));var l=e.getTerminal(c,!this.isSource);this.graph.connectCell(d,l,!this.isSource);c=d}c=this.connect(c,f,this.isSource,d,b)}finally{e.endUpdate()}}else this.graph.isAllowDanglingEdges()&&(e=this.abspoints[this.isSource?
-0:this.abspoints.length-1],e.x=this.roundLength(e.x/this.graph.view.scale-this.graph.view.translate.x),e.y=this.roundLength(e.y/this.graph.view.scale-this.graph.view.translate.y),f=this.graph.getView().getState(this.graph.getModel().getParent(c)),null!=f&&(e.x-=f.origin.x,e.y-=f.origin.y),e.x-=this.graph.panDx/this.graph.view.scale,e.y-=this.graph.panDy/this.graph.view.scale,c=this.changeTerminalPoint(c,e,this.isSource,d))}else this.active?c=this.changePoints(c,this.points,d):(this.graph.getView().invalidate(this.state.cell),
-this.graph.getView().validate(this.state.cell))}null!=this.marker&&(this.reset(),c!=this.state.cell&&this.graph.setSelectionCell(c));b.consume()}};
-mxEdgeHandler.prototype.reset=function(){this.active&&this.refresh();this.snapPoint=this.points=this.label=this.index=this.error=null;this.active=this.isTarget=this.isSource=this.isLabel=!1;if(this.livePreview&&null!=this.sizers)for(var a=0;a");this.div.style.visibility="";mxUtils.fit(this.div)}};
-mxTooltipHandler.prototype.destroy=function(){this.destroyed||(this.graph.removeMouseListener(this),mxEvent.release(this.div),null!=this.div&&null!=this.div.parentNode&&this.div.parentNode.removeChild(this.div),this.destroyed=!0,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){};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.opacity=mxConstants.HIGHLIGHT_OPACITY,this.repaintHandler=mxUtils.bind(this,function(){if(null!=this.state){var a=this.graph.view.getState(this.state.cell);null==a?this.hide():(this.state=a,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=this.graph.cellRenderer.createShape(this.state);a.svgStrokeTolerance=this.graph.tolerance;a.points=this.state.absolutePoints;a.apply(this.state);a.stroke=this.highlightColor;a.opacity=this.opacity;a.isDashed=this.dashed;a.isShadow=!1;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);this.graph.dialect!=
-mxConstants.DIALECT_SVG?a.pointerEvents=!1:a.svgPointerEvents="stroke";return a};mxCellHighlight.prototype.getStrokeWidth=function(a){return this.strokeWidth};
-mxCellHighlight.prototype.repaint=function(){if(null!=this.state&&null!=this.shape){this.shape.scale=this.state.view.scale;this.graph.model.isEdge(this.state.cell)?(this.shape.strokewidth=this.getStrokeWidth(),this.shape.points=this.state.absolutePoints,this.shape.outline=!1):(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),this.shape.rotation=Number(this.state.style[mxConstants.STYLE_ROTATION]||
-"0"),this.shape.strokewidth=this.getStrokeWidth()/this.state.view.scale,this.shape.outline=!0);null!=this.state.shape&&this.shape.setCursor(this.state.shape.getCursor());if(mxClient.IS_QUIRKS||8==document.documentMode)"transparent"==this.shape.stroke?(this.shape.stroke="white",this.shape.opacity=1):this.shape.opacity=this.opacity;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.isHighlightAt=function(a,b){var c=!1;if(null!=this.shape&&null!=document.elementFromPoint&&!mxClient.IS_QUIRKS)for(var d=document.elementFromPoint(a,b);null!=d;){if(d==this.shape.node){c=!0;break}d=d.parentNode}return c};
-mxCellHighlight.prototype.destroy=function(){this.graph.getView().removeListener(this.resetHandler);this.graph.getView().removeListener(this.repaintHandler);this.graph.getModel().removeListener(this.repaintHandler);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 k=!1;null!=f;){if("add"==f.nodeName){var l=f.getAttribute("if");if(null==l||e[l]){var l=f.getAttribute("as"),l=mxResources.get(l)||l,m=mxUtils.eval(mxUtils.getTextContent(f)),n=f.getAttribute("action"),p=f.getAttribute("icon"),q=f.getAttribute("iconCls"),r=f.getAttribute("enabled-if"),r=null==r||e[r];k&&(b.addSeparator(g),k=!1);null!=p&&this.imageBasePath&&(p=this.imageBasePath+p);l=this.addAction(b,a,l,p,m,n,c,g,q,r);this.addItems(a,
-b,c,d,e,f.firstChild,l)}}else"separator"==f.nodeName&&(k=!0);f=f.nextSibling}};mxDefaultPopupMenu.prototype.addAction=function(a,b,c,d,e,f,g,k,l,m){return a.addItem(c,d,function(a){"function"==typeof e&&e.call(b,b,g,a);null!=f&&b.execute(f,g,a)},k,l,m)};
-mxDefaultPopupMenu.prototype.createConditions=function(a,b,c){var d=a.graph.getModel(),e=d.getChildCount(b),f=[];f.nocell=null==b;f.ncells=1 "+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&&0n&&(c-=c+k-n);n=l.y+l.height;d+m>n&&(d-=d+m-n)}}else null!=k&&(c-=k.x*f,d-=k.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.elements=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){if(null==this.elements){if(null==this.document.documentElement)throw Error("mxCodec constructor needs document parameter");this.elements={};this.addElement(this.document.documentElement)}return this.elements[a]};mxCodec.prototype.addElement=function(a){if(a.nodeType==mxConstants.NODETYPE_ELEMENT){var b=a.getAttribute("id");null!=b&&null==this.elements[b]&&(this.elements[b]=a)}for(a=a.firstChild;null!=a;)this.addElement(a),a=a.nextSibling};
-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=mxUtils.importNode(this.document,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){c=null;try{c=window[a.nodeName]}catch(d){}c=mxCodecRegistry.getCodec(c);null!=c?c=c.decode(this,a,b):(c=a.cloneNode(!0),c.removeAttribute("as"))}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;dNote that not all labs host every kind of machine.
-As you make your selections, labs and hosts that are not compatible
-with your current configuration will become unavailable.
-NOTE: Only PTL's are able to create multi-node PODs. See
- here
- for more details
-
-{% endblock content %}
diff --git a/src/templates/base/resource/steps/host_info.html b/src/templates/base/resource/steps/host_info.html
deleted file mode 100644
index 3230d8f..0000000
--- a/src/templates/base/resource/steps/host_info.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-{% if error %}
-{{error}}
-{% else %}
-
-
-
-{% endif %}
-{% endblock content %}
diff --git a/src/templates/base/resource/steps/meta_info.html b/src/templates/base/resource/steps/meta_info.html
deleted file mode 100644
index 6fef065..0000000
--- a/src/templates/base/resource/steps/meta_info.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-
-{% endblock content %}
diff --git a/src/templates/base/resource/steps/pod_definition.html b/src/templates/base/resource/steps/pod_definition.html
deleted file mode 100644
index 233d995..0000000
--- a/src/templates/base/resource/steps/pod_definition.html
+++ /dev/null
@@ -1,72 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% block extrahead %}
-Pod Definition Prototype
-
-
-
-{% endblock extrahead %}
-
-
-{% block content %}
-
-
-
-
-
-
-
Hold right click to drag
-
-
-
-
-
-
-
-
-
Add Network
-
-
Submit
-
-
-
-
-
-
-{% endblock content %}
-{% block onleave %}
-network_step.submitForm();
-{% endblock %}
diff --git a/src/templates/base/resource/uncommon.css b/src/templates/base/resource/uncommon.css
deleted file mode 100644
index 1ea9f35..0000000
--- a/src/templates/base/resource/uncommon.css
+++ /dev/null
@@ -1,162 +0,0 @@
-div.mxRubberband {
- position: absolute;
- overflow: hidden;
- border-style: solid;
- border-width: 1px;
- border-color: #0000FF;
- background: #0077FF;
-}
-.mxCellEditor {
- background: url(data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7);
- _background: url('static/img/mxgraph/transparent.gif');
- border-color: transparent;
- border-style: solid;
- display: inline-block;
- position: absolute;
- overflow: visible;
- word-wrap: normal;
- border-width: 0;
- min-width: 1px;
- resize: none;
- padding: 0px;
- margin: 0px;
-}
-.mxPlainTextEditor * {
- padding: 0px;
- margin: 0px;
-}
-div.mxWindow {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: url('static/img/mxgraph/window.gif');
- border:1px solid #c3c3c3;
- position: absolute;
- overflow: hidden;
- z-index: 3;
-}
-table.mxWindow {
- border-collapse: collapse;
- table-layout: fixed;
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxWindowTitle {
- background: url('static/img/mxgraph/window-title.gif') repeat-x;
- text-overflow: ellipsis;
- white-space: nowrap;
- text-align: center;
- font-weight: bold;
- overflow: hidden;
- height: 13px;
- padding: 2px;
- padding-top: 4px;
- padding-bottom: 6px;
- color: black;
-}
-td.mxWindowPane {
- vertical-align: top;
- padding: 0px;
-}
-div.mxWindowPane {
- overflow: hidden;
- position: relative;
-}
-td.mxWindowPane td {
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio {
- border-color: #8C8C8C;
- border-style: solid;
- border-width: 1px;
- font-family: Arial;
- font-size: 8pt;
- padding: 1px;
-}
-td.mxWindowPane button {
- background: url('static/img/mxgraph/button.gif') repeat-x;
- font-family: Arial;
- font-size: 8pt;
- padding: 2px;
- float: left;
-}
-img.mxToolbarItem {
- margin-right: 6px;
- margin-bottom: 6px;
- border-width: 1px;
-}
-select.mxToolbarCombo {
- vertical-align: top;
- border-style: inset;
- border-width: 2px;
-}
-div.mxToolbarComboContainer {
- padding: 2px;
-}
-img.mxToolbarMode {
- margin: 2px;
- margin-right: 4px;
- margin-bottom: 4px;
- border-width: 0px;
-}
-img.mxToolbarModeSelected {
- margin: 0px;
- margin-right: 2px;
- margin-bottom: 2px;
- border-width: 2px;
- border-style: inset;
-}
-div.mxTooltip {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: #FFFFCC;
- border-style: solid;
- border-width: 1px;
- border-color: black;
- font-family: Arial;
- font-size: 8pt;
- position: absolute;
- cursor: default;
- padding: 4px;
- color: black;
-}
-div.mxPopupMenu {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: url('static/img/mxgraph/window.gif');
- position: absolute;
- border-style: solid;
- border-width: 1px;
- border-color: black;
-}
-table.mxPopupMenu {
- border-collapse: collapse;
- margin-top: 1px;
- margin-bottom: 1px;
-}
-tr.mxPopupMenuItem {
- color: black;
- cursor: pointer;
-}
-tr.mxPopupMenuItemHover {
- background-color: #000066;
- color: #FFFFFF;
- cursor: pointer;
-}
-td.mxPopupMenuItem {
- padding: 2px 30px 2px 10px;
- white-space: nowrap;
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxPopupMenuIcon {
- background-color: #D0D0D0;
- padding: 2px 4px 2px 4px;
-}
-.mxDisabled {
- opacity: 0.2 !important;
- cursor:default !important;
-}
diff --git a/src/templates/base/rest_framework/api.html b/src/templates/base/rest_framework/api.html
deleted file mode 100644
index a62c8f5..0000000
--- a/src/templates/base/rest_framework/api.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{% extends "rest_framework/base.html" %}
-
-{% block title %}Laas Dashboard API{% endblock %}
-
-{% block branding %}
-
- Laas Dashboard API
-
-{% endblock %}
\ No newline at end of file
diff --git a/src/templates/base/snapshot_workflow/steps/meta.html b/src/templates/base/snapshot_workflow/steps/meta.html
deleted file mode 100644
index 88136d2..0000000
--- a/src/templates/base/snapshot_workflow/steps/meta.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-{% bootstrap_form_errors form type='non_fields' %}
-
-{% endblock content %}
diff --git a/src/templates/base/snapshot_workflow/steps/select_host.html b/src/templates/base/snapshot_workflow/steps/select_host.html
deleted file mode 100644
index 4243145..0000000
--- a/src/templates/base/snapshot_workflow/steps/select_host.html
+++ /dev/null
@@ -1,83 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-{% bootstrap_form_errors form type='non_fields' %}
-
-
-
-{% endblock content %}
diff --git a/src/templates/base/workflow/confirm.html b/src/templates/base/workflow/confirm.html
deleted file mode 100644
index bc8e4e3..0000000
--- a/src/templates/base/workflow/confirm.html
+++ /dev/null
@@ -1,56 +0,0 @@
-{% extends "workflow/viewport-element.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-
-
Confirm Session
-
-
-
-
-
{{confirmation_info|escape}}
-
-
-
-
-
-
-
- Confirm
- Cancel
-
-
-
-
-
-
-
-
-
-{% block element_messages %}
-
-{% endblock element_messages %}
-{% endblock content %}
diff --git a/src/templates/base/workflow/no_workflow.html b/src/templates/base/workflow/no_workflow.html
deleted file mode 100644
index 0ac6549..0000000
--- a/src/templates/base/workflow/no_workflow.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/src/templates/base/workflow/viewport-base.html b/src/templates/base/workflow/viewport-base.html
deleted file mode 100644
index 88229ca..0000000
--- a/src/templates/base/workflow/viewport-base.html
+++ /dev/null
@@ -1,82 +0,0 @@
-{% extends "base.html" %}
-{% load staticfiles %}
-
-{% load bootstrap4 %}
-
-{% block content %}
-
-
-
-
-
- Is something not working right? Let us know
here!
-
-
-
-
-
-{% csrf_token %}
-
-
-
-
-
-
-
-{% endblock content %}
diff --git a/src/templates/base/workflow/viewport-element.html b/src/templates/base/workflow/viewport-element.html
deleted file mode 100644
index db4da54..0000000
--- a/src/templates/base/workflow/viewport-element.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{% load bootstrap4 %}
-{% load staticfiles %}
-
-{% block basecontent %}
-
- {% block content %}
- {% endblock content %}
-
-
- {% block element_messages %}
- {% bootstrap_messages %}
- {% endblock %}
-
-{% endblock basecontent %}
-
-{% block extrajs %}
-{% endblock extrajs %}
diff --git a/src/templates/laas/base.html b/src/templates/laas/base.html
deleted file mode 100644
index f980268..0000000
--- a/src/templates/laas/base.html
+++ /dev/null
@@ -1,89 +0,0 @@
-{% extends "base/base.html" %}
-{% load staticfiles %}
-{% block logo %}
-
-
-
-
-{% endblock logo %}
-
diff --git a/src/templates/laas/dashboard/landing.html b/src/templates/laas/dashboard/landing.html
deleted file mode 100644
index 12d8924..0000000
--- a/src/templates/laas/dashboard/landing.html
+++ /dev/null
@@ -1,12 +0,0 @@
-{% extends "base/dashboard/landing.html" %}
-
-{% block about_us %}
-The Lab as a Service (LaaS) project aims to help in the development and testing of LFN projects such as
- Anuket
- by hosting hardware and providing access to the community. Currently, the only participating lab is the
- University of New Hampshire Interoperability Lab (UNH-IOL).
-To get started, you can request access to a server at the right. PTL's have the ability to design and
- book a
- whole block of servers with customized layer2 networks (e.g. a Pharos Pod). Read more here:
- LaaS Wiki
-{% endblock %}
\ No newline at end of file
diff --git a/src/templates/laas/layout.html b/src/templates/laas/layout.html
deleted file mode 100644
index f9b1d99..0000000
--- a/src/templates/laas/layout.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends "base/layout.html" %}
-
-{% block head-title %}
-LaaS Dashboard
-{% endblock head-title %}
diff --git a/src/templates/lfedge/base.html b/src/templates/lfedge/base.html
deleted file mode 100644
index 4413340..0000000
--- a/src/templates/lfedge/base.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% extends "base/base.html" %}
-{% load staticfiles %}
-{% block bgColor %}
-
-
-
-{% endblock bgColor %}
-
-{% block logo %}
-
-{% endblock logo %}
-{% block dropDown %}
-{% endblock dropDown %}
-{% block userDropDownText %}
-
- {% if request.user.username %}
- {{request.user.username}}
- {% else %}
-
- {% endif %}
-
-
-{% endblock userDropDownText %}
diff --git a/src/templates/lfedge/booking/booking_table.html b/src/templates/lfedge/booking/booking_table.html
deleted file mode 100644
index 4020b5e..0000000
--- a/src/templates/lfedge/booking/booking_table.html
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
- Owner
- Purpose
- Project
- Start
- End
- Operating System
- Pod
-
-
-
-{% for booking in bookings %}
-
-
- {{ booking.owner.username }}
-
-
- {{ booking.purpose }}
-
-
- {{ booking.project }}
-
-
- {{ booking.start }}
-
-
- {{ booking.end }}
-
-
- {{ booking.resource.get_head_node.config.image.os.name }}
-
-
- {{ booking.resource.get_template_name }}
-
-
-{% endfor %}
-
diff --git a/src/templates/lfedge/booking/quick_deploy.html b/src/templates/lfedge/booking/quick_deploy.html
deleted file mode 100644
index ccafd90..0000000
--- a/src/templates/lfedge/booking/quick_deploy.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "base/booking/quick_deploy.html" %}
-{% block opnfv %}
-{% endblock opnfv %}
-{% block form-text %}
-
- Please select a host type you wish to book.
- Only available types are shown.
- More information can be found here:
- LF Edge Wiki .
- If something isn't working right, let us know here!
-
-{% endblock form-text %}
-{% block collab %}
-
-
- Collaborators
- {{ form.users }}
-
-
-{% endblock collab %}
-
-{% block image_script %}
-
-{% endblock image_script %}
diff --git a/src/templates/lfedge/dashboard/landing.html b/src/templates/lfedge/dashboard/landing.html
deleted file mode 100644
index 9a776dc..0000000
--- a/src/templates/lfedge/dashboard/landing.html
+++ /dev/null
@@ -1,23 +0,0 @@
-{% extends "base/dashboard/landing.html" %}
-{% block about_us %}
- The Shared Community Lab at the IOL aims to help development and testing of LF Edge projects by hosting hardware and providing access to the community.
- To get started, you can request access to a pod at the right.
-{% endblock about_us %}
-
-{% block btnGrp %}
-
-To get started, book a pod below:
-Book a Pod
-{% endblock btnGrp %}
-
-{% block returningUsers %}
-{% endblock returningUsers %}
diff --git a/src/templates/lfedge/layout.html b/src/templates/lfedge/layout.html
deleted file mode 100644
index 217060c..0000000
--- a/src/templates/lfedge/layout.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% extends "base/layout.html" %}
-
-{% block head-title %}
-LF Edge Dashboard
-{% endblock head-title %}
diff --git a/src/workflow/README b/src/workflow/README
deleted file mode 100644
index fb4b949..0000000
--- a/src/workflow/README
+++ /dev/null
@@ -1,31 +0,0 @@
-This app creates "workflows", which are long and complex interactions from the user.
-Workflows are composed of multiple steps. At each step the user inputs some information.
-The content of one step may impact following steps.
-
-The WorkflowStep object is the abstract type for all the workflow steps.
-Important attributes and methods:
-
-template - the django template to use when rendering this step
-valid - the status code from WorkflowStepStatus
-
-get_context() - returns a dictionary that is used when rendering this step's template
- You should always call super's get_context and add / overwrite any data into that
- dictionary
-
-post(data, user) - this method is called when the step is POST'd to.
- data is from the request object, suitable for a Form's constructor
-
-
-Repository
-Each step has a reference to a shared repository (self.repo).
-The repo is a key-value store that allows the steps to share data
-
-Steps render based on the current state of the repo. For example, a step
-may get information about each host the user said they want and ask for additional
-input for each machine.
-Because the steps render based on what is in the repo, a user can easily go back to
-a previous step and change some data. This data will change in the repo and
-affect later steps accordingly.
-
-Everything stored in the repo is temporary. After a workflow has been completed, the repo
-is translated into Django models and saved to the database.
diff --git a/src/workflow/__init__.py b/src/workflow/__init__.py
deleted file mode 100644
index e0408fa..0000000
--- a/src/workflow/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
diff --git a/src/workflow/apps.py b/src/workflow/apps.py
deleted file mode 100644
index adc2738..0000000
--- a/src/workflow/apps.py
+++ /dev/null
@@ -1,15 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.apps import AppConfig
-
-
-class WorkflowConfig(AppConfig):
- name = 'workflow'
diff --git a/src/workflow/booking_workflow.py b/src/workflow/booking_workflow.py
deleted file mode 100644
index ef89804..0000000
--- a/src/workflow/booking_workflow.py
+++ /dev/null
@@ -1,182 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
-# Copyright (c) 2020 Sawyer Bergeron, Sean Smith, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-from django.utils import timezone
-
-from datetime import timedelta
-
-from booking.models import Booking
-from workflow.models import WorkflowStep, AbstractSelectOrCreate
-from workflow.forms import ResourceSelectorForm, BookingMetaForm, OPNFVSelectForm
-from resource_inventory.models import OPNFVConfig, ResourceTemplate
-from django.db.models import Q
-
-
-"""
-subclassing notes:
- subclasses have to define the following class attributes:
- self.repo_key: main output of step, where the selected/created single selector
- result is placed at the end
- self.confirm_key:
-"""
-
-
-class Abstract_Resource_Select(AbstractSelectOrCreate):
- form = ResourceSelectorForm
- template = 'dashboard/genericselect.html'
- title = "Select Resource"
- description = "Select a resource template to use for your deployment"
- short_title = "pod select"
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.select_repo_key = self.repo.SELECTED_RESOURCE_TEMPLATE
- self.confirm_key = self.workflow_type
-
- def alert_bundle_missing(self):
- self.set_invalid("Please select a valid resource template")
-
- def get_form_queryset(self):
- user = self.repo_get(self.repo.SESSION_USER)
- return ResourceTemplate.objects.filter((Q(owner=user) | Q(public=True)))
-
- def get_page_context(self):
- return {
- 'select_type': 'resource',
- 'select_type_title': 'Resource template',
- 'addable_type_num': 1
- }
-
- def put_confirm_info(self, bundle):
- confirm_dict = self.repo_get(self.repo.CONFIRMATION)
- if self.confirm_key not in confirm_dict:
- confirm_dict[self.confirm_key] = {}
- confirm_dict[self.confirm_key]["Resource Template"] = bundle.name
- self.repo_put(self.repo.CONFIRMATION, confirm_dict)
-
-
-class Booking_Resource_Select(Abstract_Resource_Select):
- workflow_type = "booking"
-
-
-class OPNFV_EnablePicker(object):
- pass
-
-
-class OPNFV_Select(AbstractSelectOrCreate, OPNFV_EnablePicker):
- title = "Choose an OPNFV Config"
- description = "Choose or create a description of how you want to deploy OPNFV"
- short_title = "opnfv config"
- form = OPNFVSelectForm
- enabled = False
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.select_repo_key = self.repo.SELECTED_OPNFV_CONFIG
- self.confirm_key = "booking"
-
- def alert_bundle_missing(self):
- self.set_invalid("Please select a valid OPNFV config")
-
- def get_form_queryset(self):
- cb = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
- qs = OPNFVConfig.objects.filter(bundle=cb)
- return qs
-
- def put_confirm_info(self, config):
- confirm_dict = self.repo_get(self.repo.CONFIRMATION)
- if self.confirm_key not in confirm_dict:
- confirm_dict[self.confirm_key] = {}
- confirm_dict[self.confirm_key]["OPNFV Configuration"] = config.name
- self.repo_put(self.repo.CONFIRMATION, confirm_dict)
-
- def get_page_context(self):
- return {
- 'select_type': 'opnfv',
- 'select_type_title': 'OPNFV Config',
- 'addable_type_num': 4
- }
-
-
-class Booking_Meta(WorkflowStep):
- template = 'booking/steps/booking_meta.html'
- title = "Extra Info"
- description = "Tell us how long you want your booking, what it is for, and who else should have access to it"
- short_title = "booking info"
-
- def get_context(self):
- context = super(Booking_Meta, self).get_context()
- initial = {}
- default = []
- try:
- models = self.repo_get(self.repo.BOOKING_MODELS, {})
- booking = models.get("booking")
- if booking:
- initial['purpose'] = booking.purpose
- initial['project'] = booking.project
- initial['length'] = (booking.end - booking.start).days
- info = self.repo_get(self.repo.BOOKING_INFO_FILE, False)
- if info:
- initial['info_file'] = info
- users = models.get("collaborators", [])
- for user in users:
- default.append(user.userprofile)
- except Exception:
- pass
-
- owner = self.repo_get(self.repo.SESSION_USER)
-
- context['form'] = BookingMetaForm(initial=initial, user_initial=default, owner=owner)
- return context
-
- def post(self, post_data, user):
- form = BookingMetaForm(data=post_data, owner=user)
-
- forms = self.repo_get(self.repo.BOOKING_FORMS, {})
-
- forms["meta_form"] = form
- self.repo_put(self.repo.BOOKING_FORMS, forms)
-
- if form.is_valid():
- models = self.repo_get(self.repo.BOOKING_MODELS, {})
- if "booking" not in models:
- models['booking'] = Booking()
- models['collaborators'] = []
- confirm = self.repo_get(self.repo.CONFIRMATION)
- if "booking" not in confirm:
- confirm['booking'] = {}
-
- models['booking'].start = timezone.now()
- models['booking'].end = timezone.now() + timedelta(days=int(form.cleaned_data['length']))
- models['booking'].purpose = form.cleaned_data['purpose']
- models['booking'].project = form.cleaned_data['project']
- for key in ['length', 'project', 'purpose']:
- confirm['booking'][key] = form.cleaned_data[key]
-
- if form.cleaned_data["deploy_opnfv"]:
- self.repo_get(self.repo.SESSION_MANAGER).set_step_statuses(OPNFV_EnablePicker, desired_enabled=True)
- else:
- self.repo_get(self.repo.SESSION_MANAGER).set_step_statuses(OPNFV_EnablePicker, desired_enabled=False)
-
- userprofile_list = form.cleaned_data['users']
- confirm['booking']['collaborators'] = []
- for userprofile in userprofile_list:
- models['collaborators'].append(userprofile.user)
- confirm['booking']['collaborators'].append(userprofile.user.username)
-
- info_file = form.cleaned_data.get("info_file", False)
- if info_file:
- self.repo_put(self.repo.BOOKING_INFO_FILE, info_file)
-
- self.repo_put(self.repo.BOOKING_MODELS, models)
- self.repo_put(self.repo.CONFIRMATION, confirm)
- self.set_valid("Step Completed")
- else:
- self.set_invalid("Please complete the fields highlighted in red to continue")
diff --git a/src/workflow/forms.py b/src/workflow/forms.py
deleted file mode 100644
index 62abad6..0000000
--- a/src/workflow/forms.py
+++ /dev/null
@@ -1,489 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
-# Copyright (c) 2020 Sawyer Bergeron, Sean Smith, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-import django.forms as forms
-from django.forms import widgets, ValidationError
-from django.utils.safestring import mark_safe
-from django.template.loader import render_to_string
-from django.forms.widgets import NumberInput
-
-import json
-import urllib
-
-from account.models import Lab
-from account.models import UserProfile
-from resource_inventory.models import (
- OPNFVRole,
- Installer,
- Scenario
-)
-from resource_inventory.resource_manager import ResourceManager
-from booking.lib import get_user_items, get_user_field_opts
-
-
-class SearchableSelectMultipleWidget(widgets.SelectMultiple):
- template_name = 'dashboard/searchable_select_multiple.html'
-
- def __init__(self, attrs=None):
- self.items = attrs['items']
- self.show_from_noentry = attrs['show_from_noentry']
- self.show_x_results = attrs['show_x_results']
- self.results_scrollable = attrs['results_scrollable']
- self.selectable_limit = attrs['selectable_limit']
- self.placeholder = attrs['placeholder']
- self.name = attrs['name']
- self.initial = attrs.get("initial", [])
-
- super(SearchableSelectMultipleWidget, self).__init__()
-
- def render(self, name, value, attrs=None, renderer=None):
-
- context = self.get_context(attrs)
- return mark_safe(render_to_string(self.template_name, context))
-
- def get_context(self, attrs):
- return {
- 'items': self.items,
- 'name': self.name,
- 'show_from_noentry': self.show_from_noentry,
- 'show_x_results': self.show_x_results,
- 'results_scrollable': self.results_scrollable,
- 'selectable_limit': self.selectable_limit,
- 'placeholder': self.placeholder,
- 'initial': self.initial,
- }
-
-
-class SearchableSelectMultipleField(forms.Field):
- def __init__(self, *args, required=True, widget=None, label=None, disabled=False,
- items=None, queryset=None, show_from_noentry=True, show_x_results=-1,
- results_scrollable=False, selectable_limit=-1, placeholder="search here",
- name="searchable_select", initial=[], **kwargs):
- """
- From the documentation.
-
- # required -- Boolean that specifies whether the field is required.
- # True by default.
- # widget -- A Widget class, or instance of a Widget class, that should
- # be used for this Field when displaying it. Each Field has a
- # default Widget that it'll use if you don't specify this. In
- # most cases, the default widget is TextInput.
- # label -- A verbose name for this field, for use in displaying this
- # field in a form. By default, Django will use a "pretty"
- # version of the form field name, if the Field is part of a
- # Form.
- # initial -- A value to use in this Field's initial display. This value
- # is *not* used as a fallback if data isn't given.
- # help_text -- An optional string to use as "help text" for this Field.
- # error_messages -- An optional dictionary to override the default
- # messages that the field will raise.
- # show_hidden_initial -- Boolean that specifies if it is needed to render a
- # hidden widget with initial value after widget.
- # validators -- List of additional validators to use
- # localize -- Boolean that specifies if the field should be localized.
- # disabled -- Boolean that specifies whether the field is disabled, that
- # is its widget is shown in the form but not editable.
- # label_suffix -- Suffix to be added to the label. Overrides
- # form's label_suffix.
- """
- self.widget = widget
- if self.widget is None:
- self.widget = SearchableSelectMultipleWidget(
- attrs={
- 'items': items,
- 'initial': [obj.id for obj in initial],
- 'show_from_noentry': show_from_noentry,
- 'show_x_results': show_x_results,
- 'results_scrollable': results_scrollable,
- 'selectable_limit': selectable_limit,
- 'placeholder': placeholder,
- 'name': name,
- 'disabled': disabled
- }
- )
- self.disabled = disabled
- self.queryset = queryset
- self.selectable_limit = selectable_limit
-
- super().__init__(disabled=disabled, **kwargs)
-
- self.required = required
-
- def clean(self, data):
- data = data[0]
- if not data:
- if self.required:
- raise ValidationError("Nothing was selected")
- else:
- return []
- try:
- data_as_list = json.loads(data)
- except json.decoder.JSONDecodeError:
- data_as_list = None
- if not data_as_list:
- raise ValidationError("Contents Not JSON")
- if self.selectable_limit != -1:
- if len(data_as_list) > self.selectable_limit:
- raise ValidationError("Too many items were selected")
-
- items = []
- for elem in data_as_list:
- items.append(self.queryset.get(id=elem))
-
- return items
-
-
-class SearchableSelectAbstractForm(forms.Form):
- def __init__(self, *args, queryset=None, initial=[], **kwargs):
- self.queryset = queryset
- items = self.generate_items(self.queryset)
- options = self.generate_options()
-
- super(SearchableSelectAbstractForm, self).__init__(*args, **kwargs)
- self.fields['searchable_select'] = SearchableSelectMultipleField(
- initial=initial,
- items=items,
- queryset=self.queryset,
- **options
- )
-
- def get_validated_bundle(self):
- bundles = self.cleaned_data['searchable_select']
- if len(bundles) < 1: # don't need to check for >1, as field does that for us
- raise ValidationError("No bundle was selected")
- return bundles[0]
-
- def generate_items(self, queryset):
- raise Exception("SearchableSelectAbstractForm does not implement concrete generate_items()")
-
- def generate_options(self, disabled=False):
- return {
- 'show_from_noentry': True,
- 'show_x_results': -1,
- 'results_scrollable': True,
- 'selectable_limit': 1,
- 'placeholder': 'Search for a Bundle',
- 'name': 'searchable_select',
- 'disabled': False
- }
-
-
-class SWConfigSelectorForm(SearchableSelectAbstractForm):
- def generate_items(self, queryset):
- items = {}
-
- for bundle in queryset:
- items[bundle.id] = {
- 'expanded_name': bundle.name,
- 'small_name': bundle.owner.username,
- 'string': bundle.description,
- 'id': bundle.id
- }
-
- return items
-
-
-class OPNFVSelectForm(SearchableSelectAbstractForm):
- def generate_items(self, queryset):
- items = {}
-
- for config in queryset:
- items[config.id] = {
- 'expanded_name': config.name,
- 'small_name': config.bundle.owner.username,
- 'string': config.description,
- 'id': config.id
- }
-
- return items
-
-
-class ResourceSelectorForm(SearchableSelectAbstractForm):
- def generate_items(self, queryset):
- items = {}
-
- for bundle in queryset:
- items[bundle.id] = {
- 'expanded_name': bundle.name,
- 'small_name': bundle.owner.username,
- 'string': bundle.description,
- 'id': bundle.id
- }
-
- return items
-
-
-class BookingMetaForm(forms.Form):
- # Django Form class for Book a Pod
- length = forms.IntegerField(
- widget=NumberInput(
- attrs={
- "type": "range",
- 'min': "1",
- "max": "21",
- "value": "1"
- }
- )
- )
- purpose = forms.CharField(max_length=1000)
- project = forms.CharField(max_length=400)
- info_file = forms.CharField(max_length=1000, required=False)
- deploy_opnfv = forms.BooleanField(required=False)
-
- def __init__(self, *args, user_initial=[], owner=None, **kwargs):
- super(BookingMetaForm, self).__init__(**kwargs)
-
- self.fields['users'] = SearchableSelectMultipleField(
- queryset=UserProfile.objects.select_related('user').exclude(user=owner),
- initial=user_initial,
- items=get_user_items(exclude=owner),
- required=False,
- **get_user_field_opts()
- )
-
-
-class MultipleSelectFilterWidget(forms.Widget):
- def __init__(self, *args, display_objects=None, filter_items=None, neighbors=None, **kwargs):
- super(MultipleSelectFilterWidget, self).__init__(*args, **kwargs)
- self.display_objects = display_objects
- self.filter_items = filter_items
- self.neighbors = neighbors
- self.template_name = "dashboard/multiple_select_filter_widget.html"
-
- def render(self, name, value, attrs=None, renderer=None):
- context = self.get_context(name, value, attrs)
- html = render_to_string(self.template_name, context=context)
- return mark_safe(html)
-
- def get_context(self, name, value, attrs):
- return {
- 'display_objects': self.display_objects,
- 'neighbors': self.neighbors,
- 'filter_items': self.filter_items,
- 'initial_value': value
- }
-
-
-class MultipleSelectFilterField(forms.Field):
-
- def __init__(self, **kwargs):
- self.initial = kwargs.get("initial")
- super().__init__(**kwargs)
-
- def to_python(self, value):
- try:
- return json.loads(value)
- except json.decoder.JSONDecodeError:
- pass
- raise ValidationError("content is not valid JSON")
-
-
-class FormUtils:
- @staticmethod
- def getLabData(multiple_hosts=False, user=None):
- """
- Get all labs and thier host profiles, returns a serialized version the form can understand.
-
- Could be rewritten with a related query to make it faster
- """
- # javascript truthy variables
- true = 1
- false = 0
- if multiple_hosts:
- multiple_hosts = true
- else:
- multiple_hosts = false
- labs = {}
- resources = {}
- items = {}
- neighbors = {}
- for lab in Lab.objects.all():
- lab_node = {
- 'id': "lab_" + str(lab.lab_user.id),
- 'model_id': lab.lab_user.id,
- 'name': lab.name,
- 'description': lab.description,
- 'selected': false,
- 'selectable': true,
- 'follow': multiple_hosts,
- 'multiple': false,
- 'class': 'lab',
- 'available_resources': json.dumps(lab.get_available_resources())
- }
-
- items[lab_node['id']] = lab_node
- neighbors[lab_node['id']] = []
- labs[lab_node['id']] = lab_node
-
- for template in ResourceManager.getInstance().getAvailableResourceTemplates(lab, user):
- resource_node = {
- 'form': {"name": "host_name", "type": "text", "placeholder": "hostname"},
- 'id': "resource_" + str(template.id),
- 'model_id': template.id,
- 'name': template.name,
- 'description': template.description,
- 'selected': false,
- 'selectable': true,
- 'follow': false,
- 'multiple': multiple_hosts,
- 'class': 'resource',
- 'required_resources': json.dumps(template.get_required_resources())
- }
-
- if multiple_hosts:
- resource_node['values'] = [] # place to store multiple values
-
- items[resource_node['id']] = resource_node
- neighbors[lab_node['id']].append(resource_node['id'])
-
- if resource_node['id'] not in neighbors:
- neighbors[resource_node['id']] = []
-
- neighbors[resource_node['id']].append(lab_node['id'])
- resources[resource_node['id']] = resource_node
-
- display_objects = [("lab", labs.values()), ("resource", resources.values())]
-
- context = {
- 'display_objects': display_objects,
- 'neighbors': neighbors,
- 'filter_items': items
- }
-
- return context
-
-
-class HardwareDefinitionForm(forms.Form):
-
- def __init__(self, user, *args, **kwargs):
- super(HardwareDefinitionForm, self).__init__(*args, **kwargs)
- attrs = FormUtils.getLabData(multiple_hosts=True, user=user)
- self.fields['filter_field'] = MultipleSelectFilterField(
- widget=MultipleSelectFilterWidget(**attrs)
- )
-
-
-class PodDefinitionForm(forms.Form):
-
- fields = ["xml"]
- xml = forms.CharField()
-
-
-class ResourceMetaForm(forms.Form):
-
- bundle_name = forms.CharField(label="POD Name")
- bundle_description = forms.CharField(label="POD Description", widget=forms.Textarea, max_length=1000)
-
-
-class GenericHostMetaForm(forms.Form):
-
- host_profile = forms.CharField(label="Host Type", disabled=True, required=False)
- host_name = forms.CharField(label="Host Name")
-
-
-class NetworkDefinitionForm(forms.Form):
- def __init__(self, *args, **kwargs):
- super(NetworkDefinitionForm, self).__init__(**kwargs)
-
-
-class NetworkConfigurationForm(forms.Form):
- def __init__(self, *args, **kwargs):
- super(NetworkConfigurationForm).__init__(**kwargs)
-
-
-class HostSoftwareDefinitionForm(forms.Form):
- # Django Form class for Design a Pod
- host_name = forms.CharField(
- max_length=200,
- disabled=False,
- required=True
- )
- headnode = forms.BooleanField(required=False, widget=forms.HiddenInput)
-
- def __init__(self, *args, **kwargs):
- imageQS = kwargs.pop("imageQS")
- super(HostSoftwareDefinitionForm, self).__init__(*args, **kwargs)
- self.fields['image'] = forms.ModelChoiceField(queryset=imageQS)
-
-
-class WorkflowSelectionForm(forms.Form):
- fields = ['workflow']
-
- empty_permitted = False
-
- workflow = forms.ChoiceField(
- choices=(
- (0, 'Booking'),
- (1, 'Resource Bundle'),
- (2, 'Software Configuration')
- ),
- label="Choose Workflow",
- initial='booking',
- required=True
- )
-
-
-class SnapshotHostSelectForm(forms.Form):
- host = forms.CharField()
-
-
-class BasicMetaForm(forms.Form):
- name = forms.CharField()
- description = forms.CharField(widget=forms.Textarea)
-
-
-class ConfirmationForm(forms.Form):
- fields = ['confirm']
-
- confirm = forms.ChoiceField(
- choices=(
- (False, "Cancel"),
- (True, "Confirm")
- )
- )
-
-
-def validate_step(value):
- if value not in ["prev", "next", "current"]:
- raise ValidationError(str(value) + " is not allowed")
-
-
-def validate_step_form(value):
- try:
- urllib.parse.unquote_plus(value)
- except Exception:
- raise ValidationError("Value is not url encoded data")
-
-
-class ManagerForm(forms.Form):
- step = forms.CharField(widget=forms.widgets.HiddenInput, validators=[validate_step])
- step_form = forms.CharField(widget=forms.widgets.HiddenInput, validators=[validate_step_form])
- # other fields?
-
-
-class OPNFVSelectionForm(forms.Form):
- installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=True)
- scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=True)
-
-
-class OPNFVNetworkRoleForm(forms.Form):
- role = forms.CharField(max_length=200, disabled=True, required=False)
-
- def __init__(self, *args, config_bundle, **kwargs):
- super(OPNFVNetworkRoleForm, self).__init__(*args, **kwargs)
- self.fields['network'] = forms.ModelChoiceField(
- queryset=config_bundle.bundle.networks.all()
- )
-
-
-class OPNFVHostRoleForm(forms.Form):
- host_name = forms.CharField(max_length=200, disabled=True, required=False)
- role = forms.ModelChoiceField(queryset=OPNFVRole.objects.all().order_by("name").distinct("name"))
diff --git a/src/workflow/models.py b/src/workflow/models.py
deleted file mode 100644
index e065202..0000000
--- a/src/workflow/models.py
+++ /dev/null
@@ -1,693 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.template.loader import get_template
-from django.http import HttpResponse
-from django.utils import timezone
-from django.db import transaction
-
-import yaml
-import requests
-
-from workflow.forms import ConfirmationForm
-from api.models import JobFactory
-from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException
-from resource_inventory.models import Image, OPNFVConfig, ResourceOPNFVConfig, NetworkRole
-from resource_inventory.resource_manager import ResourceManager
-from resource_inventory.pdf_templater import PDFTemplater
-from notifier.manager import NotificationHandler
-from booking.models import Booking
-
-
-class BookingAuthManager():
- """
- Verifies Booking Authorization.
-
- Class to verify that the user is allowed to book the requested resource
- The user must input a url to the INFO.yaml file to prove that they are the ptl of
- an approved project if they are booking a multi-node pod.
- This class parses the url and checks the logged in user against the info file.
- """
-
- LFN_PROJECTS = ["opnfv"] # TODO
-
- def parse_github_url(self, url):
- project_leads = []
- try:
- parts = url.split("/")
- if "http" in parts[0]: # the url include http(s)://
- parts = parts[2:]
- if parts[-1] != "INFO.yaml":
- return None
- if parts[0] not in ["github.com", "raw.githubusercontent.com"]:
- return None
- if parts[1] not in self.LFN_PROJECTS:
- return None
- # now to download and parse file
- if parts[3] == "blob":
- parts[3] = "raw"
- url = "https://" + "/".join(parts)
- info_file = requests.get(url, timeout=15).text
- info_parsed = yaml.load(info_file)
- ptl = info_parsed.get('project_lead')
- if ptl:
- project_leads.append(ptl)
- sub_ptl = info_parsed.get("subproject_lead")
- if sub_ptl:
- project_leads.append(sub_ptl)
-
- except Exception:
- pass
-
- return project_leads
-
- def parse_gerrit_url(self, url):
- project_leads = []
- try:
- halfs = url.split("?")
- parts = halfs[0].split("/")
- args = halfs[1].split(";")
- if "http" in parts[0]: # the url include http(s)://
- parts = parts[2:]
- if "f=INFO.yaml" not in args:
- return None
- if "gerrit.opnfv.org" not in parts[0]:
- return None
- try:
- i = args.index("a=blob")
- args[i] = "a=blob_plain"
- except ValueError:
- pass
- # recreate url
- halfs[1] = ";".join(args)
- halfs[0] = "/".join(parts)
- # now to download and parse file
- url = "https://" + "?".join(halfs)
- info_file = requests.get(url, timeout=15).text
- info_parsed = yaml.load(info_file)
- ptl = info_parsed.get('project_lead')
- if ptl:
- project_leads.append(ptl)
- sub_ptl = info_parsed.get("subproject_lead")
- if sub_ptl:
- project_leads.append(sub_ptl)
-
- except Exception:
- return None
-
- return project_leads
-
- def parse_opnfv_git_url(self, url):
- project_leads = []
- try:
- parts = url.split("/")
- if "http" in parts[0]: # the url include http(s)://
- parts = parts[2:]
- if "INFO.yaml" not in parts[-1]:
- return None
- if "git.opnfv.org" not in parts[0]:
- return None
- if parts[-2] == "tree":
- parts[-2] = "plain"
- # now to download and parse file
- url = "https://" + "/".join(parts)
- info_file = requests.get(url, timeout=15).text
- info_parsed = yaml.load(info_file)
- ptl = info_parsed.get('project_lead')
- if ptl:
- project_leads.append(ptl)
- sub_ptl = info_parsed.get("subproject_lead")
- if sub_ptl:
- project_leads.append(sub_ptl)
-
- except Exception:
- return None
-
- return project_leads
-
- def parse_url(self, info_url):
- """
- Parse the project URL.
-
- Gets the INFO.yaml file from the project and returns the PTL info.
- """
- if "github" in info_url:
- return self.parse_github_url(info_url)
-
- if "gerrit.opnfv.org" in info_url:
- return self.parse_gerrit_url(info_url)
-
- if "git.opnfv.org" in info_url:
- return self.parse_opnfv_git_url(info_url)
-
- def booking_allowed(self, booking, repo):
- """
- Assert the current Booking Policy.
-
- This is the method that will have to change whenever the booking policy changes in the Infra
- group / LFN. This is a nice isolation of that administration crap
- currently checks if the booking uses multiple servers. if it does, then the owner must be a PTL,
- which is checked using the provided info file
- """
- if booking.owner.userprofile.booking_privledge:
- return True # admin override for this user
- if Booking.objects.filter(owner=booking.owner, end__gt=timezone.now()).count() >= 3:
- return False
- if len(booking.resource.template.get_required_resources()) < 2:
- return True # if they only have one server, we dont care
- if repo.BOOKING_INFO_FILE not in repo.el:
- return False # INFO file not provided
- ptl_info = self.parse_url(repo.el.get(repo.BOOKING_INFO_FILE))
- for ptl in ptl_info:
- if ptl['email'] == booking.owner.userprofile.email_addr:
- return True
- return False
-
-
-class WorkflowStepStatus(object):
- """
- Poor man's enum for the status of a workflow step.
-
- The steps in a workflow are not completed (UNTOUCHED)
- or they have been completed correctly (VALID) or they were filled out
- incorrectly (INVALID)
- """
-
- UNTOUCHED = 0
- INVALID = 100
- VALID = 200
-
-
-class WorkflowStep(object):
- template = 'bad_request.html'
- title = "Generic Step"
- description = "You were led here by mistake"
- short_title = "error"
- metastep = None
- # phasing out metastep:
-
- valid = WorkflowStepStatus.UNTOUCHED
- message = ""
-
- enabled = True
-
- def cleanup(self):
- raise Exception("WorkflowStep subclass of type " + str(type(self)) + " has no concrete implemented cleanup() method")
-
- def enable(self):
- if not self.enabled:
- self.enabled = True
-
- def disable(self):
- if self.enabled:
- self.cleanup()
- self.enabled = False
-
- def set_invalid(self, message, code=WorkflowStepStatus.INVALID):
- self.valid = code
- self.message = message
-
- def set_valid(self, message, code=WorkflowStepStatus.VALID):
- self.valid = code
- self.message = message
-
- def to_json(self):
- return {
- 'title': self.short_title,
- 'enabled': self.enabled,
- 'valid': self.valid,
- 'message': self.message,
- }
-
- def __init__(self, id, repo=None):
- self.repo = repo
- self.id = id
-
- def get_context(self):
- context = {}
- context['step_number'] = self.repo_get('steps')
- context['active_step'] = self.repo_get('active_step')
- context['render_correct'] = "true"
- context['step_title'] = self.title
- context['description'] = self.description
- return context
-
- def render(self, request):
- return HttpResponse(self.render_to_string(request))
-
- def render_to_string(self, request):
- template = get_template(self.template)
- return template.render(self.get_context(), request)
-
- def post(self, post_content, user):
- raise Exception("WorkflowStep subclass of type " + str(type(self)) + " has no concrete post() method")
-
- def validate(self, request):
- pass
-
- def repo_get(self, key, default=None):
- return self.repo.get(key, default, self.id)
-
- def repo_put(self, key, value):
- return self.repo.put(key, value, self.id)
-
-
-"""
-subclassing notes:
- subclasses have to define the following class attributes:
- self.select_repo_key: where the selected "object" or "bundle" is to be placed in the repo
- self.form: the form to be used
- alert_bundle_missing(): what message to display if a user does not select/selects an invalid object
- get_form_queryset(): generate a queryset to be used to filter available items for the field
- get_page_context(): return simple context such as page header and other info
-"""
-
-
-class AbstractSelectOrCreate(WorkflowStep):
- template = 'dashboard/genericselect.html'
- title = "Select a Bundle"
- short_title = "select"
- description = "Generic bundle selector step"
-
- select_repo_key = None
- form = None # subclasses are expected to use a form that is a subclass of SearchableSelectGenericForm
-
- def alert_bundle_missing(self): # override in subclasses to change message if field isn't filled out
- self.set_invalid("Please select a valid bundle")
-
- def post(self, post_data, user):
- form = self.form(post_data, queryset=self.get_form_queryset())
- if form.is_valid():
- bundle = form.get_validated_bundle()
- if not bundle:
- self.alert_bundle_missing()
- return
- self.repo_put(self.select_repo_key, bundle)
- self.put_confirm_info(bundle)
- self.set_valid("Step Completed")
- else:
- self.alert_bundle_missing()
-
- def get_context(self):
- default = []
-
- bundle = self.repo_get(self.select_repo_key, False)
- if bundle:
- default.append(bundle)
-
- form = self.form(queryset=self.get_form_queryset(), initial=default)
-
- context = {'form': form, **self.get_page_context()}
- context.update(super().get_context())
-
- return context
-
- def get_page_context():
- return {
- 'select_type': 'generic',
- 'select_type_title': 'Generic Bundle'
- }
-
-
-class Confirmation_Step(WorkflowStep):
- template = 'workflow/confirm.html'
- title = "Confirm Changes"
- description = "Does this all look right?"
-
- short_title = "confirm"
-
- def get_context(self):
- context = super(Confirmation_Step, self).get_context()
- context['form'] = ConfirmationForm()
- # Summary of submitted form data shown on the 'confirm' step of the workflow
- confirm_details = "\nPod:\n Name: '{name}'\n Description: '{desc}'\nLab: '{lab}'".format(
- name=self.repo_get(self.repo.CONFIRMATION)['resource']['name'],
- desc=self.repo_get(self.repo.CONFIRMATION)['resource']['description'],
- lab=self.repo_get(self.repo.CONFIRMATION)['template']['lab'])
- confirm_details += "\nResources:"
- for i, device in enumerate(self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS)['resources']):
- confirm_details += "\n " + str(device) + ": " + str(self.repo_get(self.repo.CONFIRMATION)['template']['resources'][i]['profile'])
- context['confirmation_info'] = confirm_details
- if self.valid == WorkflowStepStatus.VALID:
- context["confirm_succeeded"] = "true"
-
- return context
-
- def flush_to_db(self):
- errors = self.repo.make_models()
- if errors:
- return errors
-
- def post(self, post_data, user):
- form = ConfirmationForm(post_data)
- if form.is_valid():
- data = form.cleaned_data['confirm']
- if data == "True":
- errors = self.flush_to_db()
- if errors:
- self.set_invalid("ERROR OCCURRED: " + errors)
- else:
- self.set_valid("Confirmed")
-
- elif data == "False":
- self.repo.cancel()
- self.set_valid("Canceled")
- else:
- self.set_invalid("Bad Form Contents")
-
- else:
- self.set_invalid("Bad Form Contents")
-
-
-class Repository():
-
- EDIT = "editing"
- MODELS = "models"
- RESOURCE_SELECT = "resource_select"
- CONFIRMATION = "confirmation"
- SELECTED_RESOURCE_TEMPLATE = "selected resource template pk"
- SELECTED_OPNFV_CONFIG = "selected opnfv deployment config"
- RESOURCE_TEMPLATE_MODELS = "generic_resource_template_models"
- RESOURCE_TEMPLATE_INFO = "generic_resource_template_info"
- BOOKING = "booking"
- LAB = "lab"
- RCONFIG_LAST_HOSTLIST = "resource_configuration_network_previous_hostlist"
- BOOKING_FORMS = "booking_forms"
- SWCONF_HOSTS = "swconf_hosts"
- BOOKING_MODELS = "booking models"
- CONFIG_MODELS = "configuration bundle models"
- OPNFV_MODELS = "opnfv configuration models"
- SESSION_USER = "session owner user account"
- SESSION_MANAGER = "session manager for current session"
- VALIDATED_MODEL_GRB = "valid grb config model instance in db"
- VALIDATED_MODEL_CONFIG = "valid config model instance in db"
- VALIDATED_MODEL_BOOKING = "valid booking model instance in db"
- VLANS = "a list of vlans"
- SNAPSHOT_MODELS = "the models for snapshotting"
- SNAPSHOT_BOOKING_ID = "the booking id for snapshotting"
- SNAPSHOT_NAME = "the name of the snapshot"
- SNAPSHOT_DESC = "description of the snapshot"
- BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
-
- # new keys for migration to using ResourceTemplates:
- RESOURCE_TEMPLATE_MODELS = "current working model of resource template"
-
- # migratory elements of segmented workflow
- # each of these is the end result of a different workflow.
- HAS_RESULT = "whether or not workflow has a result"
- RESULT_KEY = "key for target index that result will be put into in parent"
- RESULT = "result object from workflow"
-
- def get_child_defaults(self):
- return_tuples = []
- for key in [self.SELECTED_RESOURCE_TEMPLATE, self.SESSION_USER]:
- return_tuples.append((key, self.el.get(key)))
- return return_tuples
-
- def set_defaults(self, defaults):
- for key, value in defaults:
- self.el[key] = value
-
- def get(self, key, default, id):
-
- self.add_get_history(key, id)
- return self.el.get(key, default)
-
- def put(self, key, val, id):
- self.add_put_history(key, id)
- self.el[key] = val
-
- def add_get_history(self, key, id):
- self.add_history(key, id, self.get_history)
-
- def add_put_history(self, key, id):
- self.add_history(key, id, self.put_history)
-
- def add_history(self, key, id, history):
- if key not in history:
- history[key] = [id]
- else:
- history[key].append(id)
-
- def cancel(self):
- if self.RESOURCE_TEMPLATE_MODELS in self.el:
- models = self.el[self.RESOURCE_TEMPLATE_MODELS]
- if models['template'].temporary:
- models['template'].delete()
- # deleting current template should cascade delete all
- # necessary related models
-
- def make_models(self):
- if self.SNAPSHOT_MODELS in self.el:
- errors = self.make_snapshot()
- if errors:
- return errors
-
- # if GRB WF, create it
- if self.RESOURCE_TEMPLATE_MODELS in self.el:
- errors = self.make_generic_resource_bundle()
- if errors:
- return errors
- else:
- self.el[self.HAS_RESULT] = True
- self.el[self.RESULT_KEY] = self.SELECTED_RESOURCE_TEMPLATE
- return
-
- if self.OPNFV_MODELS in self.el:
- errors = self.make_opnfv_config()
- if errors:
- return errors
- else:
- self.el[self.HAS_RESULT] = True
- self.el[self.RESULT_KEY] = self.SELECTED_OPNFV_CONFIG
-
- if self.BOOKING_MODELS in self.el:
- errors = self.make_booking()
- if errors:
- return errors
- # create notification
- booking = self.el[self.BOOKING_MODELS]['booking']
- NotificationHandler.notify_new_booking(booking)
-
- def make_snapshot(self):
- owner = self.el[self.SESSION_USER]
- models = self.el[self.SNAPSHOT_MODELS]
- image = models.get('snapshot', Image())
- booking_id = self.el.get(self.SNAPSHOT_BOOKING_ID)
- if not booking_id:
- return "SNAP, No booking ID provided"
- booking = Booking.objects.get(pk=booking_id)
- if booking.start > timezone.now() or booking.end < timezone.now():
- return "Booking is not active"
- name = self.el.get(self.SNAPSHOT_NAME)
- if not name:
- return "SNAP, no name provided"
- host = models.get('host')
- if not host:
- return "SNAP, no host provided"
- description = self.el.get(self.SNAPSHOT_DESC, "")
- image.from_lab = booking.lab
- image.name = name
- image.description = description
- image.public = False
- image.lab_id = -1
- image.owner = owner
- image.host_type = host.profile
- image.save()
- try:
- current_image = host.config.image
- image.os = current_image.os
- image.save()
- except Exception:
- pass
- JobFactory.makeSnapshotTask(image, booking, host)
-
- self.el[self.RESULT] = image
- self.el[self.HAS_RESULT] = True
-
- def make_generic_resource_bundle(self):
- owner = self.el[self.SESSION_USER]
- if self.RESOURCE_TEMPLATE_MODELS in self.el:
- models = self.el[self.RESOURCE_TEMPLATE_MODELS]
- models['template'].owner = owner
- models['template'].temporary = False
- models['template'].save()
- self.el[self.RESULT] = models['template']
- self.el[self.HAS_RESULT] = True
- return False
-
- else:
- return "GRB no models given. CODE:0x0001"
-
- def make_software_config_bundle(self):
- models = self.el[self.CONFIG_MODELS]
- if 'bundle' in models:
- bundle = models['bundle']
- bundle.bundle = self.el[self.SELECTED_RESOURCE_TEMPLATE]
- try:
- bundle.save()
- except Exception as e:
- return "SWC, saving bundle generated exception: " + str(e) + "CODE:0x0007"
-
- else:
- return "SWC, no bundle in models. CODE:0x0006"
- if 'host_configs' in models:
- host_configs = models['host_configs']
- for host_config in host_configs:
- host_config.template = host_config.template
- host_config.profile = host_config.profile
- try:
- host_config.save()
- except Exception as e:
- return "SWC, saving host configs generated exception: " + str(e) + "CODE:0x0009"
- else:
- return "SWC, no host configs in models. CODE:0x0008"
- if 'opnfv' in models:
- opnfvconfig = models['opnfv']
- opnfvconfig.bundle = opnfvconfig.bundle
- if opnfvconfig.scenario not in opnfvconfig.installer.sup_scenarios.all():
- return "SWC, scenario not supported by installer. CODE:0x000d"
- try:
- opnfvconfig.save()
- except Exception as e:
- return "SWC, saving opnfv config generated exception: " + str(e) + "CODE:0x000b"
- else:
- pass
-
- self.el[self.RESULT] = bundle
- return False
-
- @transaction.atomic # TODO: Rewrite transactions with savepoints at user level for all workflows
- def make_booking(self):
- models = self.el[self.BOOKING_MODELS]
- owner = self.el[self.SESSION_USER]
-
- if 'booking' in models:
- booking = models['booking']
- else:
- return "BOOK, no booking model exists. CODE:0x000f"
-
- selected_grb = None
-
- if self.SELECTED_RESOURCE_TEMPLATE in self.el:
- selected_grb = self.el[self.SELECTED_RESOURCE_TEMPLATE]
- else:
- return "BOOK, no selected resource. CODE:0x000e"
-
- if not booking.start:
- return "BOOK, booking has no start. CODE:0x0010"
- if not booking.end:
- return "BOOK, booking has no end. CODE:0x0011"
- if booking.end <= booking.start:
- return "BOOK, end before/same time as start. CODE:0x0012"
-
- if 'collaborators' in models:
- collaborators = models['collaborators']
- else:
- return "BOOK, collaborators not defined. CODE:0x0013"
- try:
- res_manager = ResourceManager.getInstance()
- resource_bundle = res_manager.instantiateTemplate(selected_grb)
- except ResourceAvailabilityException as e:
- return "BOOK, requested resources are not available. Exception: " + str(e) + " CODE:0x0014"
- except ModelValidationException as e:
- return "Error encountered when saving bundle. " + str(e) + " CODE: 0x001b"
-
- booking.resource = resource_bundle
- booking.owner = owner
- booking.lab = selected_grb.lab
-
- is_allowed = BookingAuthManager().booking_allowed(booking, self)
- if not is_allowed:
- return "BOOK, you are not allowed to book the requested resources"
-
- try:
- booking.save()
- except Exception as e:
- return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0015"
-
- for collaborator in collaborators:
- booking.collaborators.add(collaborator)
-
- try:
- booking.pdf = PDFTemplater.makePDF(booking)
- booking.save()
- except Exception as e:
- return "BOOK, failed to create Pod Desriptor File: " + str(e)
-
- try:
- JobFactory.makeCompleteJob(booking)
- except Exception as e:
- return "BOOK, serializing for api generated exception: " + str(e) + " CODE:0xFFFF"
-
- try:
- booking.save()
- except Exception as e:
- return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016"
-
- self.el[self.RESULT] = booking
- self.el[self.HAS_RESULT] = True
-
- def make_opnfv_config(self):
- opnfv_models = self.el[self.OPNFV_MODELS]
- config_bundle = self.el[self.SELECTED_CONFIG_BUNDLE]
- if not config_bundle:
- return "No Configuration bundle selected"
- info = opnfv_models.get("meta", {})
- name = info.get("name", False)
- desc = info.get("description", False)
- if not (name and desc):
- return "No name or description given"
- installer = opnfv_models['installer_chosen']
- if not installer:
- return "No OPNFV Installer chosen"
- scenario = opnfv_models['scenario_chosen']
- if not scenario:
- return "No OPNFV Scenario chosen"
-
- opnfv_config = OPNFVConfig.objects.create(
- bundle=config_bundle,
- name=name,
- description=desc,
- installer=installer,
- scenario=scenario
- )
-
- network_roles = opnfv_models['network_roles']
- for net_role in network_roles:
- opnfv_config.networks.add(
- NetworkRole.objects.create(
- name=net_role['role'],
- network=net_role['network']
- )
- )
-
- host_roles = opnfv_models['host_roles']
- for host_role in host_roles:
- config = config_bundle.hostConfigurations.get(
- host__resource__name=host_role['host_name']
- )
- ResourceOPNFVConfig.objects.create(
- role=host_role['role'],
- host_config=config,
- opnfv_config=opnfv_config
- )
-
- self.el[self.RESULT] = opnfv_config
- self.el[self.HAS_RESULT] = True
-
- def __init__(self):
- self.el = {}
- self.el[self.CONFIRMATION] = {}
- self.el["active_step"] = 0
- self.el[self.HAS_RESULT] = False
- self.el[self.RESULT] = None
- self.get_history = {}
- self.put_history = {}
diff --git a/src/workflow/opnfv_workflow.py b/src/workflow/opnfv_workflow.py
deleted file mode 100644
index 6ffc91d..0000000
--- a/src/workflow/opnfv_workflow.py
+++ /dev/null
@@ -1,292 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.forms import formset_factory
-
-from workflow.models import WorkflowStep, AbstractSelectOrCreate
-from resource_inventory.models import ResourceTemplate, OPNFV_SETTINGS
-from workflow.forms import OPNFVSelectionForm, OPNFVNetworkRoleForm, OPNFVHostRoleForm, SWConfigSelectorForm, BasicMetaForm
-
-
-class OPNFV_Resource_Select(AbstractSelectOrCreate):
- title = "Select Software Configuration"
- description = "Choose the software bundle you wish to use as a base for your OPNFV configuration"
- short_title = "software config"
- form = SWConfigSelectorForm
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.select_repo_key = self.repo.SELECTED_CONFIG_BUNDLE
-
- def get_form_queryset(self):
- user = self.repo_get(self.repo.SESSION_USER)
- qs = ResourceTemplate.objects.filter(owner=user)
- return qs
-
- def put_confirm_info(self, bundle):
- confirm_dict = self.repo_get(self.repo.CONFIRMATION)
- confirm_dict['software bundle'] = bundle.name
- confirm_dict['hardware POD'] = bundle.bundle.name
- self.repo_put(self.repo.CONFIRMATION, confirm_dict)
-
- def get_page_context(self):
- return {
- 'select_type': 'swconfig',
- 'select_type_title': 'Software Config',
- 'addable_type_num': 2
- }
-
-
-class Pick_Installer(WorkflowStep):
- template = 'config_bundle/steps/pick_installer.html'
- title = 'Pick OPNFV Installer'
- description = 'Choose which OPNFV installer to use'
- short_title = "opnfv installer"
- modified_key = "installer_step"
-
- def update_confirmation(self):
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- installer = models.get("installer_chosen")
- scenario = models.get("scenario_chosen")
- if not (installer and scenario):
- return
- confirm['installer'] = installer.name
- confirm['scenario'] = scenario.name
- self.repo_put(self.repo.CONFIRMATION, confirm)
-
- def get_context(self):
- context = super(Pick_Installer, self).get_context()
-
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- initial = {
- "installer": models.get("installer_chosen"),
- "scenario": models.get("scenario_chosen")
- }
-
- context["form"] = OPNFVSelectionForm(initial=initial)
- return context
-
- def post(self, post_data, user):
- form = OPNFVSelectionForm(post_data)
- if form.is_valid():
- installer = form.cleaned_data['installer']
- scenario = form.cleaned_data['scenario']
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- models['installer_chosen'] = installer
- models['scenario_chosen'] = scenario
- self.repo_put(self.repo.OPNFV_MODELS, models)
- self.update_confirmation()
- self.set_valid("Step Completed")
- else:
- self.set_invalid("Please select an Installer and Scenario")
-
-
-class Assign_Network_Roles(WorkflowStep):
- template = 'config_bundle/steps/assign_network_roles.html'
- title = 'Pick Network Roles'
- description = 'Choose what role each network should get'
- short_title = "network roles"
- modified_key = "net_roles_step"
-
- """
- to do initial filling, repo should have a "network_roles" array with the following structure for each element:
- {
- "role": ,
- "network":
- }
- """
- def create_netformset(self, roles, config_bundle, data=None):
- roles_initial = []
- set_roles = self.repo_get(self.repo.OPNFV_MODELS, {}).get("network_roles")
- if set_roles:
- roles_initial = set_roles
- else:
- for role in OPNFV_SETTINGS.NETWORK_ROLES:
- roles_initial.append({"role": role})
-
- Formset = formset_factory(OPNFVNetworkRoleForm, extra=0)
- kwargs = {
- "initial": roles_initial,
- "form_kwargs": {"config_bundle": config_bundle}
- }
- formset = None
- if data:
- formset = Formset(data, **kwargs)
- else:
- formset = Formset(**kwargs)
- return formset
-
- def get_context(self):
- context = super(Assign_Network_Roles, self).get_context()
- config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
- if config_bundle is None:
- context["unavailable"] = True
- return context
-
- roles = OPNFV_SETTINGS.NETWORK_ROLES
- formset = self.create_netformset(roles, config_bundle)
- context['formset'] = formset
-
- return context
-
- def update_confirmation(self):
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- roles = models.get("network_roles")
- if not roles:
- return
- confirm['network roles'] = {}
- for role in roles:
- confirm['network roles'][role['role']] = role['network'].name
- self.repo_put(self.repo.CONFIRMATION, confirm)
-
- def post(self, post_data, user):
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
- roles = OPNFV_SETTINGS.NETWORK_ROLES
- net_role_formset = self.create_netformset(roles, config_bundle, data=post_data)
- if net_role_formset.is_valid():
- results = []
- for form in net_role_formset:
- results.append({
- "role": form.cleaned_data['role'],
- "network": form.cleaned_data['network']
- })
- models['network_roles'] = results
- self.set_valid("Completed")
- self.repo_put(self.repo.OPNFV_MODELS, models)
- self.update_confirmation()
- else:
- self.set_invalid("Please complete all fields")
-
-
-class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in sw workflow, merge the two?
- template = 'config_bundle/steps/assign_host_roles.html'
- title = 'Pick Host Roles'
- description = "Choose the role each machine will have in your OPNFV pod"
- short_title = "host roles"
- modified_key = "host_roles_step"
-
- def create_host_role_formset(self, hostlist=[], data=None):
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- host_roles = models.get("host_roles", [])
- if not host_roles:
- for host in hostlist:
- initial = {"host_name": host.resource.name}
- host_roles.append(initial)
- models['host_roles'] = host_roles
- self.repo_put(self.repo.OPNFV_MODELS, models)
-
- HostFormset = formset_factory(OPNFVHostRoleForm, extra=0)
-
- kwargs = {"initial": host_roles}
- formset = None
- if data:
- formset = HostFormset(data, **kwargs)
- else:
- formset = HostFormset(**kwargs)
-
- return formset
-
- def get_context(self):
- context = super(Assign_Host_Roles, self).get_context()
- config = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
- if config is None:
- context['error'] = "Please select a Configuration on the first step"
-
- formset = self.create_host_role_formset(hostlist=config.bundle.getResources())
- context['formset'] = formset
-
- return context
-
- def get_host_role_mapping(self, host_roles, hostname):
- for obj in host_roles:
- if hostname == obj['host_name']:
- return obj
- return None
-
- def update_confirmation(self):
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- roles = models.get("host_roles")
- if not roles:
- return
- confirm['host roles'] = {}
- for role in roles:
- confirm['host roles'][role['host_name']] = role['role'].name
- self.repo_put(self.repo.CONFIRMATION, confirm)
-
- def post(self, post_data, user):
- formset = self.create_host_role_formset(data=post_data)
-
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- host_roles = models.get("host_roles", [])
-
- has_jumphost = False
- if formset.is_valid():
- for form in formset:
- hostname = form.cleaned_data['host_name']
- role = form.cleaned_data['role']
- mapping = self.get_host_role_mapping(host_roles, hostname)
- mapping['role'] = role
- if "jumphost" in role.name.lower():
- has_jumphost = True
-
- models['host_roles'] = host_roles
- self.repo_put(self.repo.OPNFV_MODELS, models)
- self.update_confirmation()
-
- if not has_jumphost:
- self.set_invalid('Must have at least one "Jumphost" per POD')
- else:
- self.set_valid("Completed")
- else:
- self.set_invalid("Please complete all fields")
-
-
-class MetaInfo(WorkflowStep):
- template = 'config_bundle/steps/config_software.html'
- title = "Other Info"
- description = "Give your software config a name, description, and other stuff"
- short_title = "config info"
-
- def get_context(self):
- context = super(MetaInfo, self).get_context()
-
- initial = self.repo_get(self.repo.OPNFV_MODELS, {}).get("meta", {})
- context["form"] = BasicMetaForm(initial=initial)
- return context
-
- def update_confirmation(self):
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- meta = models.get("meta")
- if not meta:
- return
- confirm['name'] = meta['name']
- confirm['description'] = meta['description']
- self.repo_put(self.repo.CONFIRMATION, confirm)
-
- def post(self, post_data, user):
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- info = models.get("meta", {})
-
- form = BasicMetaForm(post_data)
- if form.is_valid():
- info['name'] = form.cleaned_data['name']
- info['description'] = form.cleaned_data['description']
- models['meta'] = info
- self.repo_put(self.repo.OPNFV_MODELS, models)
- self.update_confirmation()
- self.set_valid("Complete")
- else:
- self.set_invalid("Please correct the errors shown below")
- self.repo_put(self.repo.OPNFV_MODELS, models)
diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py
deleted file mode 100644
index 4e288b5..0000000
--- a/src/workflow/resource_bundle_workflow.py
+++ /dev/null
@@ -1,614 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.conf import settings
-from django.forms import formset_factory
-from django.core.exceptions import ValidationError
-
-from typing import List
-
-import re
-import json
-from xml.dom import minidom
-import traceback
-
-from workflow.models import WorkflowStep
-from account.models import Lab
-from workflow.forms import (
- HardwareDefinitionForm,
- NetworkDefinitionForm,
- ResourceMetaForm,
- HostSoftwareDefinitionForm,
-)
-from resource_inventory.models import (
- ResourceTemplate,
- ResourceConfiguration,
- InterfaceConfiguration,
- Network,
- NetworkConnection,
- Image,
-)
-from dashboard.exceptions import (
- InvalidVlanConfigurationException,
- NetworkExistsException,
- ResourceAvailabilityException
-)
-
-import logging
-logger = logging.getLogger(__name__)
-
-
-class Define_Hardware(WorkflowStep):
-
- template = 'resource/steps/define_hardware.html'
- title = "Define Hardware"
- description = "Choose the type and amount of machines you want"
- short_title = "hosts"
-
- def __init__(self, *args, **kwargs):
- self.form = None
- super().__init__(*args, **kwargs)
-
- def get_context(self):
- context = super(Define_Hardware, self).get_context()
- user = self.repo_get(self.repo.SESSION_USER)
- context['form'] = self.form or HardwareDefinitionForm(user)
- return context
-
- def update_models(self, data):
- data = data['filter_field']
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- models['resources'] = [] # This will always clear existing data when this step changes
- models['connections'] = []
- models['interfaces'] = {}
- if "template" not in models:
- template = ResourceTemplate.objects.create(temporary=True)
- models['template'] = template
-
- resource_data = data['resource']
-
- new_template = models['template']
-
- public_network = Network.objects.create(name="public", bundle=new_template, is_public=True)
-
- all_networks = {public_network.id: public_network}
-
- for resource_template_dict in resource_data.values():
- id = resource_template_dict['id']
- old_template = ResourceTemplate.objects.get(id=id)
-
- # instantiate genericHost and store in repo
- for _ in range(0, resource_template_dict['count']):
- resource_configs = old_template.resourceConfigurations.all()
- for config in resource_configs:
- # need to save now for connections to refer to it later
- new_config = ResourceConfiguration.objects.create(
- profile=config.profile,
- image=config.image,
- name=config.name,
- template=new_template)
-
- for interface_config in config.interface_configs.all():
- new_interface_config = InterfaceConfiguration.objects.create(
- profile=interface_config.profile,
- resource_config=new_config)
-
- for connection in interface_config.connections.all():
- network = None
- if connection.network.is_public:
- network = public_network
- else:
- # check if network is known
- if connection.network.id not in all_networks:
- # create matching one
- new_network = Network(
- name=connection.network.name + "_" + str(new_config.id),
- bundle=new_template,
- is_public=False)
- new_network.save()
-
- all_networks[connection.network.id] = new_network
-
- network = all_networks[connection.network.id]
-
- new_connection = NetworkConnection(
- network=network,
- vlan_is_tagged=connection.vlan_is_tagged)
-
- new_interface_config.save() # can't do later because M2M on next line
- new_connection.save()
-
- new_interface_config.connections.add(new_connection)
-
- unique_resource_ref = new_config.name + "_" + str(new_config.id)
- if unique_resource_ref not in models['interfaces']:
- models['interfaces'][unique_resource_ref] = []
- models['interfaces'][unique_resource_ref].append(interface_config)
-
- models['resources'].append(new_config)
-
- models['networks'] = all_networks
-
- # add selected lab to models
- for lab_dict in data['lab'].values():
- if lab_dict['selected']:
- models['template'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
- models['template'].save()
- break # if somehow we get two 'true' labs, we only use one
-
- # return to repo
- self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models)
-
- def update_confirmation(self):
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- if "template" not in confirm:
- confirm['template'] = {}
- confirm['template']['resources'] = []
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- if 'template' in models:
- for resource in models['template'].getConfigs():
- host_dict = {"name": resource.name, "profile": resource.profile.name}
- confirm['template']['resources'].append(host_dict)
- if "template" in models:
- confirm['template']['lab'] = models['template'].lab.lab_user.username
- self.repo_put(self.repo.CONFIRMATION, confirm)
-
- def post(self, post_data, user):
- try:
- user = self.repo_get(self.repo.SESSION_USER)
- self.form = HardwareDefinitionForm(user, post_data)
- if self.form.is_valid():
- self.update_models(self.form.cleaned_data)
- self.update_confirmation()
- self.set_valid("Step Completed")
- else:
- self.set_invalid("Please complete the fields highlighted in red to continue")
- except Exception as e:
- print("Caught exception: " + str(e))
- traceback.print_exc()
- self.form = None
- self.set_invalid("Please select a lab.")
-
-
-class Define_Software(WorkflowStep):
- template = 'config_bundle/steps/define_software.html'
- title = "Pick Software"
- description = "Choose the opnfv and image of your machines"
- short_title = "host config"
-
- def build_filter_data(self, hosts_data):
- """
- Build list of Images to filter out.
-
- returns a 2D array of images to exclude
- based on the ordering of the passed
- hosts_data
- """
-
- filter_data = []
- user = self.repo_get(self.repo.SESSION_USER)
- lab = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS)['template'].lab
- for i, host_data in enumerate(hosts_data):
- host = ResourceConfiguration.objects.get(pk=host_data['host_id'])
- wrong_owner = Image.objects.exclude(owner=user).exclude(public=True)
- wrong_host = Image.objects.exclude(architecture=host.profile.architecture)
- wrong_lab = Image.objects.exclude(from_lab=lab)
- excluded_images = wrong_owner | wrong_host | wrong_lab
- filter_data.append([])
- for image in excluded_images:
- filter_data[i].append(image.pk)
- return filter_data
-
- def create_hostformset(self, hostlist, data=None):
- hosts_initial = []
- configs = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("resources")
- if configs:
- for i in range(len(configs)):
- default_name = 'laas-node'
- if i > 0:
- default_name = default_name + "-" + str(i + 1)
- hosts_initial.append({
- 'host_id': configs[i].id,
- 'host_name': default_name,
- 'headnode': False,
- 'image': configs[i].image
- })
- else:
- for host in hostlist:
- hosts_initial.append({
- 'host_id': host.id,
- 'host_name': host.name
- })
-
- HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
- filter_data = self.build_filter_data(hosts_initial)
-
- class SpecialHostFormset(HostFormset):
- def get_form_kwargs(self, index):
- kwargs = super(SpecialHostFormset, self).get_form_kwargs(index)
- if index is not None:
- kwargs['imageQS'] = Image.objects.exclude(pk__in=filter_data[index])
- return kwargs
-
- if data:
- return SpecialHostFormset(data, initial=hosts_initial)
- return SpecialHostFormset(initial=hosts_initial)
-
- def get_host_list(self, grb=None):
- return self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS).get("resources")
-
- def get_context(self):
- context = super(Define_Software, self).get_context()
-
- context["formset"] = self.create_hostformset(self.get_host_list())
-
- return context
-
- def post(self, post_data, user):
- hosts = self.get_host_list()
- formset = self.create_hostformset(hosts, data=post_data)
- has_headnode = False
- if formset.is_valid():
- for i, form in enumerate(formset):
- host = hosts[i]
- image = form.cleaned_data['image']
- hostname = form.cleaned_data['host_name']
- headnode = form.cleaned_data['headnode']
- if headnode:
- has_headnode = True
- host.is_head_node = headnode
- host.name = hostname
- host.image = image
- # RFC921: They must start with a letter, end with a letter or digit and have only letters or digits or hyphen as interior characters
- if bool(re.match("^[A-Za-z0-9-]*$", hostname)) is False:
- self.set_invalid("Device names must only contain alphanumeric characters and dashes.")
- return
- if not hostname[0].isalpha() or not hostname[-1].isalnum():
- self.set_invalid("Device names must start with a letter and end with a letter or digit.")
- return
- for j in range(i):
- if j != i and hostname == hosts[j].name:
- self.set_invalid("Devices must have unique names. Please try again.")
- return
- host.save()
-
- if not has_headnode and len(hosts) > 0:
- self.set_invalid("No headnode. Please set a headnode.")
- return
-
- self.set_valid("Completed")
- else:
- self.set_invalid("Please complete all fields.")
-
-
-class Define_Nets(WorkflowStep):
- template = 'resource/steps/pod_definition.html'
- title = "Define Networks"
- description = "Use the tool below to draw the network topology of your POD"
- short_title = "networking"
- form = NetworkDefinitionForm
-
- def get_vlans(self):
- vlans = self.repo_get(self.repo.VLANS)
- if vlans:
- return vlans
- # try to grab some vlans from lab
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- if "bundle" not in models:
- return None
- lab = models['bundle'].lab
- if lab is None or lab.vlan_manager is None:
- return None
- try:
- vlans = lab.vlan_manager.get_vlans(count=lab.vlan_manager.block_size)
- self.repo_put(self.repo.VLANS, vlans)
- return vlans
- except Exception:
- return None
-
- def make_mx_network_dict(self, network):
- return {
- 'id': network.id,
- 'name': network.name,
- 'public': network.is_public
- }
-
- def make_mx_resource_dict(self, resource_config):
- resource_dict = {
- 'id': resource_config.id,
- 'interfaces': [],
- 'value': {
- 'name': resource_config.name,
- 'id': resource_config.id,
- 'description': resource_config.profile.description
- }
- }
-
- for interface_config in resource_config.interface_configs.all():
- connections = []
- for connection in interface_config.connections.all():
- connections.append({'tagged': connection.vlan_is_tagged, 'network': connection.network.id})
-
- interface_dict = {
- "id": interface_config.id,
- "name": interface_config.profile.name,
- "description": "speed: " + str(interface_config.profile.speed) + "M\ntype: " + interface_config.profile.nic_type,
- "connections": connections
- }
-
- resource_dict['interfaces'].append(interface_dict)
-
- return resource_dict
-
- def make_mx_host_dict(self, generic_host):
- host = {
- 'id': generic_host.profile.name,
- 'interfaces': [],
- 'value': {
- "name": generic_host.profile.name,
- "description": generic_host.profile.description
- }
- }
- for iface in generic_host.profile.interfaceprofile.all():
- host['interfaces'].append({
- "name": iface.name,
- "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type
- })
- return host
-
- # first step guards this one, so can't get here without at least empty
- # models being populated by step one
- def get_context(self):
- context = super(Define_Nets, self).get_context()
- context.update({
- 'form': NetworkDefinitionForm(),
- 'debug': settings.DEBUG,
- 'resources': {},
- 'networks': {},
- 'vlans': [],
- # remove others
- 'hosts': [],
- 'added_hosts': [],
- 'removed_hosts': []
- })
-
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS) # infallible, guarded by prior step
- for resource in models['resources']:
- d = self.make_mx_resource_dict(resource)
- context['resources'][d['id']] = d
-
- for network in models['networks'].values():
- d = self.make_mx_network_dict(network)
- context['networks'][d['id']] = d
-
- return context
-
- def post(self, post_data, user):
- try:
- xmlData = post_data.get("xml")
- self.updateModels(xmlData)
- # update model with xml
- self.set_valid("Networks applied successfully")
- except ResourceAvailabilityException:
- self.set_invalid("Public network not availble")
- except Exception as e:
- traceback.print_exc()
- self.set_invalid("An error occurred when applying networks: " + str(e))
-
- def resetNetworks(self, networks: List[Network]): # potentially just pass template here?
- for network in networks:
- network.delete()
-
- def updateModels(self, xmlData):
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- given_hosts = None
- interfaces = None
- networks = None
- try:
- given_hosts, interfaces, networks = self.parseXml(xmlData)
- except Exception as e:
- print("tried to parse Xml, got exception instead:")
- print(e)
-
- existing_rconfig_list = models.get("resources", [])
- existing_rconfigs = {} # maps id to host
- for rconfig in existing_rconfig_list:
- existing_rconfigs["host_" + str(rconfig.id)] = rconfig
-
- bundle = models.get("template") # hard fail if not in repo
-
- self.resetNetworks(models['networks'].values())
- models['networks'] = {}
-
- for net_id, net in networks.items():
- network = Network.objects.create(
- name=net['name'],
- bundle=bundle,
- is_public=net['public'])
-
- models['networks'][net_id] = network
- network.save()
-
- for hostid, given_host in given_hosts.items():
- for ifaceId in given_host['interfaces']:
- iface = interfaces[ifaceId]
-
- iface_config = InterfaceConfiguration.objects.get(id=iface['config_id'])
- if iface_config.resource_config.template.id != bundle.id:
- raise ValidationError("User does not own the template they are editing")
-
- for connection in iface['connections']:
- network_id = connection['network']
- net = models['networks'][network_id]
- connection = NetworkConnection(vlan_is_tagged=connection['tagged'], network=net)
- connection.save()
- iface_config.connections.add(connection)
- iface_config.save()
- self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models)
-
- def decomposeXml(self, xmlString):
- """
- Translate XML into useable data.
-
- This function takes in an xml doc from our front end
- and returns dictionaries that map cellIds to the xml
- nodes themselves. There is no unpacking of the
- xml objects, just grouping and organizing
- """
- connections = {}
- networks = {}
- hosts = {}
- interfaces = {}
- network_ports = {}
-
- xmlDom = minidom.parseString(xmlString)
- root = xmlDom.documentElement.firstChild
- for cell in root.childNodes:
- cellId = cell.getAttribute('id')
- group = cellId.split("_")[0]
- parentGroup = cell.getAttribute("parent").split("_")[0]
- # place cell into correct group
-
- if cell.getAttribute("edge"):
- connections[cellId] = cell
-
- elif "network" in group:
- networks[cellId] = cell
-
- elif "host" in group:
- hosts[cellId] = cell
-
- elif "host" in parentGroup:
- interfaces[cellId] = cell
-
- # make network ports also map to thier network
- elif "network" in parentGroup:
- network_ports[cellId] = cell.getAttribute("parent") # maps port ID to net ID
-
- return connections, networks, hosts, interfaces, network_ports
-
- # serialize and deserialize xml from mxGraph
- def parseXml(self, xmlString):
- networks = {} # maps net name to network object
- hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks
- interfaces = {} # maps id -> interface
- untagged_ifaces = set() # used to check vlan config
- network_names = set() # used to check network names
- xml_connections, xml_nets, xml_hosts, xml_ifaces, xml_ports = self.decomposeXml(xmlString)
-
- # parse Hosts
- for cellId, cell in xml_hosts.items():
- cell_json_str = cell.getAttribute("value")
- cell_json = json.loads(cell_json_str)
- host = {"interfaces": [], "name": cellId, "hostname": cell_json['name']}
- hosts[cellId] = host
-
- # parse networks
- for cellId, cell in xml_nets.items():
- escaped_json_str = cell.getAttribute("value")
- json_str = escaped_json_str.replace('"', '"')
- net_info = json.loads(json_str)
- net_name = net_info['name']
- public = net_info['public']
- if net_name in network_names:
- raise NetworkExistsException("Non unique network name found")
- network = {"name": net_name, "public": public, "id": cellId}
- networks[cellId] = network
- network_names.add(net_name)
-
- # parse interfaces
- for cellId, cell in xml_ifaces.items():
- parentId = cell.getAttribute('parent')
- cell_json_str = cell.getAttribute("value")
- cell_json = json.loads(cell_json_str)
- iface = {"graph_id": cellId, "connections": [], "config_id": cell_json['id'], "profile_name": cell_json['name']}
- hosts[parentId]['interfaces'].append(cellId)
- interfaces[cellId] = iface
-
- # parse connections
- for cellId, cell in xml_connections.items():
- escaped_json_str = cell.getAttribute("value")
- json_str = escaped_json_str.replace('"', '"')
- attributes = json.loads(json_str)
- tagged = attributes['tagged']
- interface = None
- network = None
- src = cell.getAttribute("source")
- tgt = cell.getAttribute("target")
- if src in interfaces:
- interface = interfaces[src]
- network = networks[xml_ports[tgt]]
- else:
- interface = interfaces[tgt]
- network = networks[xml_ports[src]]
-
- if not tagged:
- if interface['config_id'] in untagged_ifaces:
- raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
- untagged_ifaces.add(interface['config_id'])
-
- # add connection to interface
- interface['connections'].append({"tagged": tagged, "network": network['id']})
-
- return hosts, interfaces, networks
-
-
-class Resource_Meta_Info(WorkflowStep):
- template = 'resource/steps/meta_info.html'
- title = "Extra Info"
- description = "Please fill out the rest of the information about your resource"
- short_title = "pod info"
-
- def update_confirmation(self):
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- if "template" not in confirm:
- confirm['template'] = {}
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- if "template" in models:
- confirm['template']['description'] = models['template'].description
- confirm['template']['name'] = models['template'].name
- self.repo_put(self.repo.CONFIRMATION, confirm)
-
- def get_context(self):
- context = super(Resource_Meta_Info, self).get_context()
- name = ""
- desc = ""
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, None)
- bundle = models['template']
- if bundle:
- name = bundle.name
- desc = bundle.description
- context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
- return context
-
- def post(self, post_data, user):
- form = ResourceMetaForm(post_data)
- if form.is_valid():
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- name = form.cleaned_data['bundle_name']
- desc = form.cleaned_data['bundle_description']
- bundle = models['template'] # infallible
- bundle.name = name
- bundle.description = desc
- bundle.save()
- self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models)
- confirm = self.repo_get(self.repo.CONFIRMATION)
- if "resource" not in confirm:
- confirm['resource'] = {}
- confirm_info = confirm['resource']
- confirm_info["name"] = name
- tmp = desc
- if len(tmp) > 60:
- tmp = tmp[:60] + "..."
- confirm_info["description"] = tmp
- self.repo_put(self.repo.CONFIRMATION, confirm)
- self.set_valid("Step Completed")
- else:
- self.set_invalid("Please complete all fields.")
diff --git a/src/workflow/snapshot_workflow.py b/src/workflow/snapshot_workflow.py
deleted file mode 100644
index c0e2052..0000000
--- a/src/workflow/snapshot_workflow.py
+++ /dev/null
@@ -1,116 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.utils import timezone
-import json
-
-from booking.models import Booking
-from resource_inventory.models import ResourceQuery, Image
-from workflow.models import WorkflowStep
-from workflow.forms import BasicMetaForm, SnapshotHostSelectForm
-
-
-class Select_Host_Step(WorkflowStep):
- template = "snapshot_workflow/steps/select_host.html"
- title = "Select Host"
- description = "Choose which machine you want to snapshot"
- short_title = "host"
-
- def get_context(self):
- context = super(Select_Host_Step, self).get_context()
- context['form'] = SnapshotHostSelectForm()
- booking_hosts = {}
- now = timezone.now()
- user = self.repo_get(self.repo.SESSION_USER)
- bookings = Booking.objects.filter(start__lt=now, end__gt=now, owner=user)
- for booking in bookings:
- booking_hosts[booking.id] = {}
- booking_hosts[booking.id]['purpose'] = booking.purpose
- booking_hosts[booking.id]['start'] = booking.start.strftime("%Y-%m-%d")
- booking_hosts[booking.id]['end'] = booking.end.strftime("%Y-%m-%d")
- booking_hosts[booking.id]['hosts'] = []
- for genericHost in booking.resource.template.getResources():
- booking_hosts[booking.id]['hosts'].append({"name": genericHost.resource.name})
-
- context['booking_hosts'] = booking_hosts
-
- chosen_host = self.repo_get(self.repo.SNAPSHOT_MODELS, {}).get("host")
- if chosen_host:
- chosen = {}
- chosen['booking_id'] = self.repo_get(self.repo.SNAPSHOT_BOOKING_ID)
- chosen['hostname'] = chosen_host.template.resource.name
- context['chosen'] = chosen
- return context
-
- def post(self, post_data, user):
- host_data = post_data.get("host")
- if not host_data:
- self.set_invalid("Please select a host")
- return
- host = json.loads(host_data)
- if 'name' not in host or 'booking' not in host:
- self.set_invalid("Invalid host selected")
- return
- name = host['name']
- booking_id = host['booking']
- booking = Booking.objects.get(pk=booking_id)
- host = ResourceQuery.get(bundle=booking.resource, template__resource__name=name)
- models = self.repo_get(self.repo.SNAPSHOT_MODELS, {})
- if "host" not in models:
- models['host'] = host
- if 'snapshot' not in models:
- models['snapshot'] = Image()
- self.repo_put(self.repo.SNAPSHOT_MODELS, models)
- self.repo_put(self.repo.SNAPSHOT_BOOKING_ID, booking_id)
-
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- snap_confirm = confirm.get("snapshot", {})
- snap_confirm['host'] = name
- confirm['snapshot'] = snap_confirm
- self.repo_put(self.repo.CONFIRMATION, confirm)
- self.set_valid("Success")
-
-
-class Image_Meta_Step(WorkflowStep):
- template = "snapshot_workflow/steps/meta.html"
- title = "Additional Information"
- description = "We need some more info"
- short_title = "info"
-
- def get_context(self):
- context = super(Image_Meta_Step, self).get_context()
- name = self.repo_get(self.repo.SNAPSHOT_NAME, False)
- desc = self.repo_get(self.repo.SNAPSHOT_DESC, False)
- form = None
- if name and desc:
- form = BasicMetaForm(initial={"name": name, "description": desc})
- else:
- form = BasicMetaForm()
- context['form'] = form
- return context
-
- def post(self, post_data, user):
- form = BasicMetaForm(post_data)
- if form.is_valid():
- name = form.cleaned_data['name']
- self.repo_put(self.repo.SNAPSHOT_NAME, name)
- description = form.cleaned_data['description']
- self.repo_put(self.repo.SNAPSHOT_DESC, description)
-
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- snap_confirm = confirm.get("snapshot", {})
- snap_confirm['name'] = name
- snap_confirm['description'] = description
- confirm['snapshot'] = snap_confirm
- self.repo_put(self.repo.CONFIRMATION, confirm)
-
- self.set_valid("Success")
- else:
- self.set_invalid("Please Fill out the Form")
diff --git a/src/workflow/tests/__init__.py b/src/workflow/tests/__init__.py
deleted file mode 100644
index 4f0437d..0000000
--- a/src/workflow/tests/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
diff --git a/src/workflow/tests/constants.py b/src/workflow/tests/constants.py
deleted file mode 100644
index f94a949..0000000
--- a/src/workflow/tests/constants.py
+++ /dev/null
@@ -1,198 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-POD_XML = """
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"""
diff --git a/src/workflow/tests/test_fixtures.py b/src/workflow/tests/test_fixtures.py
deleted file mode 100644
index fe16be7..0000000
--- a/src/workflow/tests/test_fixtures.py
+++ /dev/null
@@ -1,2 +0,0 @@
-
-MX_GRAPH_MODEL = ' '
diff --git a/src/workflow/tests/test_steps.py b/src/workflow/tests/test_steps.py
deleted file mode 100644
index ba27313..0000000
--- a/src/workflow/tests/test_steps.py
+++ /dev/null
@@ -1,269 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-"""
-This file tests basic functionality of each step class.
-
-More in depth case coverage of WorkflowStep.post() must happen elsewhere.
-"""
-
-import json
-from unittest import SkipTest, mock
-
-from django.test import TestCase, RequestFactory
-from dashboard.testing_utils import make_lab, make_user, make_os, \
- make_complete_host_profile, make_opnfv_role, make_image, make_grb, \
- make_config_bundle, make_host, make_user_profile, make_generic_host
-from workflow import resource_bundle_workflow
-from workflow import booking_workflow
-from workflow import sw_bundle_workflow
-from workflow.models import Repository
-from workflow.tests import test_fixtures
-
-
-class TestConfig:
- """
- Basic class to instantiate and hold reference.
-
- to models we will need often
- """
-
- def __init__(self, usr=None):
- self.lab = make_lab()
- self.user = usr or make_user()
- self.os = make_os()
- self.host_prof = make_complete_host_profile(self.lab)
- self.host = make_host(self.host_prof, self.lab, name="host1")
-
- # pod description as required by testing lib
- self.topology = {
- "host1": {
- "type": self.host_prof,
- "role": make_opnfv_role(),
- "image": make_image(self.lab, 3, self.user, self.os, self.host_prof),
- "nets": [
- [{"name": "public", "tagged": True, "public": True}]
- ]
- }
- }
- self.grb = make_grb(self.topology, self.user, self.lab)[0]
- self.generic_host = make_generic_host(self.grb, self.host_prof, "host1")
-
-
-class StepTestCase(TestCase):
-
- # after setUp is called, this should be an instance of a step
- step = None
-
- post_data = {} # subclasses will set this
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- cls.factory = RequestFactory()
- cls.user_prof = make_user_profile()
- cls.user = cls.user_prof.user
-
- def setUp(self):
- super().setUp()
- if self.step is None:
- raise SkipTest("Step instance not given")
- repo = Repository()
- self.add_to_repo(repo)
- self.step = self.step(1, repo)
-
- def assertCorrectPostBehavior(self, post_data):
- """
- Stub for validating step behavior on POST request.
-
- allows subclasses to override and make assertions about
- the side effects of self.step.post()
- post_data is the data passed into post()
- """
- return
-
- def add_to_repo(self, repo):
- """
- Stub for modifying the step's repo.
-
- This method is a hook that allows subclasses to modify
- the contents of the repo before the step is created.
- """
- return
-
- def assertValidHtml(self, html_str):
- """
- Assert that html_str is a valid html fragment.
-
- However, I know of no good way of doing this in python
- """
- self.assertTrue(isinstance(html_str, str))
- self.assertGreater(len(html_str), 0)
-
- def test_render_to_string(self):
- request = self.factory.get("/workflow/manager/")
- request.user = self.user
- response_html = self.step.render_to_string(request)
- self.assertValidHtml(response_html)
-
- def test_post(self, data=None):
- post_data = data or self.post_data
- self.step.post(post_data, self.user)
- self.assertCorrectPostBehavior(data)
-
-
-class SelectStepTestCase(StepTestCase):
- # ID of model to be sent to the step's form
- # can be an int or a list of ints
- obj_id = -1
-
- def setUp(self):
- super().setUp()
-
- try:
- iter(self.obj_id)
- except TypeError:
- self.obj_id = [self.obj_id]
-
- field_data = json.dumps(self.obj_id)
- self.post_data = {
- "searchable_select": [field_data]
- }
-
-
-class DefineHardwareTestCase(StepTestCase):
- step = resource_bundle_workflow.Define_Hardware
- post_data = {
- "filter_field": {
- "lab": {
- "lab_35": {"selected": True, "id": 35}},
- "host": {
- "host_1": {"selected": True, "id": 1}}
- }
- }
-
-
-class DefineNetworkTestCase(StepTestCase):
- step = resource_bundle_workflow.Define_Nets
- post_data = {"xml": test_fixtures.MX_GRAPH_MODEL}
-
-
-class ResourceMetaTestCase(StepTestCase):
- step = resource_bundle_workflow.Resource_Meta_Info
- post_data = {
- "bundle_name": "my_bundle",
- "bundle_description": "My Bundle"
- }
-
-
-class BookingResourceTestCase(SelectStepTestCase):
- step = booking_workflow.Booking_Resource_Select
-
- def add_to_repo(self, repo):
- repo.el[repo.SESSION_USER] = self.user
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- conf = TestConfig(usr=cls.user)
- cls.obj_id = conf.grb.id
-
-
-class SoftwareSelectTestCase(SelectStepTestCase):
- step = booking_workflow.SWConfig_Select
-
- def add_to_repo(self, repo):
- repo.el[repo.SESSION_USER] = self.user
- repo.el[repo.SELECTED_RESOURCE_TEMPLATE] = self.conf.grb
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- cls.conf = TestConfig(usr=cls.user)
- host_map = {"host1": cls.conf.generic_host}
- config_bundle = make_config_bundle(cls.conf.grb, cls.conf.user, cls.conf.topology, host_map)[0]
- cls.obj_id = config_bundle.id
-
-
-class OPNFVSelectTestCase(SelectStepTestCase):
- step = booking_workflow.OPNFV_Select
-
- def add_to_repo(self, repo):
- repo.el[repo.SELECTED_CONFIG_BUNDLE] = self.config_bundle
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- conf = TestConfig(usr=cls.user)
- host_map = {"host1": conf.generic_host}
- cls.config_bundle, opnfv_config = make_config_bundle(conf.grb, conf.user, conf.topology, host_map)
- cls.obj_id = opnfv_config.id
-
-
-class BookingMetaTestCase(StepTestCase):
- step = booking_workflow.Booking_Meta
- post_data = {
- "length": 14,
- "purpose": "Testing",
- "project": "Lab as a Service",
- "users": ["[-1]"]
- }
-
- def add_to_repo(self, repo):
- repo.el[repo.SESSION_MANAGER] = mock.MagicMock()
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- new_user = make_user(username="collaborator", email="different@mail.com")
- new_user_prof = make_user_profile(user=new_user)
- data = "[" + str(new_user_prof.id) + "]" # list of IDs
- cls.post_data['users'] = [data]
-
-
-class ConfigResourceSelectTestCase(SelectStepTestCase):
- step = sw_bundle_workflow.SWConf_Resource_Select
-
- def add_to_repo(self, repo):
- repo.el[repo.SESSION_USER] = self.user
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- conf = TestConfig(usr=cls.user)
- cls.obj_id = conf.grb.id
-
-
-class DefineSoftwareTestCase(StepTestCase):
- step = sw_bundle_workflow.Define_Software
- post_data = {
- "form-0-image": 1,
- "headnode": 1,
- "form-0-headnode": "",
- "form-TOTAL_FORMS": 1,
- "form-INITIAL_FORMS": 1,
- "form-MIN_NUM_FORMS": 0,
- "form-MAX_NUM_FORMS": 1000,
- }
-
- def add_to_repo(self, repo):
- repo.el[repo.SELECTED_RESOURCE_TEMPLATE] = self.conf.grb
-
- @classmethod
- def setUpTestData(cls):
- super().setUpTestData()
- cls.conf = TestConfig(usr=cls.user)
-
-
-class ConfigSoftwareTestCase(StepTestCase):
- step = sw_bundle_workflow.Config_Software
- post_data = {
- "name": "config_bundle",
- "description": "My Config Bundle"
- }
diff --git a/src/workflow/tests/test_workflows.py b/src/workflow/tests/test_workflows.py
deleted file mode 100644
index 995d699..0000000
--- a/src/workflow/tests/test_workflows.py
+++ /dev/null
@@ -1,99 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-from unittest import SkipTest
-from django.test import TestCase
-from workflow.workflow_factory import WorkflowFactory
-
-
-"""
-To start a workflow:
- POST to /wf/workflow {"add":
-
- types:
- 0 - Booking
- 1 - Resource
- 2 - Config
-
-To remove a workflow:
- POST to /wf/workflow {"cancel": ""}
-"""
-
-
-class WorkflowTestCase(TestCase):
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
- raise SkipTest("These tests are no good")
-
- def setUp(self):
- self.clear_workflow()
- self.create_workflow(self.wf_type)
-
- def create_workflow(self, wf_type):
- self.clear_workflow()
-
- # creates workflow on backend
- self.client.post("/", {"create": int(wf_type)}) # TODO: verify content type, etc
-
- def clear_workflow(self):
- session = self.client.session
- for k in session.keys():
- del session[k]
- session.save()
-
- def render_steps(self):
- """Retrieve each step individually at /wf/workflow/step=."""
- for i in range(self.step_count):
- # renders the step itself, not in an iframe
- exception = None
- try:
- response = self.client.get("/wf/workflow/", {"step": str(i)})
- self.assertLess(response.status_code, 300)
- except Exception as e:
- exception = e
-
- self.assertIsNone(exception)
-
-
-class BookingWorkflowTestCase(WorkflowTestCase):
-
- @classmethod
- def setUpClass(cls):
- super(BookingWorkflowTestCase, cls).setUpClass()
- cls.step_count = len(WorkflowFactory.booking_steps)
- cls.wf_type = 0
-
- def test_steps_render(self):
- super(BookingWorkflowTestCase, self).render_steps()
-
-
-class ResourceWorkflowTestCase(WorkflowTestCase):
-
- @classmethod
- def setUpClass(cls):
- super(ResourceWorkflowTestCase, cls).setUpClass()
- cls.step_count = len(WorkflowFactory.resource_steps)
- cls.wf_type = 1
-
- def test_steps_render(self):
- super(ResourceWorkflowTestCase, self).render_steps()
-
-
-class ConfigWorkflowTestCase(WorkflowTestCase):
-
- @classmethod
- def setUpClass(cls):
- super(ConfigWorkflowTestCase, cls).setUpClass()
- cls.step_count = len(WorkflowFactory.config_steps)
- cls.wf_type = 2
-
- def test_steps_render(self):
- super(ConfigWorkflowTestCase, self).render_steps()
diff --git a/src/workflow/urls.py b/src/workflow/urls.py
deleted file mode 100644
index b1b95a7..0000000
--- a/src/workflow/urls.py
+++ /dev/null
@@ -1,23 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.conf.urls import url
-
-from workflow.views import manager_view, viewport_view, add_workflow, remove_workflow, create_workflow
-
-app_name = 'workflow'
-urlpatterns = [
-
- url(r'^manager/$', manager_view, name='manager'),
- url(r'^add/$', add_workflow, name='add_workflow'),
- url(r'^create/$', create_workflow, name='create_workflow'),
- url(r'^pop/$', remove_workflow, name='remove_workflow'),
- url(r'^$', viewport_view, name='viewport')
-]
diff --git a/src/workflow/views.py b/src/workflow/views.py
deleted file mode 100644
index fb311b7..0000000
--- a/src/workflow/views.py
+++ /dev/null
@@ -1,112 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.http import HttpResponse
-from django.shortcuts import render
-from account.models import Lab
-
-import uuid
-
-from workflow.workflow_manager import ManagerTracker, SessionManager
-
-import logging
-logger = logging.getLogger(__name__)
-
-
-def attempt_auth(request):
- try:
- manager = ManagerTracker.managers[request.session['manager_session']]
-
- return manager
-
- except KeyError:
- return None
-
-
-def remove_workflow(request):
- manager = attempt_auth(request)
-
- if not manager:
- return no_workflow(request)
-
- has_more_workflows, result = manager.pop_workflow(discard=True)
-
- if not has_more_workflows: # this was the last workflow, so delete the reference to it in the tracker
- del ManagerTracker.managers[request.session['manager_session']]
- return manager.render(request)
-
-
-def add_workflow(request):
- manager = attempt_auth(request)
- if not manager:
- return no_workflow(request)
- try:
- workflow_type = int(request.POST.get('workflow_type'))
- except ValueError:
- return HttpResponse(status=400)
-
- manager.add_workflow(workflow_type=workflow_type)
- return manager.render(request) # do we want this?
-
-
-def manager_view(request):
- manager = attempt_auth(request)
- if not manager:
- return no_workflow(request)
-
- return manager.handle_request(request)
-
-
-def viewport_view(request):
- if not request.user.is_authenticated:
- return login(request)
-
- manager = attempt_auth(request)
- if manager is None:
- return no_workflow(request)
-
- if request.method != 'GET':
- return HttpResponse(status=405)
-
- context = {
- 'contact_email': Lab.objects.get(name="UNH_IOL").contact_email
- }
-
- return render(request, 'workflow/viewport-base.html', context)
-
-
-def create_workflow(request):
- if request.method != 'POST':
- return HttpResponse(status=405)
- workflow_type = request.POST.get('workflow_type')
- try:
- workflow_type = int(workflow_type)
- except Exception:
- return HttpResponse(status=400)
- mgr_uuid = create_session(workflow_type, request=request,)
- request.session['manager_session'] = mgr_uuid
- return HttpResponse()
-
-
-def create_session(wf_type, request):
- smgr = SessionManager(request=request)
- smgr.add_workflow(workflow_type=wf_type, target_id=request.POST.get("target"))
- manager_uuid = uuid.uuid4().hex
- ManagerTracker.getInstance().managers[manager_uuid] = smgr
-
- return manager_uuid
-
-
-def no_workflow(request):
- return render(request, 'workflow/no_workflow.html', {'title': "Not Found"}, status=404)
-
-
-def login(request):
- return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
diff --git a/src/workflow/workflow_factory.py b/src/workflow/workflow_factory.py
deleted file mode 100644
index e688510..0000000
--- a/src/workflow/workflow_factory.py
+++ /dev/null
@@ -1,126 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, Sean Smith, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from workflow.booking_workflow import Booking_Resource_Select, Booking_Meta, OPNFV_Select
-from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info, Define_Software
-from workflow.snapshot_workflow import Select_Host_Step, Image_Meta_Step
-from workflow.opnfv_workflow import Pick_Installer, Assign_Network_Roles, Assign_Host_Roles, OPNFV_Resource_Select, MetaInfo
-from workflow.models import Confirmation_Step
-
-import uuid
-
-import logging
-logger = logging.getLogger(__name__)
-
-
-class MetaStep(object):
-
- UNTOUCHED = 0
- INVALID = 100
- VALID = 200
-
- def set_invalid(self, message, code=100):
- self.valid = code
- self.message = message
-
- def set_valid(self, message, code=200):
- self.valid = code
- self.message = message
-
- def __init__(self, *args, **kwargs):
- self.short_title = "error"
- self.skip_step = 0
- self.valid = 0
- self.hidden = False
- self.message = ""
- self.id = uuid.uuid4()
-
- def to_json(self):
- return {
- 'title': self.short_title,
- 'skip': self.skip_step,
- 'valid': self.valid,
- 'message': self.message,
- }
-
- def __str__(self):
- return "metastep: " + str(self.short_title)
-
- def __hash__(self):
- return hash(self.id)
-
- def __eq__(self, other):
- return self.id.int == other.id.int
-
- def __ne__(self, other):
- return self.id.int != other.id.int
-
-
-class Workflow(object):
- def __init__(self, steps, repository):
- self.repository = repository
- self.steps = steps
- self.active_index = 0
-
-
-class WorkflowFactory():
- booking_steps = [
- Booking_Resource_Select,
- Booking_Meta,
- OPNFV_Select,
- ]
-
- resource_steps = [
- Define_Hardware,
- Define_Software,
- Define_Nets,
- Resource_Meta_Info,
- ]
-
- snapshot_steps = [
- Select_Host_Step,
- Image_Meta_Step,
- ]
-
- opnfv_steps = [
- OPNFV_Resource_Select,
- Pick_Installer,
- Assign_Network_Roles,
- Assign_Host_Roles,
- MetaInfo
- ]
-
- def conjure(self, workflow_type=None, repo=None):
- workflow_types = [
- self.booking_steps,
- self.resource_steps,
- self.snapshot_steps,
- self.opnfv_steps,
- ]
-
- steps = self.make_steps(workflow_types[workflow_type], repository=repo)
- return steps
-
- def create_workflow(self, workflow_type=None, repo=None):
- steps = self.conjure(workflow_type, repo)
- c_step = self.make_step(Confirmation_Step, repo)
- steps.append(c_step)
- return Workflow(steps, repo)
-
- def make_steps(self, step_types, repository):
- steps = []
- for step_type in step_types:
- steps.append(self.make_step(step_type, repository))
-
- return steps
-
- def make_step(self, step_type, repository):
- iden = step_type.description + step_type.title + step_type.template
- return step_type(iden, repository)
diff --git a/src/workflow/workflow_manager.py b/src/workflow/workflow_manager.py
deleted file mode 100644
index 40be9d6..0000000
--- a/src/workflow/workflow_manager.py
+++ /dev/null
@@ -1,270 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.http import JsonResponse
-from django.http.request import QueryDict
-from django.urls import reverse
-
-from booking.models import Booking
-from workflow.workflow_factory import WorkflowFactory
-from workflow.models import Repository
-from resource_inventory.models import (
- ResourceTemplate,
- ResourceConfiguration,
- OPNFVConfig
-)
-from workflow.forms import ManagerForm
-
-import logging
-logger = logging.getLogger(__name__)
-
-
-class SessionManager():
- def active_workflow(self):
- return self.workflows[-1]
-
- def __init__(self, request=None):
- self.workflows = []
- self.owner = request.user
- self.factory = WorkflowFactory()
- self.result = None
-
- def set_step_statuses(self, superclass_type, desired_enabled=True):
- workflow = self.active_workflow()
- steps = workflow.steps
- for step in steps:
- if isinstance(step, superclass_type):
- if desired_enabled:
- step.enable()
- else:
- step.disable()
-
- def add_workflow(self, workflow_type=None, **kwargs):
- repo = Repository()
- if (len(self.workflows) >= 1):
- defaults = self.workflows[-1].repository.get_child_defaults()
- repo.set_defaults(defaults)
- repo.el[repo.HAS_RESULT] = False
- repo.el[repo.SESSION_USER] = self.owner
- repo.el[repo.SESSION_MANAGER] = self
- self.workflows.append(
- self.factory.create_workflow(
- workflow_type=workflow_type,
- repo=repo
- )
- )
-
- def get_redirect(self):
- if isinstance(self.result, Booking):
- return reverse('booking:booking_detail', kwargs={'booking_id': self.result.id})
- return "/"
-
- def pop_workflow(self, discard=False):
- multiple_wfs = len(self.workflows) > 1
- if multiple_wfs:
- if self.workflows[-1].repository.el[Repository.RESULT]: # move result
- key = self.workflows[-1].repository.el[Repository.RESULT_KEY]
- result = self.workflows[-1].repository.el[Repository.RESULT]
- self.workflows[-2].repository.el[key] = result
- prev_workflow = self.workflows.pop()
- if self.workflows:
- current_repo = self.workflows[-1].repository
- else:
- current_repo = prev_workflow.repository
- self.result = current_repo.el[current_repo.RESULT]
- if discard:
- current_repo.cancel()
- return multiple_wfs, self.result
-
- def status(self, request):
- return {
- "steps": [step.to_json() for step in self.active_workflow().steps],
- "active": self.active_workflow().repository.el['active_step'],
- "workflow_count": len(self.workflows)
- }
-
- def handle_post(self, request):
- form = ManagerForm(request.POST)
- if form.is_valid():
- self.get_active_step().post(
- QueryDict(form.cleaned_data['step_form']),
- user=request.user
- )
- # change step
- if form.cleaned_data['step'] == 'prev':
- self.go_prev()
- if form.cleaned_data['step'] == 'next':
- self.go_next()
- else:
- pass # Exception?
-
- def handle_request(self, request):
- if request.method == 'POST':
- self.handle_post(request)
- return self.render(request)
-
- def render(self, request, **kwargs):
- if self.workflows:
- return JsonResponse({
- "meta": self.status(request),
- "content": self.get_active_step().render_to_string(request),
- })
- else:
- return JsonResponse({
- "redirect": self.get_redirect()
- })
-
- def post_render(self, request):
- return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)
-
- def get_active_step(self):
- return self.active_workflow().steps[self.active_workflow().active_index]
-
- def go_next(self, **kwargs):
- # need to verify current step is valid to allow this
- if self.get_active_step().valid < 200:
- return
- next_step = self.active_workflow().active_index + 1
- if next_step >= len(self.active_workflow().steps):
- raise Exception("Out of bounds request for step")
- while not self.active_workflow().steps[next_step].enabled:
- next_step += 1
- self.active_workflow().repository.el['active_step'] = next_step
- self.active_workflow().active_index = next_step
-
- def go_prev(self, **kwargs):
- prev_step = self.active_workflow().active_index - 1
- if prev_step < 0:
- raise Exception("Out of bounds request for step")
- while not self.active_workflow().steps[prev_step].enabled:
- prev_step -= 1
- self.active_workflow().repository.el['active_step'] = prev_step
- self.active_workflow().active_index = prev_step
-
- def prefill_repo(self, target_id, workflow_type):
- self.repository.el[self.repository.EDIT] = True
- edit_object = None
- if workflow_type == 0:
- edit_object = Booking.objects.get(pk=target_id)
- self.prefill_booking(edit_object)
- elif workflow_type == 1:
- edit_object = ResourceTemplate.objects.get(pk=target_id)
- self.prefill_resource(edit_object)
- elif workflow_type == 2:
- edit_object = ResourceTemplate.objects.get(pk=target_id)
- self.prefill_config(edit_object)
-
- def prefill_booking(self, booking):
- models = self.make_booking_models(booking)
- confirmation = self.make_booking_confirm(booking)
- self.active_workflow().repository.el[self.active_workflow().repository.BOOKING_MODELS] = models
- self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirmation
- self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = self.make_grb_models(booking.resource.template)
- self.active_workflow().repository.el[self.active_workflow().repository.SELECTED_RESOURCE_TEMPLATE] = self.make_grb_models(booking.resource.template)['bundle']
- self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle)
-
- def prefill_resource(self, resource):
- models = self.make_grb_models(resource)
- confirm = self.make_grb_confirm(resource)
- self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = models
- self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm
-
- def prefill_config(self, config):
- models = self.make_config_models(config)
- confirm = self.make_config_confirm(config)
- self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = models
- self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm
- grb_models = self.make_grb_models(config.bundle)
- self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = grb_models
-
- def make_grb_models(self, resource):
- models = self.active_workflow().repository.el.get(self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS, {})
- models['hosts'] = []
- models['bundle'] = resource
- models['interfaces'] = {}
- models['vlans'] = {}
- for host in resource.getResources():
- models['hosts'].append(host)
- models['interfaces'][host.resource.name] = []
- models['vlans'][host.resource.name] = {}
- for interface in host.generic_interfaces.all():
- models['interfaces'][host.resource.name].append(interface)
- models['vlans'][host.resource.name][interface.profile.name] = []
- for vlan in interface.vlans.all():
- models['vlans'][host.resource.name][interface.profile.name].append(vlan)
- return models
-
- def make_grb_confirm(self, resource):
- confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
- confirm['resource'] = {}
- confirm['resource']['hosts'] = []
- confirm['resource']['lab'] = resource.lab.lab_user.username
- for host in resource.getResources():
- confirm['resource']['hosts'].append({"name": host.resource.name, "profile": host.profile.name})
- return confirm
-
- def make_config_models(self, config):
- models = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIG_MODELS, {})
- models['bundle'] = config
- models['host_configs'] = []
- for host_conf in ResourceConfiguration.objects.filter(bundle=config):
- models['host_configs'].append(host_conf)
- models['opnfv'] = OPNFVConfig.objects.filter(bundle=config).last()
- return models
-
- def make_config_confirm(self, config):
- confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
- confirm['configuration'] = {}
- confirm['configuration']['hosts'] = []
- confirm['configuration']['name'] = config.name
- confirm['configuration']['description'] = config.description
- opnfv = OPNFVConfig.objects.filter(bundle=config).last()
- confirm['configuration']['installer'] = opnfv.installer.name
- confirm['configuration']['scenario'] = opnfv.scenario.name
- for host_conf in ResourceConfiguration.objects.filter(bundle=config):
- h = {"name": host_conf.host.resource.name, "image": host_conf.image.name, "role": host_conf.opnfvRole.name}
- confirm['configuration']['hosts'].append(h)
- return confirm
-
- def make_booking_models(self, booking):
- models = self.active_workflow().repository.el.get(self.active_workflow().repository.BOOKING_MODELS, {})
- models['booking'] = booking
- models['collaborators'] = []
- for user in booking.collaborators.all():
- models['collaborators'].append(user)
- return models
-
- def make_booking_confirm(self, booking):
- confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
- confirm['booking'] = {}
- confirm['booking']['length'] = (booking.end - booking.start).days
- confirm['booking']['project'] = booking.project
- confirm['booking']['purpose'] = booking.purpose
- confirm['booking']['resource name'] = booking.resource.template.name
- confirm['booking']['configuration name'] = booking.config_bundle.name
- confirm['booking']['collaborators'] = []
- for user in booking.collaborators.all():
- confirm['booking']['collaborators'].append(user.username)
- return confirm
-
-
-class ManagerTracker():
- instance = None
-
- managers = {}
-
- def __init__(self):
- pass
-
- @staticmethod
- def getInstance():
- if ManagerTracker.instance is None:
- ManagerTracker.instance = ManagerTracker()
- return ManagerTracker.instance
--
cgit 1.2.3-korg