Rift.IO OSM R1 Initial Submission

Signed-off-by: Jeremy Mordkoff <jeremy.mordkoff@riftio.com>
diff --git a/skyquake/framework/widgets/JSONViewer/JSONViewer.js b/skyquake/framework/widgets/JSONViewer/JSONViewer.js
new file mode 100644
index 0000000..2f8c0a8
--- /dev/null
+++ b/skyquake/framework/widgets/JSONViewer/JSONViewer.js
@@ -0,0 +1,140 @@
+/**
+ * Created by onvelocity on 2/2/16.
+ */
+
+import React from 'react'
+import Prism from 'prismjs'
+import PureRenderMixin from 'react-addons-pure-render-mixin'
+
+import './JSONViewer.scss'
+
+const YAML = require('json2yaml');
+
+const cssString = `
+				html, body {
+					padding:0;
+					margin:0;
+				}
+					/*
+					 copied from node_modules/prismjs/themes/prismjs.css
+					 */
+					:not(pre) > code[class*="language-"],
+					pre[class*="language-"] {
+					font-size: 12px;
+					padding:1rem;
+					/*
+					border: 1px solid rgba(220, 220, 220, 0.5);
+					border-radius: 4px;
+					*/
+					background-color: rgba(255, 255, 255, .25);
+				}
+
+					/* Inline code */
+					:not(pre) > code[class*="language-"] {
+					padding: .1em;
+				}
+
+					.token.comment,
+					.token.prolog,
+					.token.doctype,
+					.token.cdata {
+					color: slategray;
+				}
+
+					.token.punctuation {
+					color: #333;
+				}
+
+					.namespace {
+					opacity: .7;
+				}
+
+					.token.property,
+					.token.tag,
+					.token.boolean,
+					.token.number,
+					.token.constant,
+					.token.symbol,
+					.token.deleted {
+					color: #905;
+				}
+
+					.token.selector,
+					.token.attr-name,
+					.token.string,
+					.token.char,
+					.token.builtin,
+					.token.inserted {
+					color: #df5000;
+				}
+
+					.token.operator,
+					.token.entity,
+					.token.url,
+					.language-css .token.string,
+					.style .token.string {
+					color: #a67f59;
+				}
+
+					.token.atrule,
+					.token.attr-value,
+					.token.keyword {
+					color: #07a;
+				}
+
+					.token.function {
+					color: #DD4A68;
+				}
+
+					.token.regex,
+					.token.important,
+					.token.variable {
+					color: #e90;
+				}
+
+					.token.important,
+					.token.bold {
+					font-weight: bold;
+				}
+					.token.italic {
+					font-style: italic;
+				}
+
+					.token.entity {
+					cursor: help;
+				}
+`;
+
+const JSONViewer = React.createClass({
+	mixins: [PureRenderMixin],
+	getInitialState: function () {
+		return {};
+	},
+	getDefaultProps: function () {
+		return {};
+	},
+	componentWillMount: function () {
+	},
+	componentDidMount: function () {
+	},
+	componentDidUpdate: function () {
+	},
+	componentWillUnmount: function () {
+	},
+	render() {
+		const text = YAML.stringify(this.props.json, undefined, 12).replace(/    /g, '     ');
+		const result = Prism.highlight(text, Prism.languages.javascript, 'javascript');
+		return (
+			<div className="JSONViewer">
+				<style dangerouslySetInnerHTML={{__html: cssString}}></style>
+				<label className="descriptor">
+					<pre className="language-js">
+						<code dangerouslySetInnerHTML={{__html: result}} />
+					</pre>
+				</label>
+			</div>
+		);
+	}
+});
+
+export default JSONViewer;
diff --git a/skyquake/framework/widgets/JSONViewer/JSONViewer.scss b/skyquake/framework/widgets/JSONViewer/JSONViewer.scss
new file mode 100644
index 0000000..08fcefd
--- /dev/null
+++ b/skyquake/framework/widgets/JSONViewer/JSONViewer.scss
@@ -0,0 +1,104 @@
+.JSONViewer {
+	/*
+	copied from node_modules/prismjs/themes/prismjs.css
+*/
+	:not(pre) > code[class*="language-"],
+	pre[class*="language-"] {
+		font-size: 12px;
+		/* border: 1px solid rgba(220, 220, 220, 0.5);*/
+		border-radius: 4px;
+		background-color: rgba(255, 255, 255, .25);
+		overflow:auto;
+	}
+
+	/* Inline code */
+	:not(pre) > code[class*="language-"] {
+		padding: .1em;
+	}
+
+	.token.comment,
+	.token.prolog,
+	.token.doctype,
+	.token.cdata {
+		color: slategray;
+	}
+
+	.token.punctuation {
+		color: #333;
+	}
+
+	.namespace {
+		opacity: .7;
+	}
+
+	.token.property,
+	.token.tag,
+	.token.boolean,
+	.token.number,
+	.token.constant,
+	.token.symbol,
+	.token.deleted {
+		color: #905;
+	}
+
+	.token.selector,
+	.token.attr-name,
+	.token.string,
+	.token.char,
+	.token.builtin,
+	.token.inserted {
+		color: #df5000;
+	}
+
+	.token.operator,
+	.token.entity,
+	.token.url,
+	.language-css .token.string,
+	.style .token.string {
+		color: #a67f59;
+		background: none !important;
+	}
+
+	.token.atrule,
+	.token.attr-value,
+	.token.keyword {
+		color: #07a;
+	}
+
+	.token.function {
+		color: #DD4A68;
+	}
+
+	.token.regex,
+	.token.important,
+	.token.variable {
+		color: #e90;
+	}
+
+	.token.important,
+	.token.bold {
+		font-weight: bold;
+	}
+	.token.italic {
+		font-style: italic;
+	}
+
+	.token.entity {
+		cursor: help;
+	}
+}
+
+.composerPopout {
+	background-color: #f1f1f1;
+	h1 {
+			height: 51px;
+			line-height: 51px;
+			margin-left: 80px;
+			padding-left: 118px;
+			position: absolute;
+			left: 0;
+			text-transform: uppercase;
+			font-size: 1.625rem;
+			font-weight: normal;
+	}
+}
diff --git a/skyquake/framework/widgets/bullet/bullet.js b/skyquake/framework/widgets/bullet/bullet.js
new file mode 100644
index 0000000..1411f0f
--- /dev/null
+++ b/skyquake/framework/widgets/bullet/bullet.js
@@ -0,0 +1,230 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require('react');
+var mixin = require('../mixins/ButtonEventListener.js')
+//TODO: Many values are hard coded, need to make bullet "smarter"
+/**
+ *  A bullet graph component.
+ *  It's props values and a brief description below
+ *
+ *  vertical: If true, the bar rises and falls vertically
+ *  value:    The value displayed
+ *  min:      The minimum value expected
+ *  max:      The maximum value expected
+ *  bulletColor: The fill color of the value section of the graph
+ *  radius:   Radius of graph corners.
+ *  containerMarginX: Container's margin along x-axis
+ *  containerMarginY: Container's margin along y-axis
+ *  width:    Width of bullet graph in pixels
+ *  height:   Height of bullet graph in pixels
+ *  fontSize:  Font size of text in pixels
+ *  textMarginX: margin for text on x-axis
+ *  textMarginY: Margin for text on y-axis
+ *  units:    units displayed. Also changes whether a max value is displayed.
+ **/
+module.exports = React.createClass({
+  displayName: 'Bullet',
+  mixins:mixin.MIXINS,
+  propTypes: {
+    vertical: React.PropTypes.bool,
+    value: React.PropTypes.number,
+    min: React.PropTypes.number,
+    max: React.PropTypes.number,
+    bulletColor: React.PropTypes.string,
+    radius: React.PropTypes.number,
+    containerMarginX: React.PropTypes.number,
+    containerMarginY: React.PropTypes.number,
+    bulletMargin: React.PropTypes.number,
+    width: React.PropTypes.number,
+    height: React.PropTypes.number,
+    markerX: React.PropTypes.number,
+    fontSize: React.PropTypes.number,
+    textMarginX: React.PropTypes.number,
+    textMarginY: React.PropTypes.number,
+    units: React.PropTypes.string
+  },
+
+  getDefaultProps: function() {
+    return {
+        vertical: false,
+        value: 0,
+        min: 0,
+        max: 100,
+        bulletColor: "blue",
+        radius: 4,
+        containerMarginX: 0,
+        containerMarginY: 0,
+        bulletMargin: 0,
+        width: 512,
+        height: 64,
+        markerX: -100,
+        fontSize: 16,
+        textMarginX: 5,
+        textMarginY: 42,
+        units:''
+    }
+  },
+
+  /**
+   * Defines default state.
+   * value: The value displayed
+   */
+  getInitialState: function() {
+    return {
+        value: this.props.value
+    }
+  },
+
+  /**
+   * Called when the props are updated.
+   * Syncs the state value with the prop value.
+   * @params nextProps
+   */
+  componentWillReceiveProps: function(nextProps) {
+    this.setState({value:nextProps.value || this.state.value})
+  },
+
+  /**
+   * Before the component will mount, the width value is recalculed based on browser.
+   */
+  componentWillMount: function() {
+        var isIE = false || !!document.documentMode;
+        var range = this.props.max - this.props.min;
+        var normalizedValue = (this.state.value - this.props.min) / range;
+        this.isPercent = (!this.props.units || this.props.units == '')? true:false
+        if (isIE) {
+            this.bulletWidth = String(Math.round(100 * normalizedValue)) + "%";
+        } else {
+            this.bulletWidth = this.props.width - (2 * this.props.containerMarginX);
+        }
+        this.displayValue = (this.isPercent)? String(Math.round(normalizedValue * 100)) + "%" : this.props.value
+
+  },
+
+  /**
+   * When the component mounts, this function sets the animation for smooth transition on value change.  This only
+   * happens if the user's browser is not IE.
+   */
+  componentDidMount: function() {
+        var isIE = false || !!document.documentMode;
+        var range = this.props.max - this.props.min;
+        var normalizedValue = (this.state.value - this.props.min) / range;
+        if (!isIE) {
+            var transform = "scaleX(" + normalizedValue + ")";
+            var bullet = React.findDOMNode(this.refs.bulletRef);
+            bullet.style.transform = transform;
+            bullet.style["-webkit-transform"] = transform;
+        }
+  },
+
+  /**
+   * On update, reaplies transformation and width changes made in componentWillMount and componentDidMount
+   * @param nextProps
+   * @param nextState
+   * @returns {boolean}
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+
+    if (String(this.state.value) == String(nextState.value)) {
+      return false;
+    } else {
+        var isIE = false || !!document.documentMode;
+        var range = this.props.max - this.props.min;
+        var normalizedValue = (nextState.value - this.props.min) / range;
+
+        if (isIE) {
+            this.bulletWidth = String(Math.round(100 * normalizedValue)) + "%";
+        } else {
+            this.bulletWidth = this.props.width - (2 * this.props.containerMarginX);
+            var transform = "scaleX(" + normalizedValue + ")";
+            var bullet = React.findDOMNode(this.refs.bulletRef);
+            bullet.style.transform = transform;
+            bullet.style["-webkit-transform"] = transform;
+        }
+        this.displayValue = (this.isPercent)? String(Math.round(normalizedValue * 100)) + "%" : nextState.value
+
+        return true;
+    }
+  },
+
+
+
+  /**
+   * Renders the Bullet Component
+   * @returns {*}
+   */
+  render: function() {
+
+    // The text element that displays the difference between the max value and the current value.
+    var maxDOMEl = (!this.isPercent)? null : React.createElement("text", {
+                //X needs better logic
+                // x: this.props.width - this.props.height * 1.25,
+                x: this.props.width - (130 - this.props.textMarginX),
+                y: this.props.textMarginY,
+                fontFamily: "Verdana",
+                fontSize: this.props.fontSize,
+                fill: "#ffffff"}, String(this.props.max - this.state.value) + "%");
+
+
+        // The main bullet element.  Made up of a static black rect in the background,
+        // a moving colored rect overlayed on top and a text element for the current value.
+        var bulletDOM = React.createElement("svg", {
+            width: "100%",
+            height: "100%",
+            viewBox: "0 0 512 " + this.props.height,
+            preserveAspectRatio: "none"},
+          React.createElement("rect", {className: "bullet-container",
+                width: this.props.width - (2 * this.props.containerMarginX),
+                height: this.props.height - (2 * this.props.containerMarginY),
+                x: this.props.containerMarginX,
+                y: this.props.containerMarginY,
+                rx: this.props.radius,
+                ry: this.props.radius}, null),
+          React.createElement("svg", {
+              x: this.props.containerMarginX,
+              y: this.props.bulletMargin},
+            React.createElement("rect", {
+                className: "bullet",
+                ref:"bulletRef",
+                fill: this.props.bulletColor,
+                width: this.bulletWidth,
+                height: this.props.height - (2 * this.props.bulletMargin),
+                rx: this.props.radius,
+                ry: this.props.radius
+            })
+          ),
+          React.createElement("line", {className: "bullet-marker",
+                x1: this.props.markerX,
+                x2: this.props.markerX,
+                y1: this.props.markerY1,
+                y2: this.props.markerY2}),
+            React.createElement("text", {
+                x: this.props.textMarginX,
+                y: this.props.textMarginY,
+                "fontFamily": "Verdana",
+              "fontSize": this.props.fontSize,
+              fill: "#ffffff"}, this.displayValue
+              ),
+            maxDOMEl
+        );
+
+
+    return bulletDOM;
+  }
+});
diff --git a/skyquake/framework/widgets/button/button.scss b/skyquake/framework/widgets/button/button.scss
new file mode 100644
index 0000000..c972e14
--- /dev/null
+++ b/skyquake/framework/widgets/button/button.scss
@@ -0,0 +1,270 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+@import '../../style/_colors.scss';
+@import '../../style/variables.scss';
+
+button{
+    color: #000000;
+    display: inline-block;
+    font-size: 0.75rem;
+    padding: 0.75rem 3rem;
+    text-decoration: none;
+    text-transform: uppercase;
+    box-shadow: 2px 2px rgba(0, 0, 0, 0.15);
+    cursor: pointer;
+    margin:0 1rem;
+  &.light {
+    background-color: #ffffff;
+    border: 1px solid #cccccc;
+    border-top: 0;
+    &.small {
+      padding:0.25rem 1rem;
+    }
+  }
+
+  &.dark {
+    background-color: #333333;
+    border: 1px solid #000000;
+    border-top: 0;
+    color: #ffffff;
+    &:hover,&:active {
+      background: #00acee;
+      color: #ffffff;
+    }
+  }
+
+
+}
+
+/* IMPORTS
+############################################################################ */
+
+
+
+
+/* BUTTON
+############################################################################ */
+
+.SqButton {
+  align-items: center;
+  border-style: solid;
+  border-radius: 3px;
+  border-width: 0px;
+  cursor: pointer;
+  display: inline-flex;
+  font-size: 1rem;
+  height: 50px;
+  justify-content: center;
+  margin: 0 10px;
+  outline: none;
+  padding: 0 15px;
+  text-transform: uppercase;
+  transition: $transition;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  user-select: none;
+
+  /* Button Content */
+  &-content {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    transition: $transition;
+    white-space: nowrap;
+  }
+
+  /* Button Icon */
+  &-icon {
+    transition: $transition;
+  }
+
+  /* Spacing between content and icon when icon is on the right */
+  &-icon + &-content {
+    margin-left: 10px;
+  }
+
+  /* Spacing between content and icon when icon is on the left */
+  &-content + &-icon {
+    margin-left: 10px;
+  }
+
+  /* Focus */
+  &:focus {
+    // box-shadow: $focus-shadow;
+    border: 1px solid red;
+  }
+
+  /* SIZES
+  ############################################################################ */
+
+  &--large {
+    width: 250px;
+  }
+
+  &--medium {
+    width: 175px;
+  }
+
+  &--small {
+    width: 85px;
+  }
+
+  /* NORMAL
+  ############################################################################ */
+
+  /* Base */
+  &--normal {
+    background: $normalBackground;
+    border-color: darken($normalBackground, 10%);
+
+    .SqButton-content {
+      color: $normalForeground;
+    }
+
+    .SqButton-icon {
+      fill: $normalForeground;
+    }
+  }
+
+  /* Hover */
+  &--normal:hover {
+    background: $normalHoverBackground;
+    border-color: darken($normalHoverBackground, 10%);
+
+    .SqButton-content {
+      color: $normalHoverForeground;
+    }
+
+    .SqButton-icon {
+      fill: $normalHoverForeground;
+    }
+  }
+
+  /* Active */
+  &--normal:active {
+    background: $normalActiveBackground;
+    border-color: darken($normalActiveBackground, 10%);
+
+    .SqButton-content {
+      color: $normalActiveForeground;
+    }
+
+    .SqButton-icon {
+      fill: $normalActiveForeground;
+    }
+  }
+
+  /* Disabled */
+  &--normal.is-disabled {
+    cursor: default;
+    opacity: .55;
+  }
+
+  &--normal:hover.is-disabled,
+  &--normal:active.is-disabled {
+    background: $normalBackground;
+    border-color: darken($normalBackground, 10%);
+
+    .SqButton-content {
+      color: $normalForeground;
+    }
+
+    .SqButton-icon {
+      fill: $normalForeground;
+    }
+  }
+
+
+  /* PRIMARY
+  ############################################################################ */
+
+  /* Base */
+  &--primary {
+    background: $primaryBackground;
+    border-color: darken($primaryBackground, 10%);
+
+    .SqButton-content {
+      color: $primaryForeground;
+    }
+
+    .SqButton-icon {
+      fill: $primaryForeground;
+    }
+  }
+
+  /* Hover */
+  &--primary:hover {
+    background: $primaryHoverBackground;
+    border-color: darken($primaryHoverBackground, 10%);
+
+    .SqButton-content {
+      color: $primaryHoverForeground;
+    }
+
+    .SqButton-icon {
+      fill: $primaryHoverForeground;
+    }
+  }
+
+  /* Active */
+  &--primary:active {
+    background: $primaryActiveBackground;
+    border-color: darken($primaryActiveBackground, 10%);
+
+    .SqButton-content {
+      color: $primaryActiveForeground;
+    }
+
+    .SqButton-icon {
+      fill: $primaryActiveForeground;
+    }
+  }
+
+  /* Disabled */
+  &--primary.is-disabled {
+    cursor: default;
+    opacity: .55;
+  }
+
+  &--primary:hover.is-disabled,
+  &--primary:active.is-disabled {
+    background: $primaryBackground;
+    border-color: darken($primaryBackground, 10%);
+
+    .SqButton-content {
+      color: $primaryForeground;
+    }
+
+    .SqButton-icon {
+      fill: $primaryForeground;
+    }
+  }
+
+
+}
+
+
+
+
+
+
+
+
+
diff --git a/skyquake/framework/widgets/button/rw.button.js b/skyquake/framework/widgets/button/rw.button.js
new file mode 100644
index 0000000..41730eb
--- /dev/null
+++ b/skyquake/framework/widgets/button/rw.button.js
@@ -0,0 +1,261 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import './button.scss';
+import Loader from '../loading-indicator/loadingIndicator.jsx';
+var React = require('react');
+var ButtonEventListenerMixin = require('../mixins/ButtonEventListener.js');
+
+
+/**
+ *  A generic button component.
+ *  It's props values and a brief description below
+ *
+ *  Label: The label of the button. What it displays at any given time.
+ *  Icon: A url for an icon that will be displayed on the button.  Leave blank if no icon required.
+ *  Class: Css Classes applied to the element.
+ *  sizeOfButton: The preset sizes for the button (small, default, large, xlarge, expand).
+ *  minWidth: Minimum Width of the button.
+ *  maxWidth: Maximum Width of the button.
+ **/
+module.exports = React.createClass({
+  displayName: "Button",
+  mixins:[ButtonEventListenerMixin],
+  propTypes: {
+    label:           React.PropTypes.string,
+    icon:            React.PropTypes.array,
+    className:       React.PropTypes.string,
+    //sizeOfButton:    React.PropTypes.string,
+    minWidth:        React.PropTypes.string,
+    maxWidth:        React.PropTypes.string,
+    //isActive:        React.PropTypes.bool,
+    //isFocused:       React.PropTypes.bool,
+    //isHovered:       React.PropTypes.bool,
+    isDisabled:      React.PropTypes.bool
+  },
+
+
+  /**
+   * Defines default state.
+   * sizeOfButton: See Prop type definitions.
+   * class: See Prop type definitions.
+   * label: See Prop type definitions.
+   * isActive: Boolean to indicate if button is active.
+   * isHovered: Boolean to indicate if the button is being hovered over.
+   * isFocused: Boolean to indicate if the button has been focused.
+   * isDisabled: Boolean to indicate if button has been disabled.
+   * @returns {{sizeOfButton: (*|string), class: *, isActive: boolean, isHovered: boolean,
+   *            isFocused: boolean, isDisabled: (*|boolean), label: *}}
+   */
+  getInitialState: function() {
+    return {
+      //sizeOfButton:   this.props.size || '',  //There is no Medium value in CSS, default size is the absence of a value
+      className:      this.props.className || 'rw-button-primary', //Default value is 'rw-button-primary' which is the primary one
+      label:          this.props.label,
+      isActive:       false,
+      isHovered:      false,
+      isFocused:      false,
+      isLoading:      this.props.isLoading || false,
+      isDisabled:     this.props.isDisabled || false
+    }
+  },
+
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * "nextProps" and "nextState" hold the state and property variables as they will be after the update.
+   * "this.props" and "this.state" hold the state and property variables as they were before the update.
+   * returns true if the state have changed. Otherwise returns false.
+   * @param nextProps
+   * @param nextState
+   * @returns {boolean}
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = this.state.label + this.state.isDisabled + this.state.isActive + this.state.isFocused +
+      this.state.isHovered + this.props.isLoading;
+    var nextStateString = nextState.label + nextState.isDisabled + nextState.isActive + nextState.isFocused +
+      nextState.isHovered + nextProps.isLoading;
+
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    return true;
+  },
+
+
+  /**
+   * Returns a string reflecting the current state of the button.
+   * If the button state "isDisabled" is true, returns a string "disabled".
+   * Otherwise returns a string containing a word for each state that has been set to true.
+   * (ie "active focused" if the states active and focused are true, but hovered is false).
+   * @returns {string}
+   */
+  setButtonState: function() {
+    var ret = "";
+    if (this.state.isDisabled) {
+      return "disabled";
+    }
+    if (this.state.isActive) {
+      ret += "active ";
+    }
+    if (this.state.isHovered) {
+      ret += "hovered ";
+    }
+    if (this.state.isFocused) {
+      ret += "focused ";
+    }
+    return ret;
+  },
+
+
+
+  /**
+   * Track the width if set and write into markup using Style attribute
+   * Returns the minWidth and maxWidth prop in a string
+   * @returns {{}}
+   */
+  setButtonWidth:function(){

+    var width = {};


+
+    if (this.props.minWidth) {

+      width.minWidth = String(this.props.minWidth);

+    }

+    if (this.props.maxWidth) {

+      width.maxWidth = String(this.props.maxWidth);

+    }

+
+    return width;

+  },
+
+
+
+  /**
+   * Apply the size of the button to the icon directly
+   * Returns a string indicating the icon size.
+   * @returns {string}
+   */
+  /*
+  setIconSize:function(){
+
+    var iconClass = "rw-icon";
+
+    if(this.props.size){
+      iconClass += "-" + this.props.size;
+    }
+    return iconClass;
+  },
+  */
+
+
+
+  /**
+   * Builds the list of classes.
+   * Returns a string holding each class seperated by a space.
+   * @returns {string}
+   */
+  /*
+  setButtonClass:function() {
+    var buttonClass = "";
+    var buttonClassType = "rw-button-primary";
+    // If the size is declared, add it in
+    if (this.state.sizeOfButton) {
+      buttonClass += this.state.sizeOfButton;
+    }
+    //
+    if (typeof(this.props.className) != "undefined") {
+      this.props.className.push("rw-button-secondary");
+
+      // Run through the array and check all the values
+      for (var i = 0; i < this.props.className.length; i++) {
+
+        if (this.props.className[i].indexOf("secondary") > -1) {
+          buttonClassType = "rw-button-secondary";  // If the value of the array is equal to the string "secondary", add a predefined string
+        } else {
+          buttonClass += " " + this.props.className[i];  // Else just write the value of the array
+        }
+      }
+    }
+    buttonClass += " " + buttonClassType;  //Add the button style type either primary or secondary
+    return buttonClass;
+  },
+  */
+
+  /**
+   * Builds an array of html elements for the icons and returns them.
+   * @returns {Array}
+   */
+  setIconElement: function() {
+    var button_icon = [];
+
+    if (typeof(this.props.icon) != "undefined") {
+      for (var i = 0; i < this.props.icon.length; i++) {
+        button_icon.push(React.createElement('svg', {
+          className: "rw-button__icon",
+          key: i,
+          dangerouslySetInnerHTML: {__html: '<use xlink:href="#' + this.props.icon[i] + '" />'}  //Using a React method to drop in a hack since React does not support xlink:href yet
+        }));
+      }
+    }
+    return button_icon;
+  },
+
+  /**
+   * Renders the Button Component
+   * Returns a react component that constructs the html that houses the button component.
+   * @returns {*}
+   */
+  render: function() {
+    var button = null;
+    var button_style = this.setButtonWidth();
+    var button_state = this.setButtonState();
+    var button_class = this.state.className;
+    var button_icon = this.setIconElement();
+    var display = this.state.label;
+    if (this.props.isLoading) {
+      display = React.createElement(Loader, {show: true, size: '1rem', color: this.props.loadingColor});
+    }
+
+    button = React.createElement("button", {
+        className:         button_class,
+        "data-state":      button_state,
+        style:             button_style,
+
+        // onClick:           this.onClick,
+        onClick:           this.props.onClick,
+        onMouseUp:         this.onMouseUp,
+        onMouseDown:       this.onMouseDown,
+        onMouseOver:       this.onMouseOver,
+        onMouseEnter:      this.onMouseEnter,
+        onMouseLeave:      this.onMouseLeave,
+        onMouseOut:        this.onMouseOut,
+        onTouchCancel:     this.onTouchCancel,
+        onTouchEnd:        this.onTouchEnd,
+        onTouchMove:       this.onTouchMove,
+        onTouchStart:      this.onTouchStart,
+        onKeyDown:         this.onKeyDown,
+        onKeyPress:        this.onKeyPress,
+        onKeyUp:           this.onKeyUp,
+        onFocus:           this.onFocus,
+        onBlur:            this.onBlur
+      },
+      button_icon,
+      React.createElement("span", {className: "rw-button__label"}, display)
+    );
+    return button;
+  }
+});
diff --git a/skyquake/framework/widgets/button/sq-button.jsx b/skyquake/framework/widgets/button/sq-button.jsx
new file mode 100644
index 0000000..ae93128
--- /dev/null
+++ b/skyquake/framework/widgets/button/sq-button.jsx
@@ -0,0 +1,55 @@
+import React from 'react';
+
+import 'style/base.scss';
+import './button.scss';
+
+const icons = {
+    check: require("style/icons/svg-sprite-navigation-symbol.svg")  + "#ic_check_24px"
+}
+
+export default class SqButton extends React.Component {
+    constructor(props) {
+        super(props);
+    }
+    render() {
+        let {icon, primary, size, disabled, label, ...props} = this.props;
+        let svgHTML = null;
+        let Class = "SqButton";
+        if(icon) {
+            svgHTML = <svg className="svg-24px SqButton-icon">
+            <use xlinkHref={icons[icon]}></use></svg>;
+        }
+        if(primary) {
+            Class += " SqButton--primary";
+        } else {
+            Class += " SqButton--normal";
+        }
+        if(size && (
+                size == 'small'
+                || size == 'medium'
+                || size == 'large'
+                )
+            ) {
+            Class += " SqButton--" + size;
+        }
+        if(disabled) {
+            Class += " is-disabled";
+        }
+        return (
+                <div style={{display: 'flex'}}>
+            <div className={Class} tabIndex="0">
+            {svgHTML}
+              <div className="SqButton-content">{label}</div>
+            </div>
+            </div>
+        )
+    }
+}
+
+SqButton.defaultProps = {
+    icon: false,
+    primary: false,
+    disabled: false,
+    size: false, // 'small', 'medium', 'large'
+    label: 'Submit'
+}
diff --git a/skyquake/framework/widgets/components.js b/skyquake/framework/widgets/components.js
new file mode 100644
index 0000000..1fe49bc
--- /dev/null
+++ b/skyquake/framework/widgets/components.js
@@ -0,0 +1,380 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+var React = require('react');
+//var Histogram = require('react-d3-histogram')
+export default {
+  //test: require('./test/test.js'),
+  button: require('./button/rw.button.js'),
+  React: React,
+//  Histogram: Histogram,
+  Multicomponent: require('./multicomponent/multicomponent.js'),
+  Mixins: require('./mixins/ButtonEventListener.js'),
+  Gauge: require('./gauge/gauge.js'),
+  Bullet: require('./bullet/bullet.js')
+};
+
+// require('../../assets/js/n3-line-chart.js');
+// var Gauge = require('../../assets/js/gauge-modified.js');
+// var bulletController = function($scope, $element) {
+//     this.$element = $element;
+//     this.vertical = false;
+//     this.value = 0;
+//     this.min = 0;
+//     this.max = 100;
+//     //this.range = this.max - this.min;
+//     //this.percent = (this.value - this.min) / this.range;
+//     this.displayValue = this.value;
+//     this.isPercent = (this.units == '')? true:false;
+//     this.bulletColor = "#6BB814";
+//     this.fontsize = 28;
+//     this.radius = 4;
+//     this.containerMarginX = 0;
+//     this.containerMarginY = 0;
+//     this.textMarginX = 5;
+//     this.textMarginY = 42;
+//     this.bulletMargin = 0;
+//     this.width = 512;
+//     this.height = 64;
+//     this.markerX = -100; // puts it off screen unless set
+//     var self = this;
+//     if (this.isPercent) {
+//         this.displayValue + "%";
+//     }
+//     $scope.$watch(
+//       function() {
+//         return self.value;
+//       },
+//       function() {
+//         self.valueChanged();
+//       }
+//     );
+
+//   }
+
+//   bulletController.prototype = {
+
+//     valueChanged: function() {
+//       var range = this.max - this.min;
+//       var normalizedValue = (this.value - this.min) / range;
+//       if (this.isPercent) {
+//         this.displayValue = String(Math.round(normalizedValue * 100)) + "%";
+//       } else {
+//         this.displayValue = this.value;
+//       }
+//       // All versions of IE as of Jan 2015 does not support inline CSS transforms on SVG
+//       if (platform.name == 'IE') {
+//         this.bulletWidth = Math.round(100 * normalizedValue) + '%';
+//       } else {
+//         this.bulletWidth = this.width - (2 * this.containerMarginX);
+//         var transform = 'scaleX(' + normalizedValue + ')';
+//         var bullet = $(this.$element).find('.bullet2');
+//         bullet.css('transform', transform);
+//         bullet.css('-webkit-transform', transform);
+//       }
+//     },
+
+//     markerChanged: function() {
+//       var range = this.max - this.min;
+//       var w = this.width - (2 * this.containerMarginX);
+//       this.markerX = this.containerMarginX + ((this.marker - this.min) / range ) * w;
+//       this.markerY1 = 7;
+//       this.markerY2 = this.width - 7;
+//     }
+//   }
+
+// angular.module('components', ['n3-line-chart'])
+//     .directive('rwBullet', function() {
+//       return {
+//         restrict : 'E',
+//         templateUrl: 'modules/views/rw.bullet.tmpl.html',
+//         bindToController: true,
+//         controllerAs: 'bullet',
+//         controller: bulletController,
+//         replace: true,
+//         scope: {
+//           min : '@?',
+//           max : '@?',
+//           value : '@',
+//           marker: '@?',
+//           units: '@?',
+//           bulletColor: '@?',
+//           label: '@?'
+//         }
+//       };
+//     })
+//     .directive('rwSlider', function() {
+//       var controller = function($scope, $element, $timeout) {
+//         // Q: is there a way to force attributes to be ints?
+//         $scope.min = $scope.min || "0";
+//         $scope.max = $scope.max || "100";
+//         $scope.step = $scope.step || "1";
+//         $scope.height = $scope.height || "30";
+//         $scope.orientation = $scope.orientation || 'horizontal';
+//         $scope.tooltipInvert = $scope.tooltipInvert || false;
+//         $scope.percent = $scope.percent || false;
+//         $scope.kvalue = $scope.kvalue || false;
+//         $scope.direction = $scope.direction || "ltr";
+//         $($element).noUiSlider({
+//           start: parseInt($scope.value),
+//           step: parseInt($scope.step),
+//           orientation: $scope.orientation,
+//           range: {
+//             min: parseInt($scope.min),
+//             max: parseInt($scope.max)
+//           },
+//           direction: $scope.direction
+//         });
+//         //$(".no-Ui-target").Link('upper').to('-inline-<div class="tooltip"></div>')
+//         var onSlide = function(e, value) {
+//           $timeout(function(){
+//             $scope.value = value;
+//           })
+
+//         };
+//         $($element).on({
+//           change: onSlide,
+//           slide: onSlide,
+//           set: $scope.onSet({value: $scope.value})
+//         });
+//         var val = String(Math.round($scope.value));
+//         if ($scope.percent) {
+//             val += "%"
+//         } else if ($scope.kvalue) {
+//             val += "k"
+//         }
+//         $($element).height($scope.height);
+//         if ($scope.tooltipInvert) {
+//             $($element).find('.noUi-handle').append("<div class='tooltip' style='position:relative;right:20px'>" + val + "</div>");
+//         } else {
+//             $($element).find('.noUi-handle').append("<div class='tooltip' style='position:relative;left:-20px'>" + val + "</div>");
+//         }
+//         $scope.$watch('value', function(value) {
+//         var val = String(Math.round($scope.value));
+//         if ($scope.percent) {
+//             val += "%"
+//         } else if($scope.kvalue) {
+//             val += "k"
+//         }
+//           $($element).val(value);
+//           $($element).find('.tooltip').html(val);
+//         if ($scope.tooltipInvert) {
+//             $($element).find('.tooltip').css('right', $($element).find('.tooltip').innerWidth() * -1);
+//         } else {
+//             $($element).find('.tooltip').css('left', $($element).find('.tooltip').innerWidth() * -1);
+//         }
+//         });
+//       };
+
+//       return {
+//         restrict : 'E',
+//         template: '<div></div>',
+//         controller : controller,
+//         replace: true,
+//         scope: {
+//           min : '@',
+//           max : '@',
+//           width: '@',
+//           height: '@',
+//           step : '@',
+//           orientation : '@',
+//           tooltipInvert: '@',
+//           percent: '@',
+//           kvalue: '@?',
+//           onSet:'&?',
+//           direction: '@?',
+//           value:'=?'
+//         }
+//       };
+//     })
+// .directive('rwGauge', function() {
+//     return {
+//         restrict: 'AE',
+//         template: '<canvas class="rwgauge" style="width:100%;height:100%;max-width:{{width}}px;max-height:240px;"></canvas>',
+//         replace: true,
+//         scope: {
+//             min: '@?',
+//             max: '@?',
+//             size: '@?',
+//             color: '@?',
+//             value: '@?',
+//             resize: '@?',
+//             isAggregate: '@?',
+//             units: '@?',
+//             valueFormat: '=?',
+//             width: '@?'
+//         },
+//         bindToController: true,
+//         controllerAs: 'gauge',
+//         controller: function($scope, $element) {
+//             var self = this;
+//             this.gauge = null;
+//             this.min = this.min || 0;
+//             this.max = this.max || 100;
+//             this.nSteps = 14;
+//             this.size = this.size || 300;
+//             this.units = this.units || '';
+//             $scope.width = this.width || 240;
+//             this.color = this.color || 'hsla(212, 57%, 50%, 1)';
+//             if (!this.valueFormat) {
+//                 if (this.max > 1000 || this.value) {
+//                     self.valueFormat = {
+//                         "int": 1,
+//                         "dec": 0
+//                     };
+//                 } else {
+//                     self.valueFormat = {
+//                         "int": 1,
+//                         "dec": 2
+//                     };
+//                 }
+//             }
+//             this.isAggregate = this.isAggregate || false;
+//             this.resize = this.resize || false;
+//             if (this.format == 'percent') {
+//                 self.valueFormat = {
+//                     "int": 3,
+//                     "dec": 0
+//                 };
+//             }
+//             $scope.$watch(function() {
+//                 return self.max;
+//             }, function(n, o) {
+//                 if(n !== o) {
+//                     renderGauge();
+//                 }
+//             });
+//             $scope.$watch(function() {
+//                 return self.valueFormat;
+//             }, function(n, o) {
+//                 if(n != 0) {
+//                     renderGauge();
+//                 }
+//             });
+//             $scope.$watch(function() {
+//                 return self.value;
+//             }, function() {
+//                 if (self.gauge) {
+//                     // w/o rounding gauge will unexplainably thrash round.
+//                     self.valueFormat = determineValueFormat(self.value);
+//                     self.gauge.setValue(Math.ceil(self.value * 100) / 100);
+//                     //self.gauge.setValue(Math.round(self.value));
+//                 }
+//             });
+//             angular.element($element).ready(function() {
+//                 console.log('rendering')
+//                 renderGauge();
+//             })
+//             window.testme = renderGauge;
+//             function determineValueFormat(value) {
+
+//                     if (value > 999 || self.units == "%") {
+//                         return {
+//                             "int": 1,
+//                             "dec": 0
+//                         }
+//                     }
+
+//                     return {
+//                         "int": 1,
+//                         "dec": 2
+//                     }
+//                 }
+//             function renderGauge(calcWidth) {
+//                 if (self.max == self.min) {
+//                     self.max = 14;
+//                 }
+//                 var range = self.max - self.min;
+//                 var step = Math.round(range / self.nSteps);
+//                 var majorTicks = [];
+//                 for (var i = 0; i <= self.nSteps; i++) {
+//                     majorTicks.push(self.min + (i * step));
+//                 };
+//                 var redLine = self.min + (range * 0.9);
+//                 var config = {
+//                     isAggregate: self.isAggregate,
+//                     renderTo: angular.element($element)[0],
+//                     width: calcWidth || self.size,
+//                     height: calcWidth || self.size,
+//                     glow: false,
+//                     units: self.units,
+//                     title: false,
+//                     minValue: self.min,
+//                     maxValue: self.max,
+//                     majorTicks: majorTicks,
+//                     valueFormat: determineValueFormat(self.value),
+//                     minorTicks: 0,
+//                     strokeTicks: false,
+//                     highlights: [],
+//                     colors: {
+//                         plate: 'rgba(0,0,0,0)',
+//                         majorTicks: 'rgba(15, 123, 182, .84)',
+//                         minorTicks: '#ccc',
+//                         title: 'rgba(50,50,50,100)',
+//                         units: 'rgba(50,50,50,100)',
+//                         numbers: '#fff',
+//                         needle: {
+//                             start: 'rgba(255, 255, 255, 1)',
+//                             end: 'rgba(255, 255, 255, 1)'
+//                         }
+//                     }
+//                 };
+//                 var min = config.minValue;
+//                 var max = config.maxValue;
+//                 var N = 1000;
+//                 var increment = (max - min) / N;
+//                 for (i = 0; i < N; i++) {
+//                     var temp_color = 'rgb(0, 172, 238)';
+//                     if (i > 0.5714 * N && i <= 0.6428 * N) {
+//                         temp_color = 'rgb(0,157,217)';
+//                     } else if (i >= 0.6428 * N && i < 0.7142 * N) {
+//                         temp_color = 'rgb(0,142,196)';
+//                     } else if (i >= 0.7142 * N && i < 0.7857 * N) {
+//                         temp_color = 'rgb(0,126,175)';
+//                     } else if (i >= 0.7857 * N && i < 0.8571 * N) {
+//                         temp_color = 'rgb(0,122,154)';
+//                     } else if (i >= 0.8571 * N && i < 0.9285 * N) {
+//                         temp_color = 'rgb(0,96,133)';
+//                     } else if (i >= 0.9285 * N) {
+//                         temp_color = 'rgb(0,80,112)';
+//                     }
+//                     config.highlights.push({
+//                         from: i * increment,
+//                         to: increment * (i + 2),
+//                         color: temp_color
+//                     })
+//                 }
+//                 var updateSize = _.debounce(function() {
+//                     config.maxValue = self.max;
+//                     var clientWidth = self.parentNode.parentNode.clientWidth / 2;
+//                     var calcWidth = (300 > clientWidth) ? clientWidth : 300;
+//                     self.gauge.config.width = self.gauge.config.height = calcWidth;
+//                     self.renderGauge(calcWidth);
+//                 }, 500);
+//                 if (self.resize) $(window).resize(updateSize)
+//                 if (self.gauge) {
+//                     self.gauge.updateConfig(config);
+//                 } else {
+//                     self.gauge = new Gauge(config);
+//                     self.gauge.draw();
+//                 }
+//             };
+//         },
+//     }
+// });
diff --git a/skyquake/framework/widgets/dashboard_card/dashboardCardHeader.jsx b/skyquake/framework/widgets/dashboard_card/dashboardCardHeader.jsx
new file mode 100644
index 0000000..e44c4cc
--- /dev/null
+++ b/skyquake/framework/widgets/dashboard_card/dashboardCardHeader.jsx
@@ -0,0 +1,26 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+
+class DashboardCardHeader extends React.Component {
+  constructor(props) {
+    super(props);
+
+  }
+}
diff --git a/skyquake/framework/widgets/dashboard_card/dashboard_card.jsx b/skyquake/framework/widgets/dashboard_card/dashboard_card.jsx
new file mode 100644
index 0000000..4904dd2
--- /dev/null
+++ b/skyquake/framework/widgets/dashboard_card/dashboard_card.jsx
@@ -0,0 +1,105 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import './dashboard_card.scss';
+
+var cardClass = 'dashboardCard'//classSet(this.props.class);
+
+var CardHeader = React.createClass({
+  render() {
+    var cardClassHeader = cardClass + '_header';
+    if(this.props.className) {
+        cardClassHeader += ' ' + this.props.className + '_header';
+    }
+   return (
+    <header className={cardClassHeader}>
+      <h3>
+        {this.props.title}
+      </h3>
+    </header>
+    )
+  }
+});
+ CardHeader.defaultProps = {
+  title: ' Loading...'
+ }
+
+
+
+var dashboardCard = React.createClass({
+    componentDidMount: function() {
+
+    },
+    getDefaultProps: function() {
+      return {
+        isHidden: false
+      }
+    },
+    render() {
+      var cardClassWrapper = cardClass;
+      var cardClassContent = cardClass + '_content';
+      var cardClassContentBody = cardClassContent + '-body';
+      var hasHeader;
+      var cardClasses = [];
+      if(this.props.className) {
+        cardClasses = this.props.className.split(' ');
+        cardClasses.map(function(c, i) {
+          cardClassWrapper += ' ' + c;
+          cardClassContent += ' ' + c + '_content';
+          cardClassContentBody += ' ' + c + '-body';
+        })
+
+      }
+      let closeCard = null;
+    if (this.props.showHeader) {
+      hasHeader = <CardHeader className={this.props.className} title={this.props.title}/>;
+    };
+    if (this.props.closeCard) {
+      closeCard = this.props.closeCard;
+    }
+    return (
+        <div className={cardClassWrapper} style={{display: this.props.isHidden ? 'none':'inherit'}}>
+          {closeCard}
+          <i className="corner-accent top left"></i>
+          <i className="corner-accent top right"></i>
+            {hasHeader}
+            <div className={cardClassContent}>
+              <div className={cardClassContentBody}>
+                {this.props.children}
+              </div>
+            </div>
+          <i className="corner-accent bottom left"></i>
+          <i className="corner-accent bottom right"></i>
+        </div>
+      )
+  }
+})
+
+
+// class DashboardCard extends React.Component {
+//   constructor(props) {
+//     super(props)
+//   }
+//   render() {
+
+//   }
+// }
+
+
+export default dashboardCard;
diff --git a/skyquake/framework/widgets/dashboard_card/dashboard_card.scss b/skyquake/framework/widgets/dashboard_card/dashboard_card.scss
new file mode 100644
index 0000000..9a33dc5
--- /dev/null
+++ b/skyquake/framework/widgets/dashboard_card/dashboard_card.scss
@@ -0,0 +1,57 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+@import '../../style/_colors.scss';
+//Needs to be refactored
+.dashboardCard {
+  &_wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    padding: 0.5rem;
+  }
+
+  background-color: $body-color;
+  position: relative;
+  // height: 750px;
+  width: 693px;
+  margin: 0.5rem 1rem;
+  align-content: flex-start;
+  flex-direction: column;
+  &_header {
+    display: flex;
+    align-items: center;
+    padding-left: 1rem;
+    background-color: $secondary-header;
+    text-transform: uppercase;
+
+    h3 {
+      padding: 1.5rem;
+    }
+  }
+  &_content {
+    display: flex;
+    flex-direction: row;
+    flex: 1;
+    overflow:hidden;
+    &-body{
+      display:flex;
+      flex-direction:column;
+      flex:1;
+    }
+  }
+}
diff --git a/skyquake/framework/widgets/filter/filter.jsx b/skyquake/framework/widgets/filter/filter.jsx
new file mode 100644
index 0000000..0ee7af5
--- /dev/null
+++ b/skyquake/framework/widgets/filter/filter.jsx
@@ -0,0 +1,84 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require('react');
+var Slider = require('react-slick');
+// require('../../components/gauge/gauge.js');
+// require('../../components/text-area/rw.text-area.js');
+// require('../../components/test/multicomponent.js');
+import button from '../../components/components.js'
+
+require('./carousel.css');
+var SimpleSlider = React.createClass({
+  propTypes: {
+    component_list:           React.PropTypes.array.isRequired,
+    slideno:                  React.PropTypes.number
+  },
+  handleClick: function() {
+    this.setState({});
+  },
+  getInitialState: function() {
+    return {
+      }
+    
+  },
+  shouldComponentUpdate: function(nextProps) {
+
+    if (nextProps.slideno != this.props.slideno) {
+      return true;
+    }
+    return false;
+  },
+  render: function () {
+    // var settings = {
+    //   dots: true,
+    //   infinite: false,
+    //   speed: 500,
+    //   slidesToShow: 1,
+    //   slidesToScroll: 1,
+    //   centerMode: true,
+    //   initialSlide: this.props.slideno || 2
+    // };
+    var settings = {
+        dots: false,
+        infinite: false,
+        speed: 500,
+        slidesToShow: 1,
+        slidesToScroll: 1,
+        centerMode: true,
+        initialSlide: this.props.slideno || 0
+    }
+    setTimeout(function() {
+      window.dispatchEvent(new Event('resize'));
+    }, 1000)
+    var list = [];
+    if (this.props.component_list !== undefined) {
+      for (var i = 0; i < this.props.component_list.length; i++) {
+        list.push(<div key={i}  className={"component"}>{this.props.component_list[i]}</div>);
+      }
+    }
+    return (
+      <div>
+      <Slider {...settings}>
+        {list}
+      </Slider>
+      </div>o
+    );
+  }
+});
+module.exports = SimpleSlider;
diff --git a/skyquake/framework/widgets/form_controls/formControls.scss b/skyquake/framework/widgets/form_controls/formControls.scss
new file mode 100644
index 0000000..4a88435
--- /dev/null
+++ b/skyquake/framework/widgets/form_controls/formControls.scss
@@ -0,0 +1,60 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+@import 'style/_colors.scss';
+
+.sqTextInput {
+    display: -ms-flexbox;
+    display: flex;
+    -ms-flex-direction: column;
+        flex-direction: column;
+    width: 100%;
+    margin-bottom:1rem;
+    -ms-flex-align: start;
+        align-items: flex-start;
+    -ms-flex-pack:start;
+        justify-content:flex-start;
+    span {
+        color:$darker-gray;
+        text-transform:uppercase;
+    }
+    input, .readonly, textarea {
+        height: 35px;
+        box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;
+        font-size: 1rem;
+        display: block;
+        background: white !important;
+        margin: 0;
+        margin-top: 0.25rem;
+        padding-left:0.25rem;
+        min-width:100%;
+        &[disabled] {
+            background:#ccc;
+        }
+    }
+    .readonly {
+        line-height: 35px;
+        box-shadow:none;
+    }
+    textarea {
+        -ms-flex-align: stretch;
+        -ms-grid-row-align: stretch;
+        align-items: stretch;
+        border:0px;
+        height: 100%;
+    }
+}
diff --git a/skyquake/framework/widgets/form_controls/selectOption.jsx b/skyquake/framework/widgets/form_controls/selectOption.jsx
new file mode 100644
index 0000000..41a8b13
--- /dev/null
+++ b/skyquake/framework/widgets/form_controls/selectOption.jsx
@@ -0,0 +1,59 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+
+export default class SelectOption extends React.Component {
+  constructor(props){
+    super(props);
+    this.state = {};
+  }
+  handleOnChange = (e) => {
+    this.props.onChange(e);
+  }
+  render() {
+    let html;
+    let defaultValue = this.props.defaultValue;
+    let options =  this.props.options.map(function(op, i) {
+      let value = JSON.stringify(op.value);
+      return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
+    });
+    if (this.props.initial) {
+      options.unshift(<option key='blank' value={JSON.stringify(this.props.defaultValue)}></option>);
+    }
+    html = (
+        <label>
+            {this.props.label}
+            <select className={this.props.className} onChange={this.handleOnChange} defaultValue={JSON.stringify(defaultValue)} >
+                {
+                 options
+                }
+            </select>
+        </label>
+    );
+    return html;
+  }
+}
+SelectOption.defaultProps = {
+  options: [],
+  onChange: function(e) {
+    console.dir(e)
+  },
+  defaultValue: false,
+  initial: false,
+  label: null
+}
diff --git a/skyquake/framework/widgets/form_controls/textInput.jsx b/skyquake/framework/widgets/form_controls/textInput.jsx
new file mode 100644
index 0000000..03dfa9c
--- /dev/null
+++ b/skyquake/framework/widgets/form_controls/textInput.jsx
@@ -0,0 +1,75 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import './formControls.scss';
+
+import React, {Component} from 'react';
+
+export default class TextInput extends Component {
+    render() {
+        let {label, onChange, value, defaultValue, ...props} = this.props;
+        let inputProperties = {
+            value: value,
+            onChange: onChange
+        }
+        let isRequired;
+        let inputType;
+        if(this.props.required) {
+           isRequired = <span className="required">*</span>
+        }
+        if (defaultValue) {
+            inputProperties.defaultValue = defaultValue;
+        }
+        if (props.pattern) {
+            inputProperties.pattern = props.pattern;
+        }
+        if (value == undefined) {
+            value = defaultValue;
+        }
+        switch(props.type) {
+            case 'textarea':
+                inputType = <textarea {...inputProperties} value={value}/>
+
+                break;
+            default:
+                inputType = <input type={props.type} {...inputProperties} placeholder={props.placeholder}/>;
+        }
+        let html = (
+            <label className={"sqTextInput " + props.className} style={props.style}>
+              <span> { label } {isRequired}</span>
+              {
+                !props.readonly ? inputType : <div className="readonly">{value}</div>
+              }
+
+            </label>
+        );
+        return html;
+    }
+}
+
+TextInput.defaultProps = {
+    onChange: function(e) {
+        console.log(e.target.value);
+    },
+    label: '',
+    defaultValue: undefined,
+    type: 'text',
+    readonly: false,
+    style:{}
+
+}
+
diff --git a/skyquake/framework/widgets/gauge/gauge.js b/skyquake/framework/widgets/gauge/gauge.js
new file mode 100644
index 0000000..dc1a083
--- /dev/null
+++ b/skyquake/framework/widgets/gauge/gauge.js
@@ -0,0 +1,275 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require("react");
+var ReactDOM = require('react-dom');
+var MIXINS = require("../mixins/ButtonEventListener.js");
+var Gauge = require("../../js/gauge-modified.js");
+var GUID = require("utils/guid");
+import _ from 'underscore'
+
+
+
+
+/**
+ *  Gauge Component
+ *  It's props values and a brief description below
+ *
+ *  min:         minimum value expected
+ *  max:         maximum value expected
+ *  width:       width of gauge in px
+ *  height:      height of gauge in px
+ *  value:       the number displayed on the gauge
+ *  resize:      should the gauge resize with container
+ *  unit:        the units displayed on the gauge
+ *  valueFormat: An object with an 'int' and 'dec' property. The 'int' is the min number of integer digits displayed
+ *                 and the 'dec' object is the min number of fractional digits displayed.
+ *
+ *
+ **/
+module.exports = React.createClass({
+  displayName: 'Gauge',
+  mixins:MIXINS,
+  propTypes: {
+    min:           React.PropTypes.number,
+    max:           React.PropTypes.number,
+    width:         React.PropTypes.number,
+    height:        React.PropTypes.string,
+    value:         React.PropTypes.number,
+    resize:        React.PropTypes.bool,
+    isAggregate:   React.PropTypes.bool,
+    units:         React.PropTypes.string,
+    valueFormat:   React.PropTypes.shape({
+      'int':         React.PropTypes.number,
+      'dec':         React.PropTypes.number
+    })
+  },
+  clone: function(obj) {
+      if (null == obj || "object" != typeof obj) return obj;
+      var copy = obj.constructor();
+      for (var attr in obj) {
+          if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
+      }
+      return copy;
+  },
+
+  /**
+   * Defines default state.
+   *
+   *  min:         minimum value expected
+   *  max:         maximum value expected
+   *  nSteps:      fixed number for now. The number of ticks in the gauge.
+   *  width:       width of gauge in px
+   *  height:      height of gauge in px
+   *  value:       the number displayed on the gauge
+   *  resize:      should the gauge resize with container
+   *  unit:        the units displayed on the gauge
+   *  valueFormat: An object with an 'int' and 'dec' property. The 'int' is the min number of integer digits displayed
+   *                 and the 'dec' object is the min number of fractional digits displayed.
+   */
+  getInitialState: function() {
+    var valueFormatState = null
+    this.gauge = null;
+    this.gaugeID = GUID();
+    if (!this.props.valueFormat) {
+        if ((this.props.max && this.props.max > 1000) || this.props.value) {
+            valueFormatState = {
+                "int": 1,
+                "dec": 0
+            };
+        } else {
+            valueFormatState = {
+                "int": 1,
+                "dec": 2
+            };
+        }
+    } else {
+      valueFormatState = this.props.valueFormat;
+    }
+    return {
+      //sizeOfButton:   this.props.size || '',  //There is no Medium value in CSS, default size is the absence of a value
+      min: this.props.min || 0,
+      max: this.props.max || 0,
+      nSteps: 14,
+      height: this.props.height || 200,
+      width: this.props.width || 200,
+      color: this.props.color || 'hsla(212, 57%, 50%, 1)',
+      value: this.props.value || 0,
+      valueFormat: valueFormatState,
+      isAggregate: this.props.isAggregate || false,
+      units: this.props.units || '',
+      resize:this.props.resize || false
+
+    }
+  },
+
+
+  /**
+   *  Called when props are changed.  Syncs props with state.
+   */
+  componentWillReceiveProps: function(nextProps) {
+    this.setState({
+      max:nextProps.max || this.state.max,
+      value:nextProps.value || 0,
+      valueFormat:nextProps.valueFormat || this.state.valueFormat
+    });
+  },
+
+  /**
+   * Calls the render on the gauge object once the component first mounts
+   */
+  componentDidMount: function() {
+    this.canvasRender(this.state);
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * Note, this is where the render step occures for the gauge object.
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = String(this.state.max) + String(this.state.valueFormat.int) + String(this.state.valueFormat.dec) + String(this.state.value);
+    var nextStateString = String(nextState.max) + String(nextState.valueFormat.int) + String(nextState.valueFormat.dec) + String(nextState.value);
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    this.state.valueFormat = this.determineValueFormat(nextState.value);
+    this.canvasRender(nextState);
+    return true;
+  },
+
+  /**
+   * Default value format based on units.
+   */
+  determineValueFormat: function(value) {
+          if (value > 999 || this.state.units == "%") {
+              return {
+                  "int": 1,
+                  "dec": 0
+              }
+          }
+
+          return {
+              "int": 1,
+              "dec": 2
+          }
+      },
+
+
+  /**
+   * Render step for the gauge object. Sets some defaults, passes some of the component's state down.
+   */
+  canvasRender: function(state) {
+    if (state.max == state.min) {
+        state.max = 14;
+    }
+    var range = state.max - state.min;
+    var step = Math.round(range / state.nSteps);
+    var majorTicks = [];
+    for (var i = 0; i <= state.nSteps; i++) {
+        majorTicks.push(state.min + (i * step));
+    }
+    var redLine = state.min + (range * 0.9);
+    var config = {
+        isAggregate: state.isAggregate,
+        renderTo: ReactDOM.findDOMNode(document.getElementById(this.gaugeID)),
+        width: state.width,
+        height: state.height,
+        glow: false,
+        units: state.units,
+        title: false,
+        minValue: state.min,
+        maxValue: state.max,
+        majorTicks: majorTicks,
+        valueFormat: this.determineValueFormat(self.value),
+        minorTicks: 0,
+        strokeTicks: false,
+        highlights: [],
+        colors: {
+            plate: 'rgba(0,0,0,0)',
+            majorTicks: 'rgba(15, 123, 182, .84)',
+            minorTicks: '#ccc',
+            title: 'rgba(50,50,50,100)',
+            units: 'rgba(50,50,50,100)',
+            numbers: '#fff',
+            needle: {
+                start: 'rgba(255, 255, 255, 1)',
+                end: 'rgba(255, 255, 255, 1)'
+            }
+        }
+    };
+
+    var min = config.minValue;
+    var max = config.maxValue;
+    var N = 1000;
+    var increment = (max - min) / N;
+    for (i = 0; i < N; i++) {
+        var temp_color = 'rgb(0, 172, 238)';
+        if (i > 0.5714 * N && i <= 0.6428 * N) {
+            temp_color = 'rgb(0,157,217)';
+        } else if (i >= 0.6428 * N && i < 0.7142 * N) {
+            temp_color = 'rgb(0,142,196)';
+        } else if (i >= 0.7142 * N && i < 0.7857 * N) {
+            temp_color = 'rgb(0,126,175)';
+        } else if (i >= 0.7857 * N && i < 0.8571 * N) {
+            temp_color = 'rgb(0,122,154)';
+        } else if (i >= 0.8571 * N && i < 0.9285 * N) {
+            temp_color = 'rgb(0,96,133)';
+        } else if (i >= 0.9285 * N) {
+            temp_color = 'rgb(0,80,112)';
+        }
+        config.highlights.push({
+            from: i * increment,
+            to: increment * (i + 2),
+            color: temp_color
+        })
+    }
+    var updateSize = _.debounce(function() {
+        config.maxValue = state.max;
+    }, 500);
+    if (state.resize) $(window).resize(updateSize)
+
+    if (this.gauge) {
+        this.gauge.setValue(Math.ceil(state.value* 100) / 100)
+        this.gauge.updateConfig(config);
+    } else {
+        this.gauge = new Gauge(config);
+        this.gauge.setValue(Math.ceil(state.value* 100) / 100)
+        this.gauge.draw();
+    }
+  },
+
+  /**
+   * Renders the Gauge Component
+   * Returns the canvas element the gauge will be housed in.
+   * @returns {*}
+   */
+  render: function() {
+    var gaugeDOM = React.createElement("div", null,
+      React.createElement("canvas",
+        {className: "rwgauge", style:
+          {width:'100%','maxWidth':this.state.width + 'px','maxHeight':this.state.width},
+          id:this.gaugeID
+        }
+      )
+    )
+
+
+
+    return gaugeDOM;
+  }
+});
diff --git a/skyquake/framework/widgets/header/header.jsx b/skyquake/framework/widgets/header/header.jsx
new file mode 100644
index 0000000..21f4c10
--- /dev/null
+++ b/skyquake/framework/widgets/header/header.jsx
@@ -0,0 +1,152 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+import React from 'react';
+import './header.scss';
+import Crouton from 'react-crouton';
+import HeaderStore from './headerStore.js';
+import ScreenLoader from '../screen-loader/screenLoader.jsx';
+export default class AppHeader extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {};
+        this.state.validateErrorEvent = 0;
+        this.state.validateErrorMsg = '';
+    }
+    componentDidMount() {
+        HeaderStore.listen(this.storeListener);
+    }
+    componentWillUnmount(){
+        HeaderStore.unlisten(this.storeListener);
+    }
+    closeError() {
+        LaunchpadFleetActions.validateReset()
+    }
+    storeListener = (state) => {
+        this.setState(state);
+    }
+    render() {
+        let html;
+        let navItems = this.props.nav.map(function(nav, i) {
+            if (nav.href || nav.onClick) {
+                return <li key={i}><a {...nav}>{nav.name}</a></li>
+            } else {
+                return <li key={i}><span className="current"> {nav.name} </span></li>
+            }
+        });
+        let errorMessage = <Crouton
+                id={Date.now()}
+                message={this.state.validateErrorMsg}
+                type={"error"}
+                hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
+                onDismiss={HeaderStore.validateReset}
+            />
+
+        // html = (
+        //     <header className="header-app-component">
+        //     {errorMessage}
+        //     <ScreenLoader show={this.props.isLoading}/>
+        //     <div className="header-app-main">
+        //         <h1>{this.props.title}</h1>
+        //         <CommonLinks></CommonLinks>
+        //     </div>
+        //     <div className="header-app-nav">
+        //         <ul>
+        //             {navItems}
+        //         </ul>
+        //     </div>
+        //     </header>
+        // );
+
+        html = (
+            <header className="header-app-component">
+            {errorMessage}
+            <ScreenLoader show={this.props.isLoading}/>
+            <div className="header-app-nav">
+                <ul>
+                    {navItems}
+                </ul>
+            </div>
+            </header>
+        );
+
+        return html;
+    }
+}
+export class CommonLinks extends React.Component {
+    constructor(props) {
+        super(props);
+    }
+    openAbout() {
+        generateRefPage();
+        navigateTo('about')
+    }
+    openDebug() {
+        generateRefPage();
+        navigateTo('debug');
+    }
+    render() {
+        let links = [];
+        setContext();
+        links.push(<li key={'about'} onClick={this.openAbout}><a>About</a></li>);
+        links.push(<li  key={'debug'} onClick={this.openDebug}><a>Debug</a></li>);
+        let html;
+        html = (
+            <nav>
+                <ul>
+                    {links}
+                </ul>
+            </nav>
+        );
+        return html;
+    }
+}
+AppHeader.defaultProps = {
+    nav: [],
+    isLoading: false
+}
+function generateRefPage() {
+    let applicationContext = window.sessionStorage.getItem('applicationContext') || 'launchpad';
+    let hash = window.location.hash.split('/');
+    let pageTitle = 'Dashboard';
+    if (applicationContext === 'launchpad') {
+        hash = '#/launchpad/' + hash[2];
+    } else {
+        hash = "#/"
+    }
+    let refPage = {
+        'hash': hash,
+        'title': pageTitle
+    };
+    window.sessionStorage.setItem('refPage', JSON.stringify(refPage));
+    return refPage;
+}
+function navigateTo(loc) {
+    window.location.hash = '#/' + loc;
+}
+function setContext() {
+     let applicationContext;
+     let hashOne = window.location.hash.split('/')[1];
+    if(hashOne == "launchpad") {
+      applicationContext = "launchpad";
+    } else {
+      applicationContext = "missioncontrol";
+    }
+    if(hashOne != 'about' && hashOne != 'debug'){
+        window.sessionStorage.setItem('applicationContext', applicationContext);}
+}
diff --git a/skyquake/framework/widgets/header/header.scss b/skyquake/framework/widgets/header/header.scss
new file mode 100644
index 0000000..9238c22
--- /dev/null
+++ b/skyquake/framework/widgets/header/header.scss
@@ -0,0 +1,87 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+header.header-app-component {
+    padding: 20px 0px;
+    display:flex;
+    flex-direction:column;
+    .header-app-main {
+        display:flex;
+        flex-direction:row;
+        justify-content:space-between;
+        align-items:center;
+    }
+    h1 {
+        background: url('../../style/img/header-logo.png') no-repeat;
+        background-size:contain;
+        height: 51px;
+        line-height: 51px;
+        margin-left: 20px;
+        padding-left: 100px;
+        left: 0;
+        text-transform: uppercase;
+        font-size: 1.625rem;
+        font-weight: 400;
+        position:relative;
+        flex: 1 0 auto;
+
+    }
+    ul {
+            display:flex;
+        }
+        li {
+            display:flex;
+             flex:1 1 auto;
+             border-right:1px solid #e5e5e5;
+             padding: 0 1rem;
+            &:last-child {
+                border:none;
+            }
+            a {
+                cursor:pointer;
+                // padding: 0.125rem;
+                // border-bottom:1px solid black;
+                text-decoration:underline;
+            }
+        }
+    .header-app-nav {
+        display:flex;
+        margin-left: 0.25rem;
+        a,span {
+            text-transform:uppercase;
+        }
+        a {
+            cursor: pointer;
+            margin-left: 0.5rem;
+            font-weight: normal;
+            color: black;
+        }
+
+        .spacer {
+
+        }
+        .current {
+            font-weight: bold;
+        }
+    }
+    nav {
+        display:flex;
+        flex:0 1 auto;
+        align-items:center;
+    }
+}
diff --git a/skyquake/framework/widgets/header/headerActions.js b/skyquake/framework/widgets/header/headerActions.js
new file mode 100644
index 0000000..e71e79f
--- /dev/null
+++ b/skyquake/framework/widgets/header/headerActions.js
@@ -0,0 +1,20 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+import Alt from '../skyquake_container/skyquakeAltInstance';
+export default Alt.generateActions('showError', 'resetError');
diff --git a/skyquake/framework/widgets/header/headerStore.js b/skyquake/framework/widgets/header/headerStore.js
new file mode 100644
index 0000000..4ee86ff
--- /dev/null
+++ b/skyquake/framework/widgets/header/headerStore.js
@@ -0,0 +1,46 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+import HeaderActions from './headerActions.js';
+import Alt from '../skyquake_container/skyquakeAltInstance';
+
+class HeaderStoreConstructor {
+    constructor() {
+        var self = this;
+        this.validateErrorEvent = 0;
+        this.validateErrorMsg = '';
+        this.bindActions(HeaderActions);
+        this.exportPublicMethods({
+            validateReset: self.validateReset
+        })
+    }
+    showError = (msg) => {
+        console.log('message received');
+        this.setState({
+            validateErrorEvent: true,
+            validateErrorMsg: msg
+        });
+    }
+    validateReset = () => {
+        this.setState({
+            validateErrorEvent: false
+        });
+    }
+}
+
+export default Alt.createStore(HeaderStoreConstructor)
diff --git a/skyquake/framework/widgets/input-range-slider/input-range-slider.jsx b/skyquake/framework/widgets/input-range-slider/input-range-slider.jsx
new file mode 100644
index 0000000..d9e0544
--- /dev/null
+++ b/skyquake/framework/widgets/input-range-slider/input-range-slider.jsx
@@ -0,0 +1,72 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+// import Slider from 'react-rangeslider';
+// import Slider from './react-rangeslider.jsx';
+import './input-range-slider.scss';
+
+
+class RWslider extends React.Component {
+  constructor(props, context) {
+    super(props, context);
+    this.state = {...props}
+  }
+
+  handleChange = (value) => {
+    this.props.handleInputUpdate(value);
+    this.setState({
+      value: value
+    });
+  };
+
+  render() {
+    let state = this.state;
+      var className = "input-range-slider_" + this.props.orientation;
+    return (
+      <div className={className}>
+      <div className={className + '-info'}>
+      {state["min-value"]}
+      </div>
+      <Slider
+        displayValue={true}
+        value={state.value}
+        max={state["max-value"]}
+        min={state["min-value"]}
+        step={state["step-value"]}
+        onChange={this.handleChange}
+        className={className + '-info'} />
+      <div className={className + '-info'}>
+      {state["max-value"]}
+      </div>
+      </div>
+    );
+  }
+}
+
+RWslider.defaultProps = {
+    value: 10,
+    "min-value": 0,
+    "max-value":100,
+    "step-value":1,
+    "units": "%",
+    orientation: "horizontal"
+}
+
+export default RWslider
+
diff --git a/skyquake/framework/widgets/input-range-slider/input-range-slider.scss b/skyquake/framework/widgets/input-range-slider/input-range-slider.scss
new file mode 100644
index 0000000..803bd8b
--- /dev/null
+++ b/skyquake/framework/widgets/input-range-slider/input-range-slider.scss
@@ -0,0 +1,104 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+@import '../../style/_colors.scss';
+
+  .input-range-slider_horizontal{
+    display:flex;
+    align-items:baseline;
+    &-info {
+      margin:0 0.5rem;
+    }
+  }
+
+
+.rangeslider {
+  margin: 20px 0;
+  position: relative;
+  background: #e6e6e6;
+  display:flex;
+  flex:1;
+  .rangeslider__fill, .rangeslider__handle {
+    position: absolute;
+  }
+  &, .rangeslider__fill {
+    display: block;
+    box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3);
+  }
+  .rangeslider__handle {
+    background: #fff;
+    border: 1px solid #ccc;
+    cursor: pointer;
+    display: inline-block;
+    position: absolute;
+    &:active {
+      background: #999;
+    }
+  }
+}
+
+/**
+ * Rangeslider - Horizontal slider
+ */
+.rangeslider-horizontal {
+  height: 1rem;
+  margin-bottom: 2.5rem;
+  .rangeslider__fill {
+    height: 100%;
+    background: $light-blue;
+    top: 0;
+  }
+  .rangeslider__handle {
+    width: 0.5rem;
+    height: 2rem;
+    top: -0.5rem;
+    left:-10px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    >div{
+          width: 5rem;
+          margin-top: 2rem;
+          text-align: center;
+    }
+  }
+}
+
+/**
+ * Rangeslider - Vertical slider
+ */
+.rangeslider-vertical {
+  margin: 20px auto;
+  height: 150px;
+  max-width: 10px;
+  background: none;
+  .rangeslider__fill {
+    width: 100%;
+    background: $light-blue;
+    box-shadow: none;
+    bottom: 0;
+  }
+  .rangeslider__handle {
+    width: 30px;
+    height: 10px;
+    left: -10px;
+    &:active {
+      box-shadow: none;
+    }
+  }
+}
diff --git a/skyquake/framework/widgets/input-range-slider/react-rangeslider.jsx b/skyquake/framework/widgets/input-range-slider/react-rangeslider.jsx
new file mode 100644
index 0000000..feff1eb
--- /dev/null
+++ b/skyquake/framework/widgets/input-range-slider/react-rangeslider.jsx
@@ -0,0 +1,251 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+/**
+ * https://github.com/larsmaultsby/react-rangeslider
+ *
+ *
+ * Forked from: https://github.com/whoisandie/react-rangeslider
+ *
+ *
+    The MIT License (MIT)
+
+    Copyright (c) 2015 Bhargav Anand
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE.
+ *
+ */
+
+import React, { PropTypes, Component, findDOMNode } from 'react';
+import joinClasses from 'react/lib/joinClasses';
+
+function capitalize(str) {
+  return str.charAt(0).toUpperCase() + str.substr(1);
+}
+
+function maxmin(pos, min, max) {
+  if (pos < min) { return min; }
+  if (pos > max) { return max; }
+  return pos;
+}
+
+const constants = {
+  orientation: {
+    horizontal: {
+      dimension: 'width',
+      direction: 'left',
+      coordinate: 'x',
+    },
+    vertical: {
+      dimension: 'height',
+      direction: 'top',
+      coordinate: 'y',
+    }
+  }
+};
+
+class Slider extends Component {
+  static propTypes = {
+    min: PropTypes.number,
+    max: PropTypes.number,
+    step: PropTypes.number,
+    value: PropTypes.number,
+    orientation: PropTypes.string,
+    onChange: PropTypes.func,
+    className: PropTypes.string,
+  }
+
+  static defaultProps = {
+    min: 0,
+    max: 100,
+    step: 1,
+    value: 0,
+    orientation: 'horizontal',
+  }
+
+  state = {
+    limit: 0,
+    grab: 0
+  }
+
+  // Add window resize event listener here
+  componentDidMount() {
+    this.calculateDimensions();
+    window.addEventListener('resize', this.calculateDimensions);
+  }
+
+  componentWillUnmount() {
+    window.removeEventListener('resize', this.calculateDimensions);
+  }
+
+  handleSliderMouseDown = (e) => {
+    let value, { onChange } = this.props;
+    if (!onChange) return;
+    value = this.position(e);
+    onChange && onChange(value);
+  }
+
+  handleKnobMouseDown = () => {
+    document.addEventListener('mousemove', this.handleDrag);
+    document.addEventListener('mouseup', this.handleDragEnd);
+  }
+
+  handleDrag = (e) => {
+    let value, { onChange } = this.props;
+    if (!onChange) return;
+    value = this.position(e);
+    onChange && onChange(value);
+  }
+
+  handleDragEnd = () => {
+    document.removeEventListener('mousemove', this.handleDrag);
+    document.removeEventListener('mouseup', this.handleDragEnd);
+  }
+
+  handleNoop = (e) => {
+    e.stopPropagation();
+    e.preventDefault();
+  }
+
+  calculateDimensions = () => {
+    let { orientation } = this.props;
+    let dimension = capitalize(constants.orientation[orientation].dimension);
+    const sliderPos = findDOMNode(this.refs.slider)['offset' + dimension];
+    const handlePos = findDOMNode(this.refs.handle)['offset' + dimension]
+    this.setState({
+      limit: sliderPos - handlePos,
+      grab: handlePos / 2,
+    });
+  }
+  getPositionFromValue = (value) => {
+    let percentage, pos;
+    let { limit } = this.state;
+    let { min, max } = this.props;
+    percentage = (value - min) / (max - min);
+    pos = Math.round(percentage * limit);
+
+    return pos;
+  }
+
+  getValueFromPosition = (pos) => {
+    let percentage, value;
+    let { limit } = this.state;
+    let { orientation, min, max, step } = this.props;
+    percentage = (maxmin(pos, 0, limit) / (limit || 1));
+
+    if (orientation === 'horizontal') {
+      value = step * Math.round(percentage * (max - min) / step) + min;
+    } else {
+      value = max - (step * Math.round(percentage * (max - min) / step) + min);
+    }
+
+    return value;
+  }
+
+  position = (e) => {
+    let pos, value, { grab } = this.state;
+    let { orientation } = this.props;
+    const node = findDOMNode(this.refs.slider);
+    const coordinateStyle = constants.orientation[orientation].coordinate;
+    const directionStyle = constants.orientation[orientation].direction;
+    const coordinate = e['client' + capitalize(coordinateStyle)];
+    const direction = node.getBoundingClientRect()[directionStyle];
+
+    pos = coordinate - direction - grab;
+    value = this.getValueFromPosition(pos);
+
+    return value;
+  }
+
+  coordinates = (pos) => {
+    let value, fillPos, handlePos;
+    let { limit, grab } = this.state;
+    let { orientation } = this.props;
+    value = this.getValueFromPosition(pos);
+    handlePos = this.getPositionFromValue(value);
+
+    if (orientation === 'horizontal') {
+      fillPos = handlePos + grab;
+    } else {
+      fillPos = limit - handlePos + grab;
+    }
+
+    return {
+      fill: fillPos,
+      handle: handlePos,
+    };
+  }
+
+  render() {
+    let dimension, direction, position, coords, fillStyle, handleStyle, displayValue;
+    let { value, orientation, className } = this.props;
+
+    dimension = constants.orientation[orientation].dimension;
+    direction = constants.orientation[orientation].direction;
+
+    position = this.getPositionFromValue(value);
+    coords = this.coordinates(position);
+
+    fillStyle = {[dimension]: `${coords.fill}px`};
+    handleStyle = {[direction]: `${coords.handle}px`};
+
+    if(this.props.displayValue) {
+      displayValue = <div>{this.props.value}</div>;
+    }
+
+    return (
+      <div
+        ref="slider"
+        className={joinClasses('rangeslider ', 'rangeslider-' + orientation, className)}
+        onMouseDown={this.handleSliderMouseDown}
+        onClick={this.handleNoop}
+        style={{display:'flex'}}>
+        <div
+          ref="fill"
+          className="rangeslider__fill"
+          style={fillStyle} />
+        <div
+          ref="handle"
+          className="rangeslider__handle"
+          onMouseDown={this.handleKnobMouseDown}
+          onClick={this.handleNoop}
+          style={handleStyle}>
+            {displayValue}
+          </div>
+      </div>
+    );
+  }
+}
+
+export default Slider;
diff --git a/skyquake/framework/widgets/listy/listy.js b/skyquake/framework/widgets/listy/listy.js
new file mode 100644
index 0000000..ac0e58a
--- /dev/null
+++ b/skyquake/framework/widgets/listy/listy.js
@@ -0,0 +1,152 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import _ from 'lodash';
+
+/**
+ *
+ *
+ */
+export default class Listy extends React.Component {
+	constructor(props) {
+ 		super(props);
+ 	}
+
+ 	/**
+
+ 	 */
+ 	createList(data, iter=0) {
+ 		var groupTag = this.props.groupTag.tag;
+ 		var groupClass = this.props.groupTag.className;
+ 		var itemTag = this.props.itemTag.tag;
+ 		var itemClass = this.props.itemTag.className;
+ 		var listHeaderTag = this.props.listHeaderTag.tag;
+ 		var listHeaderClass = this.props.listHeaderTag.className;
+
+ 		var listNode = null;
+ 		var self = this;
+ 		if (_.isArrayLike(data) && _.isObjectLike(data)) {
+ 			var children = [];
+			data.forEach(function(element, index, array) {
+				if ( _.isArrayLike(element) || _.isObjectLike(element)) {
+					children.push(self.createList(element, iter+1));
+				} else {
+					children.push(React.createElement(itemTag, {
+						key: index,
+						className: itemClass
+					}, element));
+				}
+			});
+
+			listNode = React.createElement(groupTag, {
+				key: iter,
+				className: groupClass }, children);
+ 		}
+ 		else if (_.isObjectLike(data)) {
+ 			var children = [];
+ 			for (var key in data) {
+ 				if ( _.isArrayLike(data[key]) || _.isObjectLike(data[key])) {
+ 					children.push(
+ 						React.createElement(listHeaderTag, {
+ 							key: key + '_header',
+ 							className: listHeaderClass }, key + ":")
+ 					);
+ 					children.push(
+ 						React.createElement(groupTag, {
+ 							key: key + '_list',
+ 							className: groupClass },
+ 							[this.createList(data[key], iter + 1)])
+ 					);
+
+ 				} else {
+ 					// TODO: Add span to line-wrap the data part (hanging)
+ 					children.push(React.createElement(itemTag, {
+ 						key: key,
+ 						className: itemClass},
+ 						key + ": " + data[key]));
+ 				}
+ 			}
+ 			listNode = React.createElement(groupTag, {
+ 				key: iter,
+ 				className: groupClass }, children);
+ 		} else {
+ 			listNode = React.createElement(itemTag, {
+ 				className: itemClass}, data);
+ 		}
+
+ 		return listNode;
+ 	}
+
+ 	noDataMessage() {
+ 		return React.createElement("div", {
+ 			className: this.props.noDataMessageClass},
+ 			this.props.noDataMessage);
+ 	}
+
+ 	render () {
+ 		var data = this.props.data;
+
+ 		return React.createElement("div", {
+ 			className: "listy" },
+ 			_.isEmpty(data) ? 
+ 			this.noDataMessage() : 
+ 			this.createList(data)
+ 		)
+ 	}
+}
+
+Listy.validateTagDefinition = function(props, propName, componentName) {
+	let obj = props[propName];
+	let fullAttr = componentName + "." + propName;
+
+	if (!obj)
+		return new Error('Validation failed. "%" is undefined', fullAttr);
+	if (!obj.hasOwnProperty("tag") || _.isEmpty(obj.tag))
+		return new Error('Validation failed. "%s" missing attribute "tag"', fullAttr);
+	if (!obj.hasOwnProperty("className") || obj.className == undefined)
+		return new Error('Validation failed. "%s" missing attribute "className"', fullAttr);
+}
+
+Listy.propTypes = {
+	data: React.PropTypes.object,
+	groupTag: Listy.validateTagDefinition,
+	itemTag: Listy.validateTagDefinition,
+	listHeaderTag: Listy.validateTagDefinition,	
+	debugMode: React.PropTypes.bool
+}
+
+Listy.defaultProps = {
+	data: {},
+
+	// Visual Rules
+	groupTag: {
+		tag: "ul",
+		className: "listyGroup"
+	},
+	itemTag: {
+		tag: "li",
+		className: "listyItem"
+	},
+	listHeaderTag: {
+		tag: "h2",
+		className: "listyGroupHeader"
+	},
+	noDataMessage: "No data",
+	noDataMessageClass: "listyNoDataMessage",
+	debugMode: false
+}
diff --git a/skyquake/framework/widgets/loading-indicator/loading-indicator-animations.scss b/skyquake/framework/widgets/loading-indicator/loading-indicator-animations.scss
new file mode 100644
index 0000000..44222de
--- /dev/null
+++ b/skyquake/framework/widgets/loading-indicator/loading-indicator-animations.scss
@@ -0,0 +1,53 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+.line-scale-pulse-out-rapid > div {
+  background-color: #fff;
+  width: 4px;
+  height: 2rem;
+  border-radius: 2px;
+  margin: 2px;
+  animation-fill-mode: both;
+  display: inline-block;
+  animation: line-scale-pulse-out-rapid 0.9s -0.5s infinite cubic-bezier(0.11, 0.49, 0.38, 0.78); }
+  .line-scale-pulse-out-rapid > div:nth-child(2), .line-scale-pulse-out-rapid > div:nth-child(4) {
+    animation-delay: -0.25s !important; }
+  .line-scale-pulse-out-rapid > div:nth-child(1), .line-scale-pulse-out-rapid > div:nth-child(5) {
+    animation-delay: 0s !important; }
+
+
+.loader-animation-enter, .loader-animation-appear {
+  opacity: 0.01;
+}
+
+.loader-animation-enter.loader-animation-enter-active, .loader-animation-appear.loader-animation-appear-active {
+  opacity: 1.0;
+  transition: opacity 300ms ease-in;
+}
+
+.loader-animation-leave {
+  visibility:hidden;
+  height:0px;
+  width:0px;
+  opacity:1.0;
+}
+
+.loader-animation-leave.loader-animation-leave-active {
+  opacity:0;
+  transition: opacity 300ms ease-in;
+}
diff --git a/skyquake/framework/widgets/loading-indicator/loadingIndicator.jsx b/skyquake/framework/widgets/loading-indicator/loadingIndicator.jsx
new file mode 100644
index 0000000..9e77fdd
--- /dev/null
+++ b/skyquake/framework/widgets/loading-indicator/loadingIndicator.jsx
@@ -0,0 +1,60 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import '../../../node_modules/loaders.css/src/animations/line-scale-pulse-out-rapid.scss';
+import './loading-indicator-animations.scss';
+let ReactCSSTransitionGroup = require('react-addons-css-transition-group');
+export default class Loader extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+  render() {
+    let loader = '';
+    var style = {
+      height: this.props.size + 'rem',
+      width: this.props.size * 0.15 + 'rem',
+      backgroundColor: this.props.color || 'white'
+    }
+    if (this.props.show) {
+      loader = (
+                <div
+                  transitionName="loader-animation"
+                  transitionAppear={true}
+                  component="div"
+                  className={"line-scale-pulse-out-rapid"}>
+                  <div style={style}></div>
+                  <div style={style}></div>
+                  <div style={style}></div>
+                  <div style={style}></div>
+                  <div style={style}></div>
+                </div>
+      );
+    }else {
+      loader = <span></span>
+    }
+    return loader;
+
+  }
+}
+
+Loader.defaultProps = {
+  show: true,
+  size: '5'
+}
+
diff --git a/skyquake/framework/widgets/login/login.js b/skyquake/framework/widgets/login/login.js
new file mode 100644
index 0000000..f6ce188
--- /dev/null
+++ b/skyquake/framework/widgets/login/login.js
@@ -0,0 +1,51 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require('react');
+var LoginScreen = require('./login.jsx');
+angular.module('login', ['ui.router'])
+    .config(function($stateProvider) {
+
+     $rw.nav.push({
+        module: 'login',
+        name: "Login"
+      });
+
+      $stateProvider.state('login', {
+        url: '/login',
+        replace: true,
+        template: '<login-screen></login-screen>'
+      });
+
+})
+.directive('loginScreen', function() {
+  return {
+      restrict: 'AE',
+      controller: function($element) {
+        function reactRender() {
+          React.render(
+            React.createElement(LoginScreen, null)
+            ,
+            $element[0]
+          );
+        }
+        reactRender();
+      }
+    };
+})
+    ;
diff --git a/skyquake/framework/widgets/login/login.jsx b/skyquake/framework/widgets/login/login.jsx
new file mode 100644
index 0000000..1506809
--- /dev/null
+++ b/skyquake/framework/widgets/login/login.jsx
@@ -0,0 +1,91 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import Utils from 'utils/utils.js';
+import Button from 'widgets/button/rw.button.js';
+import './login.scss'
+let rw = require('utils/rw.js');
+class LoginScreen extends React.Component{
+  constructor(props) {
+    super(props);
+    var API_SERVER =  rw.getSearchParams(window.location).api_server;
+    if (!API_SERVER) {
+      window.location.href = "//" + window.location.host + '/index.html?api_server=' + window.location.protocol + '//localhost';
+    }
+    this.state = {
+      username: '',
+      password: ''
+    };
+
+  }
+  updateValue = (e) => {
+    let state = {};
+    state[e.target.name] = e.target.value;
+    this.setState(state);
+  }
+  validate = (e) => {
+    let self = this;
+    let state = this.state;
+    e.preventDefault();
+    if (state.username == '' || state.password == '') {
+      console.log('false');
+      return false;
+    } else {
+      Utils.setAuthentication(state.username, state.password, function() {
+        //Returning to previous location disabled post port
+        //  let hash = window.sessionStorage.getItem("locationRefHash") || '#/';
+        //   if (hash == '#/login') {
+        //     hash = '#/'
+        //   }
+        // window.location.hash = hash;
+        self.context.router.push('/');
+      });
+
+    }
+  }
+  submitForm = (e) => {
+      if(e.keyCode == 13){
+        this.validate(e);
+      }
+  }
+  render() {
+    let html;
+    html = (
+      <form className="login-cntnr" autoComplete="on" onKeyUp={this.submitForm}>
+        <div className="logo"> </div>
+        <h1 className="riftio">Launchpad Login</h1>
+        <p>
+            <input type="text" placeholder="Username" name="username" value={this.state.username} onChange={this.updateValue} autoComplete="username"></input>
+        </p>
+        <p>
+            <input type="password" placeholder="Password" name="password" onChange={this.updateValue} value={this.state.password} autoComplete="password"></input>
+        </p>
+        <p>
+           <Button className="sign-in" onClick={this.validate}  style={{cursor: 'pointer'}} type="submit" label="Sign In"/>
+        </p>
+      </form>
+    )
+    return html;
+  }
+}
+LoginScreen.contextTypes = {
+    router: React.PropTypes.object
+  };
+
+
+export default LoginScreen;
diff --git a/skyquake/framework/widgets/login/login.scss b/skyquake/framework/widgets/login/login.scss
new file mode 100644
index 0000000..cb14e9b
--- /dev/null
+++ b/skyquake/framework/widgets/login/login.scss
@@ -0,0 +1,21 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+.login-cntnr .logo {
+    background-image: url(../../style/img/header-logo.png);
+
+}
diff --git a/skyquake/framework/widgets/login/loginAuthActions.js b/skyquake/framework/widgets/login/loginAuthActions.js
new file mode 100644
index 0000000..c2c805f
--- /dev/null
+++ b/skyquake/framework/widgets/login/loginAuthActions.js
@@ -0,0 +1,21 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import Alt from 'widgets/skyquake_container/skyquakeAltInstance';
+module.exports = Alt.generateActions(
+                                       'notAuthenticated'
+                                     );
diff --git a/skyquake/framework/widgets/login/main.js b/skyquake/framework/widgets/login/main.js
new file mode 100644
index 0000000..f69a91e
--- /dev/null
+++ b/skyquake/framework/widgets/login/main.js
@@ -0,0 +1,11 @@
+import { render } from 'react-dom';
+import SkyquakeRouter from 'widgets/skyquake_container/skyquakeRouter.jsx';
+const config = require('json!../config.json');
+
+let context = require.context('./', true, /^\.\/.*\.jsx$/);
+let router = SkyquakeRouter(config, context);
+let element = document.querySelector('#content');
+
+render(router, element);
+
+
diff --git a/skyquake/framework/widgets/metric-bars/metricBarGroup.jsx b/skyquake/framework/widgets/metric-bars/metricBarGroup.jsx
new file mode 100644
index 0000000..d4f92d6
--- /dev/null
+++ b/skyquake/framework/widgets/metric-bars/metricBarGroup.jsx
@@ -0,0 +1,141 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import ReactDOM from 'react-dom';
+import d3 from 'd3';
+import './metricBarGroup.scss';
+
+class MetricBarGroup extends React.Component {
+  constructor(props) {
+    super(props);
+
+  }
+  componentWillMount() {
+    const {...props} = this.props;
+    this.margin = {top: 20, right: 50, bottom: 700, left: 100};
+    this.width = 1220 - this.margin.left - this.margin.right;
+    this.height = 1220 - this.margin.top - this.margin.bottom;
+    // this.width = 800;
+    // this.height = 600;
+    this.x = d3.scale.ordinal()
+      .rangeRoundBands([0, this.width], .1);
+
+    this.y = d3.scale.linear()
+      .range([this.height, 0]);
+
+    this.xAxis = d3.svg.axis()
+      .scale(this.x)
+      .orient("bottom");
+
+    this.yAxis = d3.svg.axis()
+        .scale(this.y)
+        .orient("left")
+        .ticks(props.ticks.number, props.ticks.type);
+  }
+  componentDidMount() {
+    let el = document.querySelector('#' + this.props.title + this.props.lp_id);
+    this.svg = d3.select(el)
+      .append('svg');
+    let self = this;
+
+    let totalWidth = this.width + this.margin.left + this.margin.right;
+    let totalHeight = this.height + this.margin.top + this.margin.bottom;
+    this.svg = this.svg
+      .attr("viewBox", "-10 0 " + totalWidth + " " + totalHeight + "")
+      .attr("preserveAspectRatio", "xMidYMid meet")
+      .style("overflow","visible")
+      .append("g")
+      .attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
+
+    this.svg.append("g")
+        .attr("class", "y axis")
+        .call(this.yAxis);
+    this.svg.append("g")
+        .attr("class", "x axis")
+        .attr("transform", "translate(-1," + this.height + ")")
+        .call(this.xAxis)
+    this.drawBars(this.props);
+
+
+  }
+  componentWillReceiveProps(props) {
+    this.drawBars(props);
+  }
+  drawBars = (props) => {
+    let DATA = props.data.sort(function(a,b){
+      return (a.id > b.id) ? -1 : 1;
+    });
+    this.x.domain(DATA.map(function(d, i) { return d.id }));
+    let self = this;
+    let barGroup = this.svg.selectAll(".barGroup").data(DATA, function(d, i) { return d.id});;
+    let barGroupExit = barGroup.exit().remove();
+    let barGroupEnter =  barGroup.enter().append('g')
+      .attr('class', 'barGroup');
+    barGroupEnter
+      .append("rect")
+      .attr("class", "bar")
+      .attr("x", function(d) { return self.x(d.id); })
+      .attr("width", self.x.rangeBand())
+      .attr("y", function(d) { return self.y(d[props.valueName]); })
+      .attr("height", function(d) {return self.height - self.y(d[props.valueName]); });
+    barGroupEnter.append("g")
+      .attr("transform", function(d){
+        return "translate("+ (parseInt(self.x(d.id)) + (self.x.rangeBand() / 2) + 10) +"," + (parseInt(self.height) + 10) + ")"
+       })
+      .append('text')
+      .classed('metriclabel', true)
+      .style("text-anchor", "end")
+      .attr('transform', 'rotate(-75)')
+      .text(function(d) { return d.name;} )
+
+    let barGroupUpdate = barGroup.transition();
+    barGroupUpdate.select('rect')
+      .attr("class", "bar")
+      .attr("x", function(d) { return self.x(d.id) })
+      .attr("width", self.x.rangeBand())
+      .attr("y", function(d) { return self.y(d[props.valueName]); })
+      .attr("height", function(d) {return self.height - self.y(d[props.valueName]); });
+    barGroupUpdate
+      .select('g')
+      .attr("transform", function(d){
+        return "translate("+ (parseInt(self.x(d.id)) + (self.x.rangeBand() / 2) + 10) +"," + (parseInt(self.height) + 10) + ")"
+       })
+      .select('text')
+      .style("text-anchor", "end")
+      .attr('transform', 'rotate(-75)')
+      .text(function(d) { return d.name;} )
+  }
+  render() {
+    let html = <div></div>;
+    let self = this;
+    return <div className="metricBarGroup"><h3>{this.props.title}</h3><div id={this.props.title + this.props.lp_id}></div></div>;
+  }
+}
+MetricBarGroup.defaultProps = {
+  ticks: {
+    number: 2,
+    type: '%'
+  },
+  valueName: 'utilization',
+  title: '',
+  data: []
+}
+
+
+export default MetricBarGroup;
diff --git a/skyquake/framework/widgets/metric-bars/metricBarGroup.scss b/skyquake/framework/widgets/metric-bars/metricBarGroup.scss
new file mode 100644
index 0000000..6517612
--- /dev/null
+++ b/skyquake/framework/widgets/metric-bars/metricBarGroup.scss
@@ -0,0 +1,49 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+@import "../../../framework/style/_colors.scss";
+    .metricBarGroup {
+      display:flex;
+      flex-direction:column;
+      padding: 1rem;
+      justify-content: center;
+      h3 {
+        text-align:center;
+        font-weight:bold;
+      }
+      .bar {
+        fill: $brand-blue-light;
+        stroke: black;
+      }
+      .y.axis {
+        .domain {
+          transform:scaleX(0.25);
+        }
+      }
+      .x.axis {
+        .domain {
+          transform:scaleY(0.25);
+        }
+     }
+     .metriclabel, .y.axis .tick text{
+      font-size:3rem;
+     }
+     &>div {
+      flex: 1 1 %33;
+     }
+   }
diff --git a/skyquake/framework/widgets/mixins/ButtonEventListener.js b/skyquake/framework/widgets/mixins/ButtonEventListener.js
new file mode 100644
index 0000000..7a8e0cc
--- /dev/null
+++ b/skyquake/framework/widgets/mixins/ButtonEventListener.js
@@ -0,0 +1,207 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require('react');
+
+
+/**
+ * Event listener Mixins. A vast majority of components are going to expose these events to the user so we're making
+ * a central location to house all of them.
+ */
+module.exports = {
+  propTypes: {
+    onClick:       React.PropTypes.func,
+    onMouseUp:     React.PropTypes.func,
+    onMouseDown:   React.PropTypes.func,
+    onMouseOver:   React.PropTypes.func,
+    onMouseEnter:  React.PropTypes.func,
+    onMouseLeave:  React.PropTypes.func,
+    onMouseOut:    React.PropTypes.func,
+
+    onTouchCancel: React.PropTypes.func,
+    onTouchEnd:    React.PropTypes.func,
+    onTouchMove:   React.PropTypes.func,
+    onTouchStart:  React.PropTypes.func,
+
+    onKeyDown:     React.PropTypes.func,
+    onKeyPress:    React.PropTypes.func,
+    onKeyUp:       React.PropTypes.func,
+
+    onFocus:       React.PropTypes.func,
+    onBlur:        React.PropTypes.func
+  },
+
+  /**
+   * A vast majority of these functions just check to see if the event function is defined and then passes the function
+   * both the event and the local props.
+   * @param e
+   */
+  onClick: function(e) {
+    if (Boolean(this.props.onClick) && !this.state.isDisabled && !this.state.isReadOnly) {
+      //this.props.isActive = true;
+      this.props.onClick(e, this);
+    } else {
+      e.preventDefault();
+    }
+  },
+  onMouseUp: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isActive:false});
+      if (Boolean(this.props.onMouseUp)) {
+        this.props.onMouseUp(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+  onMouseDown: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isActive:true});
+      if (Boolean(this.props.onMouseDown)) {
+        this.props.onMouseDown(e, this);
+      }
+    }
+  },
+  onMouseOver: function(e) {
+    if (Boolean(this.props.onMouseOver) && !this.state.isDisabled && !this.state.isReadOnly) {
+      this.props.onMouseOver(e, this);
+    } else {
+      e.preventDefault();
+    }
+  },
+  onMouseEnter: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isHovered:true});
+      if (this.props.onMouseEnter) {
+        this.props.onMouseEnter(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+  onMouseLeave: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isHovered:false, isActive:false});
+      if (Boolean(this.props.onMouseLeave)) {
+        this.props.onMouseLeave(e, this);
+      }
+    }
+  },
+  onMouseOut: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      if (Boolean(this.props.onMouseOut)) {
+        this.props.onMouseOut(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+  onTouchCancel: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isActive: false});
+      if (Boolean(this.props.onTouchCancel)) {
+        this.props.onTouchCancel(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+  onTouchEnd: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isActive: false});
+      if (Boolean(this.props.onTouchEnd)) {
+        this.props.onTouchEnd(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+  onTouchMove: function(e) {
+    if (Boolean(this.props.onTouchMove) && !this.state.isDisabled && !this.state.isReadOnly) {
+      this.props.onTouchMove(e, this);
+    } else {
+      e.preventDefault();
+    }
+  },
+  onTouchStart: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isActive: true});
+      if (Boolean(this.props.onTouchStart)) {
+        this.props.onTouchStart(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+  onKeyDown: function(e) {
+    if (Boolean(this.props.onKeyDown) && !this.state.isDisabled && !this.state.isReadOnly) {
+      this.props.onKeyDown(e, this);
+    } else {
+      e.preventDefault();
+    }
+  },
+  onKeyPress: function(e) {
+    if (Boolean(this.props.onKeyPress) && !this.state.isDisabled && !this.state.isReadOnly) {
+      this.props.onKeyPress(e, this);
+    } else {
+      e.preventDefault();
+    }
+  },
+  onKeyUp: function(e) {
+    if (Boolean(this.props.onKeyUp) && !this.state.isDisabled && !this.state.isReadOnly) {
+      this.props.onKeyUp(e, this);
+    } else {
+      e.preventDefault();
+    }
+  },
+  onFocus: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isFocused: true});
+      if (Boolean(this.props.onFocus)) {
+        this.props.onFocus(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+  onBlur: function(e) {
+    if (!this.state.isDisabled && !this.state.isReadOnly) {
+      this.setState({isFocused: false});
+      if (Boolean(this.props.onBlur)) {
+        this.props.onBlur(e, this);
+      }
+    } else {
+      e.preventDefault();
+    }
+  },
+
+  /**
+   * Generic clone function that takes an object and returns an independent clone of it.
+   * Needed to give the user a clone of the props instead of the props themselves to prevent direct access to the props.
+   * @param obj
+   * @returns {*}
+   **/
+  clone: function(obj) {
+    if (null == obj || "object" != typeof obj) return obj;
+    var copy = obj.constructor();
+    for (var attr in obj) {
+      if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
+    }
+    return copy;
+  }
+};
diff --git a/skyquake/framework/widgets/multicomponent/multicomponent.js b/skyquake/framework/widgets/multicomponent/multicomponent.js
new file mode 100644
index 0000000..4c2f9a5
--- /dev/null
+++ b/skyquake/framework/widgets/multicomponent/multicomponent.js
@@ -0,0 +1,57 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require('react');
+var mixin = require('../mixins/ButtonEventListener.js')
+/**
+ *  Contains a set of components.  Takes a list of components and renders them in lists.
+ *  It's props values and a brief description below
+ *  component_list: Takes a list of React components.
+ */
+module.exports = React.createClass({
+  displayName: 'Multicomponent',
+  mixins:mixin.MIXINS,
+  propTypes: {
+    component_list:           React.PropTypes.array.isRequired
+  },
+
+  /**
+   * Defines default state.
+   *  component_list: Takes a list of React components.
+   */
+  getInitialState: function() {
+    return {
+      component_list: this.props.component_list
+
+    }
+  },
+
+
+  /**
+   * Renders the multicomponent Component
+   * Returns a list React components
+   * @returns {*}
+   */
+  render: function() {
+
+    var componentDOM = React.createElement("div", {className:this.props.className}, 
+      this.props.component_list
+    )
+    return componentDOM;
+  }
+});
\ No newline at end of file
diff --git a/skyquake/framework/widgets/nfvi-metric-bars/nfviMetricBars.jsx b/skyquake/framework/widgets/nfvi-metric-bars/nfviMetricBars.jsx
new file mode 100644
index 0000000..019c4d7
--- /dev/null
+++ b/skyquake/framework/widgets/nfvi-metric-bars/nfviMetricBars.jsx
@@ -0,0 +1,80 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import './nfviMetricBars.scss';
+class MetricChart extends React.Component {
+  constructor(props) {
+    super(props)
+  }
+  render() {
+    let html;
+    let byteSize = 1e+9;
+    let utilization = this.props.utilization;
+    // let utilization = (1 > this.props.utilization) ? Math.round(this.props.utilization * 100) : this.props.utilization;
+    let isVCPU = (this.props.label == "VCPU");
+    let label;
+    if (isVCPU) {
+      label = this.props.total;
+    } else {
+      var num = this.props.used / byteSize;
+      label = Math.round(num * 100) / 100 + ' / ' +  Math.round(this.props.total / byteSize) + 'GB';
+    }
+    html = (
+            <div className="nvfi-metric-container">
+              <div className="nfvi-metric-chart">
+                <div className="nfvi-metric-chart-value">{utilization}%</div>
+                <div className="nfvi-metric-chart-bar" style={{height: utilization + '%'}}></div>
+              </div>
+              <div className="nfvi-metric-value">{label}</div>
+              <div className="nfvi-metric-label">{this.props.label}</div>
+            </div>
+            );
+    return html;
+  }
+}
+
+class nfviMetrics extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+  render() {
+    let html;
+    let nfviMetrics = this.props.metrics.map(function(metric, k) {
+      //Do not display network metrics
+      if("outgoing" in metric || "incoming" in metric){
+        return
+      } else {
+        return (<MetricChart key={k} utilization={metric.utilization} label={metric.label} total={metric.total} used={metric.used}/>)
+      }
+    });
+    html = (
+      <div className="nfvi-metrics-tray">
+        {nfviMetrics}
+      </div>
+    )
+    return html;
+  }
+}
+nfviMetrics.defaultProps = {
+  metrics: []
+}
+
+
+
+export default nfviMetrics;
diff --git a/skyquake/framework/widgets/nfvi-metric-bars/nfviMetricBars.scss b/skyquake/framework/widgets/nfvi-metric-bars/nfviMetricBars.scss
new file mode 100644
index 0000000..fcd77bb
--- /dev/null
+++ b/skyquake/framework/widgets/nfvi-metric-bars/nfviMetricBars.scss
@@ -0,0 +1,102 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+@import "../../../framework/style/_colors.scss";
+.nfvi-metrics-tray {
+      flex: 1 0;
+      display: flex;
+      flex-direction: row;
+      justify-content: space-around;
+      margin: 10px 0;
+      padding: 0 15px 0 10px;
+      height: 165px;
+     .nvfi-metric-container {
+        flex: 1 0;
+        display: flex;
+        flex-direction: column;
+        align-items: stretch;
+        margin: 0 10px 0 0;
+      }
+
+        .nfvi-metric-chart {
+          position: relative;
+          flex: 1 0;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          background: #fff;
+        }
+
+          .nfvi-metric-chart-value {
+            font-size: 2rem;
+            font-weight: 700;
+            z-index: 2;
+          }
+
+          .nfvi-metric-chart-bar {
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            right: 0;
+            height: 0%;
+            background: #00ACEE;
+            transition: all 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000);
+          }
+
+        .nfvi-metric-value {
+          padding: 5px 10px;
+          text-align: center;
+          font-size: 16px;
+        }
+
+        .nfvi-metric-label {
+          text-align: center;
+          font-size: 12px;
+          text-transform: uppercase;
+        }
+
+ }
+    .metricBars {
+      display:flex;
+      flex-direction:row;
+      flex-wrap:wrap;
+      padding: 1rem;
+      justify-content: space-around;
+      h3 {
+        text-align:center;
+        font-weight:bold;
+      }
+      .bar {
+        fill: $brand-blue-light;
+        stroke: black;
+      }
+      .y.axis {
+        .domain {
+          transform:scaleX(0.25);
+        }
+      }
+      .x.axis {
+        .domain {
+          transform:scaleY(0.25);
+        }
+     }
+     &>div {
+      flex: 1 1;
+      max-width:400px;
+     }
+   }
diff --git a/skyquake/framework/widgets/operational-status/launchpadOperationalStatus.jsx b/skyquake/framework/widgets/operational-status/launchpadOperationalStatus.jsx
new file mode 100644
index 0000000..ba77a79
--- /dev/null
+++ b/skyquake/framework/widgets/operational-status/launchpadOperationalStatus.jsx
@@ -0,0 +1,201 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import Loader from 'widgets/loading-indicator/loadingIndicator.jsx';
+
+//Currently displays all buffer state messages. Should consider showing only the n most recent.
+//TODO remove loader when current state is running
+//TODO need to look at refactoring this
+class ThrottledMessage extends React.Component {
+
+  constructor(props) {
+    super(props);
+    let self = this;
+    this.displayBuffer = [];
+    this.bufferInterval;
+    this.last_id = null;
+    this.state = {};
+    this.state.loading = props.loading;
+    this.state.buffer = {};
+    this.state.displayMessage = 'Loading...'
+  }
+
+  componentWillReceiveProps(nextProps) {
+    let buffer = nextProps.buffer;
+    if(buffer.length) {
+      this.buildBufferObject(nextProps);
+      this.bufferIt(this.props);
+    }
+  }
+
+  componentDidMount() {
+    if(this.props.buffer.length) {
+      let buffer = this.props.buffer;
+      this.buildBufferObject(this.props);
+      this.bufferIt(this.props);
+    }
+  }
+  componentWillUnmount() {
+    clearInterval(this.bufferInterval);
+  }
+  buildBufferObject(props) {
+    let self = this;
+    let buffer = self.state.buffer;
+    this.last_id = props.buffer[props.buffer.length -1].id;
+    props.buffer.map(function(item) {
+      if(!buffer[item.id]) {
+        buffer[item.id] = {
+          displayed: false,
+          data: item
+        }
+        self.displayBuffer.push(buffer[item.id]);
+      }
+    });
+  }
+
+  bufferIt(props) {
+    let self = this
+    clearInterval(self.bufferInterval);
+    self.bufferInterval = setInterval(function() {
+      let currentStatus = self.props.currentStatus;
+      let failed = currentStatus == 'failed';
+      for (let i = 0; i < self.displayBuffer.length; i++) {
+        if(!self.displayBuffer[i].displayed) {
+          self.displayBuffer[i].displayed = true;
+          let displaymsg;
+          if(failed) {
+            displaymsg = self.displayBuffer[self.displayBuffer.length-1].data.description;
+            clearInterval(self.bufferInterval);
+                self.props.onEnd(failed);
+          } else {
+            displaymsg = self.displayBuffer[i].data.description;
+          }
+          self.setState({
+              displayMessage: displaymsg
+          });
+          break;
+        }
+      }
+
+      if((currentStatus == 'running' || currentStatus == 'started' || currentStatus == 'stopped' || failed) && self.displayBuffer[self.displayBuffer.length - 1].displayed ) {
+                clearInterval(self.bufferInterval);
+                self.props.onEnd(failed);
+        }
+    }, 600)
+  }
+  render() {
+    if(!this.props.hasFailed) {
+      return (<span className='throttledMessageText'>{this.state.displayMessage}</span>)
+    } else {
+      return (<span> </span>)
+    }
+  }
+}
+
+ThrottledMessage.defaultProps = {
+  buffer: []
+}
+
+export default class operationalStatus extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {};
+    this.state.message = 'Loading...';
+    this.state.messageHistory = {};
+  }
+  componentWillReceiveProps(nextProps) {
+
+  }
+  finishedMessages(){
+    this.setState({
+      loading: false
+    })
+  }
+  statusMessage(currentStatus, currentStatusDetails) {
+    var message = currentStatus;
+    if (currentStatusDetails) {
+       message += ' - ' + currentStatusDetails;
+    }
+    return message;
+  }
+  render() {
+    let html;
+    let isDisplayed = this.props.display;
+    let isFailed = (this.props.currentStatus == 'failed') || false;
+    let title = (!this.props.loading || isFailed) ? <h2>History</h2> : '';
+    let status = this.props.status.map(function(status, index) {
+      return (
+        <li key={index}>
+          {status.description}
+          {status.details ? (
+            <ul>
+              <li>{status.details}
+              </li>
+            </ul>) : null}
+        </li>
+      )
+    }).reverse();
+    if(this.props.loading) {
+      if (!isFailed) {
+        isDisplayed = true;
+        //If there is no collection of status event message, just display currentStatus
+        if(status.length) {
+              html = (
+                      <div className={this.props.className + '_loading'}>
+                        <Loader show={!isFailed}/>
+                        <ThrottledMessage currentStatus={this.props.currentStatus} buffer={this.props.status} onEnd={this.props.doneLoading}/>
+                      </div>
+              )
+        } else {
+          html = (
+                      <div className={this.props.className + '_loading'}>
+                        <Loader show={!isFailed}/>
+                        {this.statusMessage(this.props.currentStatus,this.props.currentStatusDetails)}
+                      </div>
+              )
+        }
+      } else {
+          isDisplayed = true;
+              html = (
+
+                        <ul>
+                        <ThrottledMessage currentStatus={this.props.currentStatus} buffer={this.props.status} onEnd={this.props.doneLoading} hasFailed={isFailed}/>
+                          {status}
+                        </ul>
+              )
+      }
+    } else {
+      html = (
+          <ul>
+            {status}
+          </ul>
+      )
+    }
+    return (<div className={this.props.className + (isDisplayed ? '_open':'_close')}>{title} {html}</div>);
+  }
+}
+
+operationalStatus.defaultProps = {
+  status: [],
+  loading: true,
+  display: false
+}
+
+
+
diff --git a/skyquake/framework/widgets/panel/panel.jsx b/skyquake/framework/widgets/panel/panel.jsx
new file mode 100644
index 0000000..e8a3118
--- /dev/null
+++ b/skyquake/framework/widgets/panel/panel.jsx
@@ -0,0 +1,66 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React, {Component} from 'react';
+import 'style/core.css';
+import './panel.scss';
+export class Panel extends Component {
+    constructor(props) {
+        super(props)
+    }
+    render() {
+        let self = this;
+        let {children, className, title, ...props} = self.props;
+        let classRoot = className ? ' ' + className : ' '
+        let titleTag = title ? <header className="skyquakePanel-title">{title}</header> : '';
+        return (
+            <section className={'skyquakePanel' + classRoot} style={props.style}>
+                <i className="corner-accent top left"></i>
+                <i className="corner-accent top right"></i>
+                {titleTag}
+                <div className="skyquakePanel-wrapper">
+                    <div className={(classRoot ? 'skyquakePanel-body ' + decorateClassNames(classRoot, '-body') : 'skyquakePanel-body')}>
+                            {children}
+                    </div>
+                </div>
+                <i className="corner-accent bottom left"></i>
+                <i className="corner-accent bottom right"></i>
+            </section>
+        )
+    }
+}
+
+Panel.defaultProps = {
+
+}
+
+export class PanelWrapper extends Component {
+    render() {
+        return (<div className={'skyquakePanelWrapper'}>
+            {this.props.children}
+        </div>)
+    }
+}
+
+export default Panel;
+
+
+function decorateClassNames(className, addendum) {
+    return className.split(' ').map(function(c) {
+        return c + addendum
+    }).join(' ');
+}
diff --git a/skyquake/framework/widgets/panel/panel.scss b/skyquake/framework/widgets/panel/panel.scss
new file mode 100644
index 0000000..ff36bbf
--- /dev/null
+++ b/skyquake/framework/widgets/panel/panel.scss
@@ -0,0 +1,67 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+@import '../../style/_colors.scss';
+.skyquakePanel {
+    position:relative;
+    display:-ms-flexbox;
+    display:flex;
+    -ms-flex-direction:column;
+    flex-direction:column;
+    -ms-flex:1 1;
+    flex:1 1;
+    background-color: $body-color;
+    margin:0.5rem;
+        &-title{
+            display: -ms-flexbox;
+            display: flex;
+            -ms-flex-align: center;
+            -ms-grid-row-align: center;
+            align-items: center;
+            padding: 1rem;
+            background-color: white;
+            text-transform: uppercase;
+        }
+        &-wrapper {
+            overflow:auto;
+            height:100vh;
+        }
+        &-body {
+            display:-ms-flexbox;
+            display:flex;
+            -ms-flex-direction:column;
+            flex-direction:column;
+            padding:1rem;
+        }
+        &Wrapper {
+            display:-ms-flexbox;
+            display:flex;
+            width:100%;
+            height:100%;
+        }
+}
+
+/* Style for storybook */
+body{
+    height:100%;
+>#root{
+    height:100vh;
+    width:100vw;
+    display:-ms-flexbox;
+    display:flex;
+}
+}
diff --git a/skyquake/framework/widgets/radio-button/rw.radio-button.js b/skyquake/framework/widgets/radio-button/rw.radio-button.js
new file mode 100644
index 0000000..706d39e
--- /dev/null
+++ b/skyquake/framework/widgets/radio-button/rw.radio-button.js
@@ -0,0 +1,267 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+var React = require('react');
+var ButtonEventListenerMixin = require('../mixins/ButtonEventListener.js');
+
+
+/**
+ *  A radio-button component.
+ *  It's props values and a brief description below
+ *
+ *  label:        The label for the radio button group.
+ *  radiobuttons: The object that creates each radio button.  Each object has a property "label" and "checked".
+ *    label:        The label for the individual radio button.
+ *    checked:      If set to true, the individual radio button is initialized with a check.
+ *  requiredText: The text content of the "if required" message.
+ *  errorText:    The text content of the error message.
+ *  ClassName:    Css Classes applied to the element.
+ *  size:         The size of the element.
+ *  isRequired:   A boolean indicating whether or not the input is required.
+ *  isDisabled:   A boolean indicating the disabled state of the element.
+ *  isReadOnly:   A boolean indicating whether or not the input is read only.
+ *  instructions: The text content of the instructions
+ **/
+module.exports = React.createClass({
+  displayName: "RadioButton",
+  mixins:[ButtonEventListenerMixin],
+  propTypes: {
+    name:            React.PropTypes.string,
+    label:           React.PropTypes.string,
+    radiobuttons:    React.PropTypes.arrayOf(
+      React.PropTypes.shape(
+        {
+          label:   React.PropTypes.string,
+          checked: React.PropTypes.bool
+        }
+      )),
+    requiredText:    React.PropTypes.string,
+    errorText:       React.PropTypes.string,
+    placeholder:     React.PropTypes.string,
+    className:       React.PropTypes.string,
+    size:            React.PropTypes.string,
+    isRequired:      React.PropTypes.bool,
+    isDisabled:      React.PropTypes.bool,
+    isReadOnly:      React.PropTypes.bool,
+    instructions:    React.PropTypes.string
+  },
+
+
+  /**
+   * Sets the default input state.
+   * If there is no description for the variable, assume it's the same as it's props counterpart.
+   *
+   * isActive: Boolean to indicate if input is active.
+   * isHovered: Boolean to indicate if the input is being hovered over.
+   * isFocused: Boolean to indicate if the input has been focused.
+   * isDisabled: Boolean to indicate if input has been disabled.
+   *
+   * @returns {{sizeOfButton: (*|string), isActive: boolean, isHovered: boolean, isFocused: boolean, isDisabled: (*|boolean),
+   * isReadOnly: (*|.textInput.isReadOnly|.exports.propTypes.isReadOnly|.exports.getInitialState.isReadOnly|boolean),
+   * isRequired: (*|.textInput.isRequired|.exports.propTypes.isRequired|.exports.getInitialState.isRequired|isRequired|null),
+   * isValid: null, isSuccess: null}}
+   */
+  getInitialState: function() {
+    return {
+      label:            this.props.label || "",
+      requiredText:     this.props.requiredText || "Required",
+      instructionsText: this.props.instructions || "",
+      errorText:        this.props.errorText || "",
+      size:             this.props.size || '',
+      isActive:         false,
+      isHovered:        false,
+      isFocused:        false,
+      isDisabled:       this.props.disabled || false,
+      isReadOnly:       this.props.isReadOnly || false,
+      isRequired:       this.props.isRequired || null,
+      isValid:          null,      // Three way bool. Valid: true.   Invalid: false. Not acted on: null.
+      isSuccess:        null       // Three way bool. Success: true. Error: false.   Not acted on: null.
+    }
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * "nextProps" and "nextState" hold the state and property variables as they will be after the update.
+   * "this.props" and "this.state" hold the state and property variables as they were before the update.
+   * returns true if the state have changed. Otherwise returns false.
+   * @param nextProps
+   * @param nextState
+   * @returns {boolean}
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = this.state.isReadOnly + this.state.isDisabled + this.state.isActive + this.state.isFocused +
+      this.state.isHovered + this.state.isValid + this.state.isSuccess;
+    var nextStateString = nextState.isReadOnly + nextState.isDisabled + nextState.isActive + nextState.isFocused +
+      nextState.isHovered + nextState.isValid + nextState.isSuccess;
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    return true;
+  },
+
+
+
+  /**
+   * Returns a string reflecting the current state of the input.
+   * If the component state "isDisabled" is true, returns a string "disabled".
+   * If the component state "isReadOnly" is true, returns a string "readonly".
+   * Otherwise returns a string containing a word for each state that has been set to true.
+   * (ie "active focused" if the states active and focused are true, but hovered is false).
+   * @returns {string}
+   */
+  setComponentState: function() {
+    var ret = "";
+    if (this.state.isDisabled) {
+      return "disabled";
+    }
+    if (this.state.isReadOnly) {
+      return "readonly";
+    }
+    if (this.state.isActive) {
+      ret += "active ";
+    }
+    if (this.state.isHovered) {
+      ret += "hovered ";
+    }
+    if (this.state.isFocused) {
+      ret += "focused ";
+    }
+    return ret;
+  },
+
+  /**
+   * Renders the Text Input component.
+   *
+   **/
+  render: function() {
+    var input = [];
+    var label = [];
+    var input_style = {};
+    var input_state = this.setComponentState();
+
+    // The following if statements translates the min/max width from a number into a string.
+    if (this.props.minWidth) {
+      input_style['min-width'] = String(this.props.minWidth) + 'px';
+    }
+    if (this.props.maxWidth) {
+      input_style['max-width'] = String(this.props.maxWidth) + 'px';
+    }
+
+    //// The input element.
+    //input = React.createElement("input", {
+    //    ref:               "inputRef",
+    //    "data-state":      input_state,
+    //    "type":            "checkbox",
+    //    placeholder:       this.props.placeholder,
+    //    pattern:           this.props.pattern,
+    //    maxLength:         this.props.maxLength,
+    //    required:          this.state.isRequired,
+    //    disabled:          this.state.isDisabled,
+    //    readOnly:          this.state.isReadOnly,
+    //    onChange:          this.handleChange,
+    //    onClick:           this.onClick,
+    //    onMouseUp:         this.onMouseUp,
+    //    onMouseDown:       this.onMouseDown,
+    //    onMouseOver:       this.onMouseOver,
+    //    onMouseEnter:      this.onMouseEnter,
+    //    onMouseLeave:      this.onMouseLeave,
+    //    onMouseOut:        this.onMouseOut,
+    //    onTouchCancel:     this.onTouchCancel,
+    //    onTouchEnd:        this.onTouchEnd,
+    //    onTouchMove:       this.onTouchMove,
+    //    onTouchStart:      this.onTouchStart,
+    //    onKeyDown:         this.onKeyDown,
+    //    onKeyPress:        this.onKeyPress,
+    //    onKeyUp:           this.onKeyUp,
+    //    onFocus:           this.onFocus,
+    //    onBlur:            this.onBlur,
+    //    className:         (this.props.className || "rw-textinput"),
+    //    tabIndex:          0
+    //  },
+    //  null
+    //);
+
+    label = React.createElement("label", null, this.props.label);
+
+    for (var i = 0; i < this.props.radiobuttons.length; i++) {
+
+      // Label for the whole radio button group
+      input[i] = React.createElement("label",
+        {
+          key:i*2 + 1,
+          readOnly:this.props.readonly,
+          disabled:this.props.disabled
+        },
+
+        // Creates each radio button input element.
+        React.createElement("input", {
+            key: i*2,
+            defaultChecked: this.props.radiobuttons[i].checked,
+            type: "radio",
+            name: this.props.name,
+            readOnly: this.props.readonly,
+            disabled: this.props.disabled,
+            onClick:           this.onClick,
+            onMouseUp:         this.onMouseUp,
+            onMouseDown:       this.onMouseDown,
+            onMouseOver:       this.onMouseOver,
+            onMouseEnter:      this.onMouseEnter,
+            onMouseLeave:      this.onMouseLeave,
+            onMouseOut:        this.onMouseOut,
+            onTouchCancel:     this.onTouchCancel,
+            onTouchEnd:        this.onTouchEnd,
+            onTouchMove:       this.onTouchMove,
+            onTouchStart:      this.onTouchStart,
+            onKeyDown:         this.onKeyDown,
+            onKeyPress:        this.onKeyPress,
+            onKeyUp:           this.onKeyUp,
+            onFocus:           this.onFocus,
+            onBlur:            this.onBlur
+          },
+          null
+        ),
+        this.props.radiobuttons[i].label);
+    }
+
+    // The "if required" element. It displays a label if the element is required.
+    if(this.props.isRequired == true){
+      var requiredEle = React.createElement("small", {className: "rw-form-requiredLabel"}, this.state.requiredText);
+    }
+
+    // The "error" element.  It pops up as a message if there is an error with the input.
+    if(this.state.errorText != "") {
+      var error = React.createElement("p", {className: "rw-form-errorMsg"}, this.state.errorText);
+    }
+
+    // The "instruction" element.
+    if(this.state.instructionsText != ""){
+      var instructions = React.createElement("p", {className: "rw-form-instructions"}, this.state.instructionsText)
+    }
+
+    // The parent element for all.
+    var ret = React.createElement("div", {
+      "data-state":      input_state,
+      required:          this.state.isRequired,
+      disabled:          this.state.isDisabled,
+      readOnly:          this.state.isReadOnly
+    }, requiredEle, label, input, error, instructions);
+
+    return ret;
+  }
+});
diff --git a/skyquake/framework/widgets/screen-loader/screenLoader.jsx b/skyquake/framework/widgets/screen-loader/screenLoader.jsx
new file mode 100644
index 0000000..eac33a9
--- /dev/null
+++ b/skyquake/framework/widgets/screen-loader/screenLoader.jsx
@@ -0,0 +1,46 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import Loader from '../loading-indicator/loadingIndicator.jsx';
+import React from 'react';
+
+export default class ScreenLoader extends React.Component {
+  constructor(props) {
+    super(props)
+  }
+  render() {
+    let overlayStyle = {
+      position: 'fixed',
+      zIndex: 999,
+      width: '100%',
+      height: '100%',
+      top: 0,
+      // right: 0,
+      // bottom: 0,
+      left: 0,
+      display: this.props.show ? 'flex' : 'none',
+      justifyContent: 'center',
+      alignItems: 'center',
+      scroll: 'none',
+      backgroundColor: 'rgba(0,0,0,0.5)'
+    };
+    return (
+      <div style={overlayStyle}><Loader size="10"/></div>
+    );
+  }
+}
diff --git a/skyquake/framework/widgets/skyquake_container/eventCenter.jsx b/skyquake/framework/widgets/skyquake_container/eventCenter.jsx
new file mode 100644
index 0000000..75d2d52
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/eventCenter.jsx
@@ -0,0 +1,198 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+ /**
+  * EventCenter module to display a list of events from the system
+  * @module framework/widgets/skyquake_container/EventCenter
+  * @author Kiran Kashalkar <kiran.kashalkar@riftio.com>
+  *
+  */
+
+import React from 'react';
+import { Link } from 'react-router';
+import Utils from 'utils/utils.js';
+import Crouton from 'react-crouton';
+import TreeView from 'react-treeview';
+import _ from 'lodash';
+import '../../../node_modules/react-treeview/react-treeview.css';
+import './eventCenter.scss';
+
+export default class EventCenter extends React.Component {
+	constructor(props) {
+		super(props);
+		this.state = {};
+		this.state.isOpen = false;
+	}
+
+	componentWillReceiveProps(props) {
+		let stateObject = {};
+
+		let notificationList = sessionStorage.getItem('notificationList');
+		let latestNotification = sessionStorage.getItem('latestNotification');
+
+		if (props.newNotificationEvent && props.newNotificationMsg) {
+			if (latestNotification) {
+				latestNotification = JSON.parse(latestNotification);
+				if (!_.isEqual(props.newNotificationMsg, latestNotification)) {
+					stateObject.newNotificationEvent = props.newNotificationEvent;
+					stateObject.newNotificationMsg = props.newNotificationMsg;
+					sessionStorage.setItem('latestNotification', JSON.stringify(stateObject.newNotificationMsg));
+				} else {
+					stateObject.newNotificationEvent = false;
+					stateObject.newNotificationMsg = null;
+				}
+			} else {
+				stateObject.newNotificationEvent = true;
+				stateObject.newNotificationMsg = props.newNotificationMsg;
+				sessionStorage.setItem('latestNotification', JSON.stringify(stateObject.newNotificationMsg));
+			}
+		} else {
+			stateObject.newNotificationEvent = false;
+			stateObject.newNotificationMsg = null;
+		}
+
+		if (notificationList) {
+			stateObject.notifications = _.merge(notificationList, props.notifications);
+		} else {
+			stateObject.notifications = props.notifications;
+		}
+		sessionStorage.setItem('notifications', JSON.stringify(stateObject.notifications));
+		this.setState(stateObject);
+	}
+
+	newNotificationReset = () => {
+        this.setState({
+            newNotificationEvent: false
+        });
+    }
+
+	onClickToggleOpenClose(event) {
+		this.props.onToggle();
+		this.setState({
+			isOpen: !this.state.isOpen
+		});
+	}
+	constructTree(details) {
+		let markup = [];
+		for (let key in details) {
+			if (typeof(details[key]) == "object") {
+			    let html = (
+			      	<TreeView key={key} nodeLabel={key}>
+			          {this.constructTree(details[key])}
+			      	</TreeView>
+			    );
+			    markup.push(html);
+			} else {
+				markup.push((<div key={key} className="info">{key} = {details[key].toString()}</div>));
+			}
+		}
+		return markup;
+	}
+
+	getNotificationFields(notification) {
+		let notificationFields = {};
+		if (notification) {
+
+			notificationFields.source = notification.source;
+			notificationFields.eventTime = notification.eventTime;
+
+			Object.keys(notification).map((notificationKey) => {
+				if (_.indexOf(['source', 'eventTime'], notificationKey) == -1) {
+					notificationFields.eventKey = notificationKey;
+					notificationFields.details = notification[notificationFields.eventKey];
+				}
+			});
+
+		}
+		return notificationFields;
+	}
+
+	render() {
+		let html;
+		let displayNotifications = [];
+		let displayNotificationsTableHead = null;
+
+		if (this.state.notifications && this.state.notifications.length > 0) {
+			displayNotificationsTableHead = (
+				<thead>
+					<tr key='header' className='header'>
+						<th className='source column'> Source Event Stream </th>
+						<th className='timestamp column'> Timestamp </th>
+						<th className='event column'> Event </th>
+						<th className='details column'> Details </th>
+					</tr>
+				</thead>
+			);
+		}
+
+		this.state.notifications && this.state.notifications.map((notification, notifIndex) => {
+			let notificationFields = {};
+
+			notificationFields = this.getNotificationFields(notification);
+
+			displayNotifications.push(
+				<tr key={notifIndex} className='notificationItem'>
+					<td className='source column'> {notificationFields.source} </td>
+					<td className='timestamp column'> {notificationFields.eventTime} </td>
+					<td className='event column'> {notificationFields.eventKey} </td>
+					<td className='details column'>
+						<TreeView key={notifIndex + '-detail'} nodeLabel='Event Details'>
+							{this.constructTree(notificationFields.details)}
+						</TreeView>
+					</td>
+				</tr>
+			);
+		});
+
+		let openedClassName = this.state.isOpen ? 'open' : 'closed';
+		html = (
+			<div className={'eventCenter ' + openedClassName}>
+
+                        <div className='notification'>
+                            <Crouton
+                                id={Date.now()}
+                                message={this.getNotificationFields(this.state.newNotificationMsg).eventKey +
+                                	' notification received. Check Event Center for more details.'}
+                                type={'info'}
+                                hidden={!(this.state.newNotificationEvent && this.state.newNotificationMsg)}
+                                onDismiss={this.newNotificationReset}
+                                timeout={this.props.dismissTimeout}
+                            />
+                        </div>
+				<h1 className='title' data-open-close-icon={this.state.isOpen ? 'open' : 'closed'}
+					onClick={this.onClickToggleOpenClose.bind(this)}>
+					EVENT CENTER
+				</h1>
+				<div className='notificationListBody'>
+					<table className='notificationList'>
+						{displayNotificationsTableHead}
+						<tbody>
+							{displayNotifications}
+						</tbody>
+					</table>
+				</div>
+			</div>
+		);
+
+		return html;
+	}
+}
+
+EventCenter.defaultProps = {
+	dismissTimeout: 3000
+}
diff --git a/skyquake/framework/widgets/skyquake_container/eventCenter.scss b/skyquake/framework/widgets/skyquake_container/eventCenter.scss
new file mode 100644
index 0000000..103d416
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/eventCenter.scss
@@ -0,0 +1,152 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+@import '../../style/colors.scss';
+
+.eventCenter {
+	background-color: $white;
+	z-index: 9999;
+	position: relative;
+
+	[data-open-close-icon] {
+	    &:after {
+	        content: " ";
+	        display: block;
+	        float: right;
+	        margin-right: 8px;
+	        height: 51px;
+	        width: 51px;
+	        opacity: 0.25;
+	        background-size: 20px 20px;
+	        background-repeat: no-repeat;
+	        background-position: center center;
+	        /* background-image required in order for background-size to work*/
+	        background-image: url(../../../node_modules/open-iconic/svg/caret-bottom.svg);
+	        vertical-align: middle;
+	    }
+	    &:hover:after {
+	        opacity: 1;
+	    }
+	}
+	[data-open-close-icon="closed"]:after {
+	    transform: rotate(180deg);
+	    transition: all 300ms cubic-bezier(0.77, 0, 0.175, 1);
+	}
+	[data-open-close-icon="open"]:after {
+	    transform: rotate(0deg);
+	    transition: all 300ms cubic-bezier(0.77, 0, 0.175, 1);
+	}
+
+
+	.notification {
+        .crouton {
+            right: 4px;
+            top: 34px;
+            width: 30%;
+            left: auto;
+            .info {
+                border-radius: 25px;
+            }
+        }
+    }
+	&.-with-transitions {
+		transition: height 300ms cubic-bezier(0.230, 1.000, 0.320, 1.000);
+	}
+	&.open {
+		.notificationListBody {
+			height: 300px;
+		}
+	}
+	&.closed {
+		.notificationListBody {
+			height: 0px;
+		}
+	}
+	.title {
+		border-top: 1px solid $gray-dark;
+		border-bottom: 1px solid $gray-dark;
+		cursor: pointer;
+		color: $gray-darker;
+		font-style: normal;
+		font-size: 1.625rem;
+    	font-weight: 400;
+		height: 51px;
+  		line-height: 51px;
+  		margin: 0;
+		padding-left: 0;
+		padding-right: 0;
+		text-align: center;
+		text-transform: uppercase;
+		background-color: $gray-lighter;
+	}
+	.notificationListBody {
+		overflow: auto;
+		.notificationList {
+			width: 100%;
+			&:hover {
+				overflow: auto;
+			}
+			
+			.notificationItem {
+
+			}
+
+			th {
+				text-align: left;
+                padding: 0.5rem;
+                background: $gray-darker;
+                border: 1px solid black;
+				color: $white;
+				&.source {
+					width: 20%;
+				}
+				&.timestamp {
+					width: 20%;
+				}
+				&.event {
+					width: 20%;
+				}
+				&.details {
+					width: 40%;
+				}
+			}
+
+			tr {
+				padding: 0.5rem;
+				border: 1px solid black;
+
+				&:nth-child(odd) {
+					background: $white;
+					td {
+	                    border: 1px solid black;
+	                    padding: 0.5rem;
+	                }
+				}
+
+				&:nth-child(even) {
+					background: $gray-lighter;
+					td {
+	                    border: 1px solid black;
+	                    padding: 0.5rem;
+	                }
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeAltInstance.js b/skyquake/framework/widgets/skyquake_container/skyquakeAltInstance.js
new file mode 100644
index 0000000..0b7876e
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeAltInstance.js
@@ -0,0 +1,27 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+//Alt Instance.
+import Alt from 'alt';
+var path = require('path');
+if (!window.A) {
+    window.A = new Alt();
+    Alt.debug('alt', window.A);
+    console.log('new alt', path.resolve(__dirname));
+}
+
+module.exports = window.A;
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeApp.scss b/skyquake/framework/widgets/skyquake_container/skyquakeApp.scss
new file mode 100644
index 0000000..4f77d71
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeApp.scss
@@ -0,0 +1,143 @@
+//import a reset
+@import '../../style/_colors.scss';
+html, body {
+    height:100%;
+    width:100%;
+}
+
+
+.skyquakeApp {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    background: $gray-lightest;
+    h1 {
+        text-transform: uppercase;
+    }
+    .active {
+        background-color: $brand-blue!important;
+        border-color: $brand-blue!important;
+        color: #fff!important
+    }
+    .skyquakeNav {
+        display: flex;
+        color:white;
+        background:black;
+        position:relative;
+        z-index: 10;
+        font-size:0.75rem;
+        .secondaryNav {
+            flex: 1 1 auto;
+            display: flex;
+            justify-content: flex-end;
+        }
+        .app {
+            position:relative;
+            h2 {
+                font-size:0.75rem;
+                border-right: 1px solid black;
+                display: flex;
+                align-items: center;
+                .oi {
+                    padding-right: 0.5rem;
+                }
+            }
+            .menu {
+                position:absolute;
+                display:none;
+                z-index:2;
+                width: 100%;
+            }
+            &:first-child{
+                h2 {
+                    border-left: 1px solid black;
+                }
+            }
+            &:hover {
+                a {
+                    color:$brand-blue-light;
+                    cursor:pointer;
+                }
+                .menu {
+                    display:block;
+                    background:black;
+                    a {
+                        color:white;
+                    }
+                    li:hover {
+                        a {
+                            color:$brand-blue-light;
+                        }
+                    }
+                }
+            }
+            &.active {
+                color:white;
+                background:black;
+                a {
+                    color:white;
+                }
+            }
+        }
+        a{
+            display:block;
+            padding:0.5rem 1rem;
+            text-decoration:none;
+            text-transform:uppercase;
+            color:white;
+        }
+        &:before {
+            content: '';
+            height:1.85rem;
+            width:2.5rem;
+            margin:0 1rem;
+            padding:0 0.125rem;
+            // background: url('../../style/img/header-logo.png') no-repeat center center white;
+            background: url('../../style/img/svg/riftio_logo_white.svg') no-repeat center center;
+            background-size:65%;
+        }
+    }
+    .skyquakeContainerWrapper {
+    }
+    .titleBar {
+        padding: 1rem 0 0;
+        h1 {
+        // background: url('../../style/img/header-logo.png') no-repeat;
+        background-size:contain;
+        height: 51px;
+        line-height: 51px;
+        margin-left: 20px;
+        // padding-left: 100px;
+        left: 0;
+        font-size: 1.625rem;
+        font-weight: 400;
+        text-align:left;
+        position:relative;
+        flex: 1 0 auto;
+        }
+    }
+    .corner-accent {
+        border: 1px solid #000;
+        display: block;
+        height: 4px;
+        position: absolute;
+        width: 4px;
+
+        &.top {
+            border-bottom: 0;
+            top: -1px;
+        }
+        &.right {
+            border-left: 0;
+            right: -1px;
+        }
+        &.bottom {
+            border-top: 0;
+            bottom: -1px;
+        }
+        &.left {
+            border-right: 0;
+            left: -1px;
+        }
+    }
+}
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeComponent.jsx b/skyquake/framework/widgets/skyquake_container/skyquakeComponent.jsx
new file mode 100644
index 0000000..893a4c1
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeComponent.jsx
@@ -0,0 +1,18 @@
+import React from 'react';
+export default function(Component) {
+  class SkyquakeComponent extends React.Component {
+    constructor(props, context) {
+        super(props, context);
+        this.router = context.router;
+        this.actions = context.flux.actions.global;
+    }
+    render(props) {
+        return <Component {...this.props} router={this.router} actions={this.actions} flux={this.context.flux} />
+    }
+  }
+  SkyquakeComponent.contextTypes = {
+    router: React.PropTypes.object,
+    flux: React.PropTypes.object
+  };
+  return SkyquakeComponent;
+}
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx b/skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
new file mode 100644
index 0000000..eca9413
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
@@ -0,0 +1,143 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import AltContainer from 'alt-container';
+import Alt from './skyquakeAltInstance.js';
+import SkyquakeNav from './skyquakeNav.jsx';
+import EventCenter from './eventCenter.jsx';
+import SkyquakeContainerActions from './skyquakeContainerActions.js'
+import SkyquakeContainerStore from './skyquakeContainerStore.js';
+import Breadcrumbs from 'react-breadcrumbs';
+import Utils from 'utils/utils.js';
+import _ from 'lodash';
+import Crouton from 'react-crouton';
+import ScreenLoader from 'widgets/screen-loader/screenLoader.jsx';
+import './skyquakeApp.scss';
+// import 'style/reset.css';
+import 'style/core.css';
+export default class skyquakeContainer extends React.Component {
+    constructor(props) {
+        super(props);
+        let self = this;
+        this.state = SkyquakeContainerStore.getState();
+        //This will be populated via a store/source
+        this.state.nav = this.props.nav || [];
+        this.state.eventCenterIsOpen = false;
+        this.state.currentPlugin = SkyquakeContainerStore.currentPlugin;
+    }
+
+    componentWillMount() {
+        let self = this;
+
+        Utils.bootstrapApplication().then(function() {
+            SkyquakeContainerStore.listen(self.listener);
+            SkyquakeContainerStore.getNav();
+            SkyquakeContainerStore.getEventStreams();
+        });
+
+        // Load multiplex-client
+        const script = document.createElement("script");
+
+        script.src = "/multiplex-client";
+        script.async = true;
+
+        document.body.appendChild(script);
+
+        Utils.setupMultiplexClient();
+    }
+
+    componentWillUnmount() {
+        SkyquakeContainerStore.unlisten(this.listener);
+    }
+    listener = (state) => {
+        this.setState(state);
+    }
+    matchesLoginUrl() {
+        //console.log("window.location.hash=", window.location.hash);
+        // First element in the results of match will be the part of the string
+        // that matched the expression. this string should be true against
+        // (window.location.hash.match(re)[0]).startsWith(Utils.loginHash)
+        var re = /#\/login?(\?|\/|$)/i;
+        //console.log("res=", window.location.hash.match(re));
+        return (window.location.hash.match(re)) ? true : false;
+    }
+    onToggle = (isOpen) => {
+        this.setState({
+            eventCenterIsOpen: isOpen
+        });
+    }
+
+    render() {
+        const {displayNotification, notificationMessage, displayScreenLoader, ...state} = this.state;
+        var html;
+
+        if (this.matchesLoginUrl()) {
+            html = (
+                <AltContainer>
+                    <div className="skyquakeApp">
+                        {this.props.children}
+                    </div>
+                </AltContainer>
+            );
+        } else {
+            let tag = this.props.routes[this.props.routes.length-1].name ? ': '
+                + this.props.routes[this.props.routes.length-1].name : '';
+            let routeName = this.props.location.pathname.split('/')[1];
+            html = (
+                <AltContainer flux={Alt}>
+                    <div className="skyquakeApp wrap">
+                        <Crouton
+                            id={Date.now()}
+                            message={notificationMessage}
+                            type={"error"}
+                            hidden={!(displayNotification && notificationMessage)}
+                            onDismiss={SkyquakeContainerActions.hideNotification}
+                        />
+                        <ScreenLoader show={displayScreenLoader}/>
+                        <SkyquakeNav nav={this.state.nav}
+                            currentPlugin={this.state.currentPlugin}
+                            store={SkyquakeContainerStore} />
+                        <div className="titleBar">
+                            <h1>{this.state.currentPlugin + tag}</h1>
+                        </div>
+                        <div className={"application " + routeName}>
+                            {this.props.children}
+                        </div>
+                        <EventCenter className="eventCenter"
+                            notifications={this.state.notifications}
+                            newNotificationEvent={this.state.newNotificationEvent}
+                            newNotificationMsg={this.state.newNotificationMsg}
+                            onToggle={this.onToggle} />
+                    </div>
+                </AltContainer>
+            );
+        }
+        return html;
+    }
+}
+skyquakeContainer.contextTypes = {
+    router: React.PropTypes.object
+  };
+
+/*
+<Breadcrumbs
+                            routes={this.props.routes}
+                            params={this.props.params}
+                            separator=" | "
+                        />
+ */
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeContainerActions.js b/skyquake/framework/widgets/skyquake_container/skyquakeContainerActions.js
new file mode 100644
index 0000000..773978b
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeContainerActions.js
@@ -0,0 +1,33 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import Alt from './skyquakeAltInstance.js';
+export default Alt.generateActions(
+	'getSkyquakeNavSuccess',
+	'openNotificationsSocketLoading',
+	'openNotificationsSocketSuccess',
+	'openNotificationsSocketError',
+	'getEventStreamsLoading',
+	'getEventStreamsSuccess',
+	'getEventStreamsError',
+    //Notifications
+    'showNotification',
+    'hideNotification',
+    //Screen Loader
+    'showScreenLoader',
+    'hideScreenLoader'
+);
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js b/skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
new file mode 100644
index 0000000..2ee50c0
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
@@ -0,0 +1,117 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import Alt from './skyquakeAltInstance.js';
+import $ from 'jquery';
+import SkyquakeContainerActions from './skyquakeContainerActions'
+
+let Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+let RW_REST_API_PORT = require('utils/rw.js').getSearchParams(window.location).rw_rest_api_port || 8008;
+
+if (DEV_MODE) {
+    HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+export default {
+    getNav() {
+        return {
+            remote: function() {
+                return new Promise(function(resolve, reject) {
+                    $.ajax({
+                        url: '/nav',
+                        type: 'GET',
+                        // beforeSend: Utils.addAuthorizationStub,
+                        success: function(data) {
+                            resolve(data);
+                        }
+                    })
+                })
+            },
+            success: SkyquakeContainerActions.getSkyquakeNavSuccess
+        }
+    },
+
+    getEventStreams() {
+        return {
+            remote: function(state, recordID) {
+                return new Promise(function(resolve, reject) {
+                    $.ajax({
+                        url: '//' + window.location.hostname + ':' + NODE_PORT + '/api/operational/restconf-state/streams?api_server=' + API_SERVER,
+                        type: 'GET',
+                        beforeSend: Utils.addAuthorizationStub,
+                        success: function(data) {
+                            resolve(data);
+                        }
+                    }).fail(function(xhr) {
+                        //Authentication and the handling of fail states should be wrapped up into a connection class.
+                        Utils.checkAuthentication(xhr.status);
+                    });;
+                });
+            },
+            loading: SkyquakeContainerActions.getEventStreamsLoading,
+            success: SkyquakeContainerActions.getEventStreamsSuccess,
+            error: SkyquakeContainerActions.getEventStreamsError
+        }
+    },
+
+    openNotificationsSocket() {
+        return {
+            remote: function(state, location, streamSource) {
+                return new Promise((resolve, reject) => {
+                    $.ajax({
+                        url: '//' + window.location.hostname + ':' + NODE_PORT + '/socket-polling?api_server=' + API_SERVER,
+                        type: 'POST',
+                        beforeSend: Utils.addAuthorizationStub,
+                        data: {
+                            url: location
+                        },
+                        success: (data) => {
+                            // var url = Utils.webSocketProtocol() + '//' + window.location.hostname + ':' + data.port + data.socketPath;
+                            // var ws = new WebSocket(url);
+                            // resolve({
+                            //     ws: ws,
+                            //     streamSource: streamSource
+                            // });
+                            const checker = () => {
+                                if (!Utils.isMultiplexerLoaded()) {
+                                    setTimeout(() => {
+                                        checker();
+                                    }, 500);
+                                } else {
+                                    resolve({
+                                        connection: data.id,
+                                        streamSource: streamSource
+                                    });
+                                }
+                            };
+
+                            checker();
+                        }
+                    });
+                });
+            },
+            loading: SkyquakeContainerActions.openNotificationsSocketLoading,
+            success: SkyquakeContainerActions.openNotificationsSocketSuccess,
+            error: SkyquakeContainerActions.openNotificationsSocketError
+        }
+    }
+}
+
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js b/skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
new file mode 100644
index 0000000..fe4a7b0
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
@@ -0,0 +1,237 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+//This will reach out to the global routes endpoint
+
+import Alt from './skyquakeAltInstance.js';
+import SkyquakeContainerSource from './skyquakeContainerSource.js';
+import SkyquakeContainerActions from './skyquakeContainerActions';
+import _ from 'lodash';
+//Temporary, until api server is on same port as webserver
+var rw = require('utils/rw.js');
+var API_SERVER = rw.getSearchParams(window.location).api_server;
+var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
+
+class SkyquakeContainerStore {
+    constructor() {
+        this.currentPlugin = getCurrentPlugin();
+        this.nav = {};
+        this.notifications = [];
+        this.socket = null;
+        //Notification defaults
+        this.notificationMessage = '';
+        this.displayNotification = false;
+        //Screen Loader default
+        this.displayScreenLoader = false;
+        this.bindActions(SkyquakeContainerActions);
+        this.exportAsync(SkyquakeContainerSource);
+
+
+        this.exportPublicMethods({
+            // getNav: this.getNav
+        });
+
+    }
+    getSkyquakeNavSuccess = (data) => {
+        var self = this;
+        this.setState({
+            nav: decorateAndTransformNav(data, self.currentPlugin)
+        })
+    }
+
+    closeSocket = () => {
+        if (this.socket) {
+            window.multiplexer.channel(this.channelId).close();
+        }
+        this.setState({
+            socket: null
+        });
+    }
+    //Remove once logging plugin is implemented
+    getSysLogViewerURLSuccess(data){
+        window.open(data.url);
+    }
+    getSysLogViewerURLError(data){
+        console.log('failed', data)
+    }
+
+    openNotificationsSocketLoading = () => {
+        this.setState({
+            isLoading: true
+        })
+    }
+
+    openNotificationsSocketSuccess = (data) => {
+        var self = this;
+
+        let connection = data.connection;
+        let streamSource = data.streamSource;
+        console.log('Success opening notification socket for stream ', streamSource);
+        
+        let ws = window.multiplexer.channel(connection);
+        
+        if (!connection) return;
+        self.setState({
+            socket: ws.ws,
+            isLoading: false,
+            channelId: connection
+        });
+
+        ws.onmessage = (socket) => {
+            try {
+                var data = JSON.parse(socket.data);
+                if (!data.notification) {
+                    console.warn('No notification in the received payload: ', data);
+                } else {
+                    // Temp to test before adding multi-sources
+                    data.notification.source = streamSource;
+                    if (_.indexOf(self.notifications, data.notification) == -1) {
+                        // newly appreared event.
+                        // Add to the notifications list and setState
+                        self.notifications.unshift(data.notification);
+                        self.setState({
+                            newNotificationEvent: true,
+                            newNotificationMsg: data.notification,
+                            notifications: self.notifications,
+                            isLoading: false
+                        });
+                    }
+                }
+            } catch(e) {
+                console.log('Error in parsing data on notification socket');
+            }
+        };
+
+        ws.onclose = () => {
+            self.closeSocket();
+        };
+    }
+
+    openNotificationsSocketError = (data) => {
+        console.log('Error opening notification socket', data);
+    }
+
+    getEventStreamsLoading = () => {
+        this.setState({
+            isLoading: true
+        });
+    }
+
+    getEventStreamsSuccess = (streams) => {
+        console.log('Found streams: ', streams);
+        let self = this;
+
+        streams['ietf-restconf-monitoring:streams'] &&
+        streams['ietf-restconf-monitoring:streams']['stream'] &&
+        streams['ietf-restconf-monitoring:streams']['stream'].map((stream) => {
+            stream['access'] && stream['access'].map((streamLocation) => {
+                if (streamLocation['encoding'] == 'ws_json') {
+                    setTimeout(() => {
+                        self.getInstance().openNotificationsSocket(streamLocation['location'], stream['name']);
+                    }, 0);
+                }
+            })
+        })
+
+        this.setState({
+            isLoading: true,
+            streams: streams
+        })
+    }
+
+    getEventStreamsError = (error) => {
+        console.log('Failed to get streams object');
+        this.setState({
+            isLoading: false
+        })
+    }
+
+    //Notifications
+    showNotification = (data) => {
+        if(typeof(data) == 'string') {
+            this.setState({
+                displayNotification: true,
+                notificationMessage: data
+            });
+        } else {
+            if(data.type == 'error') {
+                this.setState({
+                    displayNotification: true,
+                    notificationMessage: data.msg,
+                    displayScreenLoader: false
+                });
+            }
+        }
+    }
+    hideNotification = () => {
+        this.setState({
+            displayNotification: false
+        })
+    }
+    //ScreenLoader
+    showScreenLoader = () => {
+        this.setState({
+            displayScreenLoader: true
+        });
+    }
+    hideScreenLoader = () => {
+        this.setState({
+            displayScreenLoader: false
+        })
+    }
+
+}
+
+/**
+ * Receives nav data from routes rest endpoint and decorates the data with internal/external linking information
+ * @param  {object} nav           Nav item from /nav endoingpoint
+ * @param  {string} currentPlugin Current plugin name taken from url path.
+ * @return {array}               Returns list of constructed nav items.
+ */
+function decorateAndTransformNav(nav, currentPlugin) {
+    for ( let k in nav) {
+        nav[k].pluginName = k;
+            if (k != currentPlugin)  {
+                nav[k].routes.map(function(route, i) {
+                    if (API_SERVER) {
+                        route.route = '/' + k + '/index.html?api_server=' + API_SERVER + '&upload_server=' + UPLOAD_SERVER + '#' + route.route;
+                    } else {
+                        route.route = '/' + k + '/#' + route.route;
+                    }
+                    route.isExternal = true;
+                })
+            }
+        }
+        return nav;
+}
+
+function getCurrentPlugin() {
+    var paths = window.location.pathname.split('/');
+    var currentPath = null;
+    if (paths[0] != "") {
+        currentPath = paths[0]
+    } else {
+        currentPath = paths[1];
+    }
+    if (currentPath != null) {
+        return currentPath;
+    } else {
+        console.error('Well, something went horribly wrong with discovering the current plugin name - perhaps you should consider moving this logic to the server?')
+    }
+}
+
+export default Alt.createStore(SkyquakeContainerStore);
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx b/skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx
new file mode 100644
index 0000000..73a2f02
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx
@@ -0,0 +1,226 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+import React from 'react';
+import { Link } from 'react-router';
+import Utils from 'utils/utils.js';
+import Crouton from 'react-crouton';
+import 'style/common.scss';
+
+//Temporary, until api server is on same port as webserver
+var rw = require('utils/rw.js');
+var API_SERVER = rw.getSearchParams(window.location).api_server;
+var UPLOAD_SERVER = rw.getSearchParams(window.location).upload_server;
+
+//
+// Internal classes/functions
+//
+
+class LogoutAppMenuItem extends React.Component {
+    handleLogout() {
+        Utils.clearAuthentication();
+    }
+    render() {
+        return (
+            <div className="app">
+                <h2>
+                    <a onClick={this.handleLogout}>
+                        Logout
+                    </a>
+                </h2>
+            </div>
+        );
+    }
+}
+
+
+//
+// Exported classes and functions
+//
+
+//
+/**
+ * Skyquake Nav Component. Provides navigation functionality between all plugins
+ */
+export default class skyquakeNav extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {};
+        this.state.validateErrorEvent = 0;
+        this.state.validateErrorMsg = '';
+    }
+    validateError = (msg) => {
+        this.setState({
+            validateErrorEvent: true,
+            validateErrorMsg: msg
+        });
+    }
+    validateReset = () => {
+        this.setState({
+            validateErrorEvent: false
+        });
+    }
+    returnCrouton = () => {
+        return <Crouton
+            id={Date.now()}
+            message={this.state.validateErrorMsg}
+            type={"error"}
+            hidden={!(this.state.validateErrorEvent && this.state.validateErrorMsg)}
+            onDismiss={this.validateReset}
+        />;
+    }
+    render() {
+        let html;
+        html = (
+                <div>
+                {this.returnCrouton()}
+            <nav className="skyquakeNav">
+                {buildNav.call(this, this.props.nav, this.props.currentPlugin)}
+            </nav>
+
+            </div>
+        )
+        return html;
+    }
+}
+skyquakeNav.defaultProps = {
+    nav: {}
+}
+/**
+ * Returns a React Component
+ * @param  {object} link  Information about the nav link
+ * @param  {string} link.route Hash route that the SPA should resolve
+ * @param  {string} link.name Link name to be displayed
+ * @param  {number} index index of current array item
+ * @return {object} component A React LI Component
+ */
+//This should be extended to also make use of internal/external links and determine if the link should refer to an outside plugin or itself.
+export function buildNavListItem (k, link, index) {
+    let html = false;
+    if (link.type == 'external') {
+        this.hasSubNav[k] = true;
+        html = (
+            <li key={index}>
+                {returnLinkItem(link)}
+            </li>
+        );
+    }
+    return html;
+}
+
+/**
+ * Builds a link to a React Router route or a new plugin route.
+ * @param  {object} link Routing information from nav object.
+ * @return {object}  component   returns a react component that links to a new route.
+ */
+export function returnLinkItem(link) {
+    let ref;
+    let route = link.route;
+    if(link.isExternal) {
+        ref = (
+            <a href={route}>{link.label}</a>
+        )
+    } else {
+        if(link.path && link.path.replace(' ', '') != '') {
+            route = link.path;
+        }
+        if(link.query) {
+            let query = {};
+            query[link.query] = '';
+            route = {
+                pathname: route,
+                query: query
+            }
+        }
+        ref = (
+           <Link to={route}>
+                {link.label}
+            </Link>
+        )
+    }
+    return ref;
+}
+
+/**
+ * Constructs nav for each plugin, along with available subnavs
+ * @param  {array} nav List returned from /nav endpoint.
+ * @return {array}     List of constructed nav element for each plugin
+ */
+export function buildNav(nav, currentPlugin) {
+    let navList = [];
+    let navListHTML = [];
+    let secondaryNav = [];
+    let self = this;
+    self.hasSubNav = {};
+    let secondaryNavHTML = (
+        <div className="secondaryNav" key="secondaryNav">
+        {secondaryNav}
+            <LogoutAppMenuItem />
+        </div>
+    )
+    for (let k in nav) {
+        if (nav.hasOwnProperty(k)) {
+            self.hasSubNav[k] = false;
+            let header = null;
+            let navClass = "app";
+            let routes = nav[k].routes;
+            let navItem = {};
+            //Primary plugin title and link to dashboard.
+            let route;
+            let NavList;
+            if (API_SERVER) {
+                route = routes[0].isExternal ? '/' + k + '/index.html?api_server=' + API_SERVER + '' + '&upload_server=' + UPLOAD_SERVER + '' : '';
+            } else {
+                route = routes[0].isExternal ? '/' + k + '/' : '';
+            }
+            let dashboardLink = returnLinkItem({
+                isExternal: routes[0].isExternal,
+                pluginName: nav[k].pluginName,
+                label: nav[k].label || k,
+                route: route
+            });
+            if (nav[k].pluginName == currentPlugin) {
+                navClass += " active";
+            }
+            NavList = nav[k].routes.map(buildNavListItem.bind(self, k));
+            navItem.priority = nav[k].priority;
+            navItem.order = nav[k].order;
+            navItem.html = (
+                <div key={k} className={navClass}>
+                    <h2>{dashboardLink} {self.hasSubNav[k] ? <span className="oi" data-glyph="caret-bottom"></span> : ''}</h2>
+                    <ul className="menu">
+                        {
+                            NavList
+                        }
+                    </ul>
+                </div>
+            );
+            navList.push(navItem)
+        }
+    }
+    //Sorts nav items by order and returns only the markup
+    navListHTML = navList.sort((a,b) => a.order - b.order).map(function(n) {
+        if((n.priority  < 2)){
+            return n.html;
+        } else {
+            secondaryNav.push(n.html);
+        }
+    });
+    navListHTML.push(secondaryNavHTML);
+    return navListHTML;
+}
diff --git a/skyquake/framework/widgets/skyquake_container/skyquakeRouter.jsx b/skyquake/framework/widgets/skyquake_container/skyquakeRouter.jsx
new file mode 100644
index 0000000..fc3231d
--- /dev/null
+++ b/skyquake/framework/widgets/skyquake_container/skyquakeRouter.jsx
@@ -0,0 +1,106 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import {
+    Router, Route, hashHistory, IndexRoute
+}
+from 'react-router';
+import SkyquakeContainer from 'widgets/skyquake_container/skyquakeContainer.jsx';
+
+export default function(config, context) {
+    let routes = [];
+    let index = null;
+    let components = null;
+    if (config && config.routes) {
+        routes =  buildRoutes(config.routes)
+        function buildRoutes(routes) {
+            return routes.map(function(route, index) {
+                let routeConfig = {};
+                if (route.route && route.component) {
+                    // ES6 modules need to specify default
+                    let path = route.route;
+                    if(route.path && route.path.replace(' ', '') != '') {
+                        path = route.path
+                    }
+                    routeConfig = {
+                        path: path,
+                        name: route.label,
+                        getComponent: function(location, cb) {
+                            require.ensure([], (require) => {
+                                cb(null, context(route.component).default)
+                            })
+                        }
+                    }
+                    if(route.routes && (route.routes.length > 0)){
+                        routeConfig.childRoutes = buildRoutes(route.routes)
+                    }
+                } else {
+                    console.error('Route not properly configured. Check that both path and component are specified');
+                }
+                return routeConfig;
+            });
+        }
+        routes.push({
+            path: '/login',
+            name: 'Login',
+            component:  require('../login/login.jsx').default
+        });
+        routes.push({
+            path:'*',
+            name: 'Dashboard',
+            getComponent: function(loc, cb) {
+                cb(null, context(config.dashboard).default);
+            }
+        });
+        if (config.dashboard) {
+            // ES6 modules need to specify default
+            index = <IndexRoute component={context(config.dashboard).default} />;
+        } else {
+            index = DefaultDashboard
+        }
+
+        const rootRoute = {
+            component: SkyquakeContainer,
+            path: '/',
+            name: 'Dashboard',
+            indexRoute: {
+                component: (config.dashboard) ? context(config.dashboard).default : DefaultDashboard
+            },
+            childRoutes: routes
+        }
+
+        return((
+            <Router history={hashHistory} routes={rootRoute}>
+            </Router>
+        ))
+    } else {
+        console.error('There are no routes configured in the config.json file');
+    }
+}
+
+//When no default dashboard is specified in the plugin_config.json, use this component.
+class DefaultDashboard extends React.Component {
+    constructor(props) {
+        super(props)
+    }
+    render() {
+        let html;
+        html = <div> This is a default dashboard page component </div>;
+        return html;
+    }
+}
diff --git a/skyquake/framework/widgets/sq-input-range-slider/sq-input-range-slider.jsx b/skyquake/framework/widgets/sq-input-range-slider/sq-input-range-slider.jsx
new file mode 100644
index 0000000..6a0bb97
--- /dev/null
+++ b/skyquake/framework/widgets/sq-input-range-slider/sq-input-range-slider.jsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import './sq-input-range-slider.scss';
+
+export default class SqInputRangeSlider extends React.Component {
+    constructor(props){
+        super(props);
+        this.state = {};
+        this.state.position = {
+            left: 50
+        };
+        this.className = "SqRangeInput";
+    }
+
+    updateHandle = (e) => {
+        this.props.onChange(e.target.valueAsNumber);
+    }
+
+    render() {
+        const {vertical, className, min, max, step,  width, disabled, ...props} = this.props;
+        let class_name = this.className;
+        let dataListHTML = null;
+        let dataListOptions = null;
+        let dataListID = null;
+        let inputStyle = {};
+        let inputContainer = {
+        };
+        let style = {
+            width: width + 'px'
+        }
+        if(vertical) {
+            class_name += ' ' + this.className + '--vertical';
+            style.position = 'absolute';
+            style.left = (width * -1 + 32) + 'px';
+            style.width = width + 'px';
+            inputContainer.margin = '0 10px';
+            inputContainer.width = '70px';
+            inputContainer.height = (width + 40) + 'px';
+            inputContainer.background = 'white';
+            inputContainer.border = '1px solid #ccc';
+        } else {
+            class_name += ' ' + this.className + '--horizontal';
+            // style.position = 'absolute';
+            // style.top = 12 + 'px';
+            inputContainer.margin = '10px 0px';
+            inputContainer.height = '70px';
+            inputContainer.width = (width + 40) + 'px';
+            inputContainer.background = 'white';
+            inputContainer.border = '1px solid #ccc';
+        }
+        return (
+                <div className={this.className + '--container'} style={inputContainer}>
+                    <div className={class_name + ' ' + className + (disabled ? ' is-disabled' : '')}>
+                        <input
+                            style={style}
+                            orient= {vertical ? "vertical" : "horizontal"}
+                            type="range"
+                            onChange={this.updateHandle}
+                            ref="range"
+                            max={max}
+                            min={min}
+                            step={step}
+                            disabled={disabled}
+                         />
+                     </div>
+                 </div>
+        )
+    }
+}
+
+SqInputRangeSlider.defaultProps = {
+    //Horizontal vs Vertical Slider
+    vertical: false,
+    //Override classes
+    className: '',
+    min:0,
+    max:100,
+    step: 1,
+    //width/length in px
+    width:100,
+    disabled: false,
+    onChange: function(value) {
+        console.log(value);
+    }
+}
diff --git a/skyquake/framework/widgets/sq-input-range-slider/sq-input-range-slider.scss b/skyquake/framework/widgets/sq-input-range-slider/sq-input-range-slider.scss
new file mode 100644
index 0000000..b09dd16
--- /dev/null
+++ b/skyquake/framework/widgets/sq-input-range-slider/sq-input-range-slider.scss
@@ -0,0 +1,176 @@
+@import '../../style/_colors.scss';
+$slider-background: rgba(19, 82, 150, 0.2);
+$disabled-slider-background: $dark-gray;
+//Generates ticks on bar. Not currently used
+$slider-background-ticks: repeating-linear-gradient(to right, $slider-background, $slider-background 0%, #fff 0%, #fff 1%, $slider-background 1%, $slider-background 25%);
+$thumb-color: rgba(0, 119, 200, 1);
+$disabled-thumb-color: $dark-gray;
+$track-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
+$thumb-height: 50;
+$track-height: 3;
+$input-margin: 20;
+$-webkit-handle-offset: ($track-height - $thumb-height) / 2;
+
+//(thH-ttW) / 2
+
+@mixin thumbStyling {
+    box-shadow: $track-shadow;
+    border: 0px solid #000000;
+    background: $thumb-color;
+    box-shadow: 2px 2px 2px 0px #ccc;
+    height: $thumb-height + px;
+    width: 20px;
+    cursor: pointer;
+}
+@mixin inputTrack {
+    width: 100%;
+    height: $track-height + px;
+    cursor: pointer;
+    animate: 0.2s;
+}
+@mixin infoBase {
+    display: flex;
+    justify-content: space-between;
+}
+@mixin sliderBase {
+    input[type=range] {
+        position:absolute;
+        -webkit-appearance: none;
+        margin: $input-margin + px 0;
+        width: 100%;
+        &:focus {
+            outline: none;
+        }
+        &::-webkit-slider-thumb {
+            @include thumbStyling;
+            -webkit-appearance: none;
+            margin-top: $-webkit-handle-offset + px;
+
+        }
+        &::-moz-range-thumb {
+            @include thumbStyling;
+        }
+        &::-ms-thumb {
+            @include thumbStyling;
+        }
+        &::-webkit-slider-runnable-track {
+            @include inputTrack;
+            box-shadow: $track-shadow;
+            background: $slider-background;
+            border: 0px solid #000101;
+        }
+        &:focus::-webkit-slider-runnable-track {
+            background: $slider-background;
+        }
+        &::-moz-range-track {
+            @include inputTrack;
+            box-shadow: $track-shadow;
+            background: $slider-background;
+            border: 0px solid #000101;
+        }
+        &::-ms-track {
+            @include inputTrack;
+            background: transparent;
+            border-color: transparent;
+            border-width: 39px 0;
+            color: transparent;
+        }
+        &::-ms-fill-lower {
+            background: $slider-background;
+            border: 0px solid #000101;
+            box-shadow: $track-shadow;
+        }
+        &::-ms-fill-upper {
+            background: #ac51b5;
+            border: 0px solid #000101;
+            box-shadow: $track-shadow;
+        }
+        &:focus::-ms-fill-lower {
+            background: $slider-background;
+        }
+        &:focus::-ms-fill-upper {
+            background: $slider-background;
+        }
+    }
+    &-info {
+        @include infoBase
+    }
+}
+.SqRangeInput {
+    &--container {
+        background:white;
+        border: 1px solid #ccc;
+    }
+    &--horizontal {
+        @include sliderBase;
+        position: relative;
+        height:$thumb-height + px;
+        margin: $input-margin / 2 + px;
+    }
+    &--vertical {
+        @include sliderBase;
+        position: relative;
+
+        input[type=range] {
+            -webkit-appearance: none;
+            height: 100%;
+            width: $track-height + px;
+            transform: rotate(270deg);
+            transform-origin: right;
+            &::-webkit-slider-thumb {
+                box-shadow: -2px 2px 2px 0px #ccc;
+            }
+            &::-moz-range-thumb {
+                box-shadow: -2px 2px 2px 0px #ccc;
+            }
+            &::-ms-thumb {
+                box-shadow: -2px 2px 2px 0px #ccc;
+            }
+        }
+        &-info {
+            @include infoBase;
+
+            div {
+                transform: translateY(10px) rotate(90deg);
+            }
+        }
+    }
+    &.is-disabled {
+        input[type=range] {
+            &:disabled {
+                &::-webkit-slider-thumb {
+                    background: $disabled-thumb-color;
+                }
+                &::-moz-range-thumb {
+                    background: $disabled-thumb-color;
+                }
+                &::-ms-thumb {
+                    background: $disabled-thumb-color;
+                }
+                &::-webkit-slider-runnable-track {
+                    background: $disabled-slider-background;
+                }
+                &:focus::-webkit-slider-runnable-track {
+                    background: $disabled-slider-background;
+                }
+                &::-moz-range-track {
+                    background: $disabled-slider-background;
+                }
+                &::-ms-track {
+                }
+                &::-ms-fill-upper {
+                    background: $disabled-slider-background;
+                }
+                &::-ms-fill-lower {
+                    background: $disabled-slider-background;
+                }
+                &:focus::-ms-fill-lower {
+                    background: $disabled-slider-background;
+                }
+                &:focus::-ms-fill-upper {
+                    background: $disabled-slider-background;
+                }
+            }
+        }
+    }
+}
diff --git a/skyquake/framework/widgets/text-area/rw.text-area.js b/skyquake/framework/widgets/text-area/rw.text-area.js
new file mode 100644
index 0000000..7a036a6
--- /dev/null
+++ b/skyquake/framework/widgets/text-area/rw.text-area.js
@@ -0,0 +1,232 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+var React = require('react');
+var ButtonEventListenerMixin = require('../mixins/ButtonEventListener.js');
+
+
+/**
+ *  A text input component.
+ *  It's props values and a brief description below
+ *
+ *  value: Holds the initial text content of the input.
+ *  label: The text content of the label.
+ *  requiredText: The text content of the "if required" message.
+ *  errorText: The text content of the error message.
+ *  placeholder: The initial placeholder text of the input
+ *  ClassName: Css Classes applied to the element.
+ *  rows: Number of text lines the input element displays at one time.
+ *  cols: Number of characters per line.
+ *  resizable: If the input element is resizable.
+ *  isRequired: A boolean indicating whether or not the input is required.
+ *  isDisabled: A boolean indicating the disabled state of the element.
+ *  isReadONly: A boolean indicating whether or not the input is read only.
+ *  maxLength: The hard limit on how many characters can be in the input.
+ **/
+module.exports = React.createClass({
+  displayName: "TextArea",
+  mixins:[ButtonEventListenerMixin],
+  propTypes: {
+    value:           React.PropTypes.string,
+    label:           React.PropTypes.string,
+    requiredText:    React.PropTypes.string,
+    errorText:       React.PropTypes.string,
+    placeholder:     React.PropTypes.string,
+    className:       React.PropTypes.string,
+    rows:            React.PropTypes.number,
+    cols:            React.PropTypes.number,
+    resizable:       React.PropTypes.bool,
+    isRequired:      React.PropTypes.bool,
+    isDisabled:      React.PropTypes.bool,
+    isReadOnly:      React.PropTypes.bool,
+    maxLength:       React.PropTypes.number,
+    instructions:    React.PropTypes.string
+  },
+
+
+  /**
+   * Sets the default input state.
+   * If there is no description for the variable, assume it's the same as it's props counterpart.
+   *
+   * value: Holds the current text contents of the input element.
+   * isActive: Boolean to indicate if input is active.
+   * isHovered: Boolean to indicate if the input is being hovered over.
+   * isFocused: Boolean to indicate if the input has been focused.
+   * isDisabled: Boolean to indicate if input has been disabled.
+   *
+   * @returns {{sizeOfButton: (*|string), isActive: boolean, isHovered: boolean, isFocused: boolean, isDisabled: (*|boolean),
+   * isReadOnly: (*|.textInput.isReadOnly|.exports.propTypes.isReadOnly|.exports.getInitialState.isReadOnly|boolean),
+   * isRequired: (*|.textInput.isRequired|.exports.propTypes.isRequired|.exports.getInitialState.isRequired|isRequired|null),
+   * isValid: null, isSuccess: null}}
+   */
+  getInitialState: function() {
+    return {
+      value:            this.props.value || "",
+      label:            this.props.label || "",
+      requiredText:     this.props.requiredText || "Required",
+      errorText:        this.props.errorText || "",
+      instructionsText: this.props.instructions || "",
+      isActive:         false,
+      isHovered:        false,
+      isFocused:        false,
+      isDisabled:       this.props.isDisabled || false,
+      isReadOnly:       this.props.isReadOnly || false,
+      isRequired:       this.props.isRequired || null,
+      isValid:          null,      // Three way bool. Valid: true.   Invalid: false. Not acted on: null.
+      isSuccess:        null     // Three way bool. Success: true. Error: false.   Not acted on: null.
+
+    }
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * "nextProps" and "nextState" hold the state and property variables as they will be after the update.
+   * "this.props" and "this.state" hold the state and property variables as they were before the update.
+   * returns true if the state have changed. Otherwise returns false.
+   * @param nextProps
+   * @param nextState
+   * @returns {boolean}
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = this.state.isReadOnly + this.state.isDisabled + this.state.isActive + this.state.isFocused +
+      this.state.isHovered + this.state.isValid + this.state.isSuccess + this.state.value;
+    var nextStateString = nextState.isReadOnly + nextState.isDisabled + nextState.isActive + nextState.isFocused +
+      nextState.isHovered + nextState.isValid + nextState.isSuccess + nextState.value;
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    return true;
+  },
+  /**
+   * Makes sure that when the user types new input, the contents of the input changes accordingly.
+   *
+   * @param event
+   */
+  handleChange: function(event) {
+    this.setState({value:event.target.value});
+  },
+
+  /**
+   * Returns a string reflecting the current state of the input.
+   * If the component state "isDisabled" is true, returns a string "disabled".
+   * If the component state "isReadOnly" is true, returns a string "readonly".
+   * Otherwise returns a string containing a word for each state that has been set to true.
+   * (ie "active focused" if the states active and focused are true, but hovered is false).
+   * @returns {string}
+   */
+  setComponentState: function() {
+    var ret = "";
+    if (this.state.isDisabled) {
+      return "disabled";
+    }
+    if (this.state.isReadOnly) {
+      return "readonly";
+    }
+    if (this.state.isActive) {
+      ret += "active ";
+    }
+    if (this.state.isHovered) {
+      ret += "hovered ";
+    }
+    if (this.state.isFocused) {
+      ret += "focused ";
+    }
+    return ret;
+  },
+
+  /**
+   * Renders the Text Input component.
+   *
+   **/
+  render: function() {
+    var value = this.state.value;
+    var input = null;
+    var textarea_style = {};
+    var textarea_state = this.setComponentState();
+
+    // creates a style for the "resizable" attribute.
+    if (!this.props.resizable) {
+      textarea_style.resize = "none";
+    }
+
+    // The input element.
+    input = React.createElement("textarea", {
+        ref:               "inputRef",
+        "data-state":      textarea_state,
+        value:             value,
+        "style":           textarea_style,
+        placeholder:       this.props.placeholder,
+        rows:              this.props.rows,
+        cols:              this.props.cols,
+        maxLength:         this.props.maxLength,
+        required:          this.state.isRequired,
+        disabled:          this.state.isDisabled,
+        readOnly:          this.state.isReadOnly,
+        onChange:          this.handleChange,
+        onClick:           this.onClick,
+        onMouseUp:         this.onMouseUp,
+        onMouseDown:       this.onMouseDown,
+        onMouseOver:       this.onMouseOver,
+        onMouseEnter:      this.onMouseEnter,
+        onMouseLeave:      this.onMouseLeave,
+        onMouseOut:        this.onMouseOut,
+        onTouchCancel:     this.onTouchCancel,
+        onTouchEnd:        this.onTouchEnd,
+        onTouchMove:       this.onTouchMove,
+        onTouchStart:      this.onTouchStart,
+        onKeyDown:         this.onKeyDown,
+        onKeyPress:        this.onKeyPress,
+        onKeyUp:           this.onKeyUp,
+        onFocus:           this.onFocus,
+        onBlur:            this.onBlur,
+          className:         (this.props.className || "rw-textarea"),
+          tabIndex:          0
+      },
+      null
+    );
+
+    // The "if required" element. It displays a label if the element is required.
+    if(this.props.isRequired == true){
+      var requiredEle = React.createElement("small", {className: "rw-form-requiredLabel"}, this.state.requiredText);
+    }
+
+    // The label element associated with the input.
+    var label = React.createElement("label", null, this.state.label, requiredEle, input);
+
+    // The "error" element.  It pops up as a message if there is an error with the input.
+    if(this.state.errorText != "") {
+      var error = React.createElement("p", {className: "rw-form-errorMsg"}, this.state.errorText);
+    }
+
+    //
+    if(this.state.instructionsText != ""){
+      var instructions = React.createElement("p", {className: "rw-form-instructions"}, this.state.instructionsText)
+    }
+
+    // The parent element for all.
+    var ret = React.createElement("div", {
+      "data-state":      textarea_state,
+      required:          this.state.isRequired,
+      disabled:          this.state.isDisabled,
+      readOnly:          this.state.isReadOnly
+    }, label, error, instructions);
+
+    return ret;
+  }
+});
diff --git a/skyquake/framework/widgets/text-input/check-box/rw.check-box.js b/skyquake/framework/widgets/text-input/check-box/rw.check-box.js
new file mode 100644
index 0000000..db8f37e
--- /dev/null
+++ b/skyquake/framework/widgets/text-input/check-box/rw.check-box.js
@@ -0,0 +1,233 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+var React = require('react');
+var ButtonEventListenerMixin = require('../mixins/ButtonEventListener.js');
+
+
+/**
+ *  A check-box component.
+ *  It's props values and a brief description below
+ *
+ *  label:        The label for the checkbox group.
+ *  checkboxes:   The object that creates each checkbox.  Each object has a property "label" and "checked".
+ *    label:        The label for the individual checkbox.
+ *    checked:      If set to true, the individual checkbox is initialized with a check.
+ *  requiredText: The text content of the "if required" message.
+ *  errorText:    The text content of the error message.
+ *  ClassName:    Css Classes applied to the element.
+ *  size:         The size of the element.
+ *  isRequired:   A boolean indicating whether or not the input is required.
+ *  isDisabled:   A boolean indicating the disabled state of the element.
+ *  isReadOnly:   A boolean indicating whether or not the input is read only.
+ *  instructions: The text content of the instructions
+ **/
+module.exports = React.createClass({
+  displayName: "CheckBox",
+  mixins:[ButtonEventListenerMixin],
+  propTypes: {
+    label:           React.PropTypes.string,
+    checkboxes:        React.PropTypes.arrayOf(
+      React.PropTypes.shape(
+        {
+          label: React.PropTypes.string,
+          checked: React.PropTypes.bool,
+          indeterminate: React.PropTypes.bool
+        }
+      )),
+    requiredText:    React.PropTypes.string,
+    instructionText: React.PropTypes.string,
+    errorText:       React.PropTypes.string,
+    className:       React.PropTypes.string,
+    size:            React.PropTypes.string,
+    isRequired:      React.PropTypes.bool,
+    isDisabled:      React.PropTypes.bool,
+    isReadOnly:      React.PropTypes.bool
+  },
+
+
+  /**
+   * Sets the default input state.
+   * If there is no description for the variable, assume it's the same as it's props counterpart.
+   *
+   * isActive: Boolean to indicate if input is active.
+   * isHovered: Boolean to indicate if the input is being hovered over.
+   * isFocused: Boolean to indicate if the input has been focused.
+   * isDisabled: Boolean to indicate if input has been disabled.
+   *
+   * @returns {{sizeOfButton: (*|string), isActive: boolean, isHovered: boolean, isFocused: boolean, isDisabled: (*|boolean),
+   * isReadOnly: (*|.textInput.isReadOnly|.exports.propTypes.isReadOnly|.exports.getInitialState.isReadOnly|boolean),
+   * isRequired: (*|.textInput.isRequired|.exports.propTypes.isRequired|.exports.getInitialState.isRequired|isRequired|null),
+   * isValid: null, isSuccess: null}}
+   */
+  getInitialState: function() {
+
+    return {
+      mounted:              false,
+      label:                this.props.label || "",
+      requiredText:         this.props.requiredText || "Required",
+      instructionsText:     this.props.instructions || "",
+      errorText:            this.props.errorText || "",
+      size:                 this.props.size || '',
+      isActive:             false,
+      isHovered:            false,
+      isFocused:            false,
+      isDisabled:           this.props.disabled || false,
+      isReadOnly:           this.props.isReadOnly || false,
+      isRequired:           this.props.isRequired || null,
+      isValid:              null,      // Three way bool. Valid: true.   Invalid: false. Not acted on: null.
+      isSuccess:            null       // Three way bool. Success: true. Error: false.   Not acted on: null.
+    }
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * "nextProps" and "nextState" hold the state and property variables as they will be after the update.
+   * "this.props" and "this.state" hold the state and property variables as they were before the update.
+   * returns true if the state have changed. Otherwise returns false.
+   * @param nextProps
+   * @param nextState
+   * @returns {boolean}
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = this.state.isReadOnly + this.state.isDisabled + this.state.isActive + this.state.isFocused +
+      this.state.isHovered + this.state.isValid + this.state.isSuccess;
+    var nextStateString = nextState.isReadOnly + nextState.isDisabled + nextState.isActive + nextState.isFocused +
+      nextState.isHovered + nextState.isValid + nextState.isSuccess;
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    return true;
+  },
+
+  componentDidMount: function() {
+    this.state.mounted = true;
+    this.render();
+  },
+
+  /**
+   * Returns a string reflecting the current state of the input.
+   * If the component state "isDisabled" is true, returns a string "disabled".
+   * If the component state "isReadOnly" is true, returns a string "readonly".
+   * Otherwise returns a string containing a word for each state that has been set to true.
+   * (ie "active focused" if the states active and focused are true, but hovered is false).
+   * @returns {string}
+   */
+  setComponentState: function() {
+
+    var ret = "";
+    if (this.state.isDisabled) {
+      return "disabled";
+    }
+    if (this.state.isReadOnly) {
+      return "readonly";
+    }
+    if (this.state.isActive) {
+      ret += "active ";
+    }
+    if (this.state.isHovered) {
+      ret += "hovered ";
+    }
+    if (this.state.isFocused) {
+      ret += "focused ";
+    }
+    return ret;
+  },
+
+  localOnClick: function(e) {
+    this.onClick(e);
+  },
+
+  /**
+   * Renders the checkbox component.
+   *
+   **/
+  render: function() {
+    var input = [];
+    var checkbox = [];
+    var label = [];
+    var input_style = {};
+    var input_state = this.setComponentState();
+
+    // The group label element
+    label = React.createElement("label", {className: "rw-form__label"}, this.props.label);
+
+    // Creates each individual checkbox element and the label for each.
+    for (var i = 0; i < this.props.checkboxes.length; i++) {
+      checkbox[i] = React.createElement("input",{
+        key:               i,
+        defaultChecked:    this.props.checkboxes[i].checked,
+        //indeterminate: true,
+        type:              "checkbox",
+        readOnly:          this.props.readonly,
+        disabled:          this.props.disabled,
+        onClick:           this.localOnClick,
+        onMouseUp:         this.onMouseUp,
+        onMouseDown:       this.onMouseDown,
+        onMouseOver:       this.onMouseOver,
+        onMouseEnter:      this.onMouseEnter,
+        onMouseLeave:      this.onMouseLeave,
+        onMouseOut:        this.onMouseOut,
+        onTouchCancel:     this.onTouchCancel,
+        onTouchEnd:        this.onTouchEnd,
+        onTouchMove:       this.onTouchMove,
+        onTouchStart:      this.onTouchStart,
+        onKeyDown:         this.onKeyDown,
+        onKeyPress:        this.onKeyPress,
+        onKeyUp:           this.onKeyUp,
+        onFocus:           this.onFocus,
+        onBlur:            this.onBlur
+      }, null);
+      if (this.state.mounted) {
+        this.getDOMNode().children[i + 1].children[0].indeterminate = this.props.checkboxes[i].indeterminate;
+      }
+
+
+      input[i] = React.createElement("label", {className: "rw-form__label-checkBox", key:i, readOnly:this.props.readonly, disabled:this.props.disabled}, checkbox[i], this.props.checkboxes[i].label);
+
+    }
+
+    // The "if required" element. It displays a label if the element is required.
+    if(this.props.isRequired == true){
+      var requiredEle = React.createElement("small", {className: "rw-form__required-label"}, this.state.requiredText);
+    }
+
+    // The "error" element.  It pops up as a message if there is an error with the input.
+    if(this.state.errorText != "") {
+      var error = React.createElement("p", {className: "rw-form__message-error"}, this.state.errorText);
+    }
+
+    // The "instruction" element.
+    if(this.state.instructionsText != ""){
+      var instructions = React.createElement("p", {className: "rw-form-instructions"}, this.state.instructionsText)
+    }
+
+    // The parent element for all.
+    var ret = React.createElement("div", {
+      "data-state":      input_state,
+      required:          this.state.isRequired,
+      disabled:          this.state.isDisabled,
+      readOnly:          this.state.isReadOnly,
+      className:         "rw-form"
+
+    }, requiredEle, label, input, error, instructions);
+
+    return ret;
+  }
+});
diff --git a/skyquake/framework/widgets/text-input/check-box/rw.check-box2.js b/skyquake/framework/widgets/text-input/check-box/rw.check-box2.js
new file mode 100644
index 0000000..cc8ea87
--- /dev/null
+++ b/skyquake/framework/widgets/text-input/check-box/rw.check-box2.js
@@ -0,0 +1,228 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+var React = require('react');
+var ButtonEventListenerMixin = require('../mixins/ButtonEventListener.js');
+
+
+/**
+ *  A check-box component.
+ *  It's props values and a brief description below
+ *
+ *  label:        The label for the checkbox group.
+ *  checkboxes:   The object that creates each checkbox.  Each object has a property "label" and "checked".
+ *    label:        The label for the individual checkbox.
+ *    checked:      If set to true, the individual checkbox is initialized with a check.
+ *  requiredText: The text content of the "if required" message.
+ *  errorText:    The text content of the error message.
+ *  ClassName:    Css Classes applied to the element.
+ *  size:         The size of the element.
+ *  isRequired:   A boolean indicating whether or not the input is required.
+ *  isDisabled:   A boolean indicating the disabled state of the element.
+ *  isReadOnly:   A boolean indicating whether or not the input is read only.
+ *  instructions: The text content of the instructions
+ **/
+module.exports = React.createClass({
+  displayName: "CheckBox",
+  mixins:[ButtonEventListenerMixin],
+  propTypes: {
+    children:        React.PropTypes.arrayOf(React.PropTypes.object),
+    label:           React.PropTypes.string,
+    checked:         React.PropTypes.bool,
+    indeterminate:   React.PropTypes.bool,
+    requiredText:    React.PropTypes.string,
+    instructionText: React.PropTypes.string,
+    errorText:       React.PropTypes.string,
+    className:       React.PropTypes.string,
+    size:            React.PropTypes.string,
+    isRequired:      React.PropTypes.bool,
+    isDisabled:      React.PropTypes.bool,
+    isReadOnly:      React.PropTypes.bool
+  },
+
+
+  /**
+   * Sets the default input state.
+   * If there is no description for the variable, assume it's the same as it's props counterpart.
+   *
+   * isActive: Boolean to indicate if input is active.
+   * isHovered: Boolean to indicate if the input is being hovered over.
+   * isFocused: Boolean to indicate if the input has been focused.
+   * isDisabled: Boolean to indicate if input has been disabled.
+   *
+   * @returns {{sizeOfButton: (*|string), isActive: boolean, isHovered: boolean, isFocused: boolean, isDisabled: (*|boolean),
+   * isReadOnly: (*|.textInput.isReadOnly|.exports.propTypes.isReadOnly|.exports.getInitialState.isReadOnly|boolean),
+   * isRequired: (*|.textInput.isRequired|.exports.propTypes.isRequired|.exports.getInitialState.isRequired|isRequired|null),
+   * isValid: null, isSuccess: null}}
+   */
+  getInitialState: function() {
+
+    return {
+      //mounted:              false,
+      label:                this.props.label || "",
+      requiredText:         this.props.requiredText || "Required",
+      instructionsText:     this.props.instructions || "",
+      errorText:            this.props.errorText || "",
+      size:                 this.props.size || '',
+      isActive:             false,
+      isHovered:            false,
+      isFocused:            false,
+      isDisabled:           this.props.disabled || false,
+      isReadOnly:           this.props.isReadOnly || false,
+      isRequired:           this.props.isRequired || null,
+      isValid:              null,      // Three way bool. Valid: true.   Invalid: false. Not acted on: null.
+      isSuccess:            null       // Three way bool. Success: true. Error: false.   Not acted on: null.
+    }
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * "nextProps" and "nextState" hold the state and property variables as they will be after the update.
+   * "this.props" and "this.state" hold the state and property variables as they were before the update.
+   * returns true if the state have changed. Otherwise returns false.
+   * @param nextProps
+   * @param nextState
+   * @returns {boolean}
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = this.state.isReadOnly + this.state.isDisabled + this.state.isActive + this.state.isFocused +
+      this.state.isHovered + this.state.isValid + this.state.isSuccess;
+    var nextStateString = nextState.isReadOnly + nextState.isDisabled + nextState.isActive + nextState.isFocused +
+      nextState.isHovered + nextState.isValid + nextState.isSuccess;
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    return true;
+  },
+
+  componentDidMount: function() {
+    this.state.mounted = true;
+    this.render();
+  },
+
+  /**
+   * Returns a string reflecting the current state of the input.
+   * If the component state "isDisabled" is true, returns a string "disabled".
+   * If the component state "isReadOnly" is true, returns a string "readonly".
+   * Otherwise returns a string containing a word for each state that has been set to true.
+   * (ie "active focused" if the states active and focused are true, but hovered is false).
+   * @returns {string}
+   */
+  setComponentState: function() {
+
+    var ret = "";
+    if (this.state.isDisabled) {
+      return "disabled";
+    }
+    if (this.state.isReadOnly) {
+      return "readonly";
+    }
+    if (this.state.isActive) {
+      ret += "active ";
+    }
+    if (this.state.isHovered) {
+      ret += "hovered ";
+    }
+    if (this.state.isFocused) {
+      ret += "focused ";
+    }
+    return ret;
+  },
+
+  localOnClick: function(e) {
+    this.onClick(e);
+  },
+
+  /**
+   * Renders the checkbox component.
+   *
+   **/
+  render: function() {
+    var input = [];
+    var checkbox = [];
+    var label = [];
+    var input_style = {};
+    var input_state = this.setComponentState();
+
+    // The group label element
+    label = React.createElement("label", {className: "rw-form__label"}, this.props.label);
+
+    // Creates each individual checkbox element and the label for each.
+    for (var i = 0; i < this.props.checkboxes.length; i++) {
+      checkbox[i] = React.createElement("input",{
+        key:               i,
+        defaultChecked:    this.props.checkboxes[i].checked,
+        //indeterminate: true,
+        type:              "checkbox",
+        readOnly:          this.props.readonly,
+        disabled:          this.props.disabled,
+        onClick:           this.localOnClick,
+        onMouseUp:         this.onMouseUp,
+        onMouseDown:       this.onMouseDown,
+        onMouseOver:       this.onMouseOver,
+        onMouseEnter:      this.onMouseEnter,
+        onMouseLeave:      this.onMouseLeave,
+        onMouseOut:        this.onMouseOut,
+        onTouchCancel:     this.onTouchCancel,
+        onTouchEnd:        this.onTouchEnd,
+        onTouchMove:       this.onTouchMove,
+        onTouchStart:      this.onTouchStart,
+        onKeyDown:         this.onKeyDown,
+        onKeyPress:        this.onKeyPress,
+        onKeyUp:           this.onKeyUp,
+        onFocus:           this.onFocus,
+        onBlur:            this.onBlur
+      }, null);
+      if (this.state.mounted) {
+        this.getDOMNode().children[i + 1].children[0].indeterminate = this.props.checkboxes[i].indeterminate;
+      }
+
+
+      input[i] = React.createElement("label", {className: "rw-form__label-checkBox", key:i, readOnly:this.props.readonly, disabled:this.props.disabled}, checkbox[i], this.props.checkboxes[i].label);
+
+    }
+
+    // The "if required" element. It displays a label if the element is required.
+    if(this.props.isRequired == true){
+      var requiredEle = React.createElement("small", {className: "rw-form__required-label"}, this.state.requiredText);
+    }
+
+    // The "error" element.  It pops up as a message if there is an error with the input.
+    if(this.state.errorText != "") {
+      var error = React.createElement("p", {className: "rw-form__message-error"}, this.state.errorText);
+    }
+
+    // The "instruction" element.
+    if(this.state.instructionsText != ""){
+      var instructions = React.createElement("p", {className: "rw-form-instructions"}, this.state.instructionsText)
+    }
+
+    // The parent element for all.
+    var ret = React.createElement("div", {
+      "data-state":      input_state,
+      required:          this.state.isRequired,
+      disabled:          this.state.isDisabled,
+      readOnly:          this.state.isReadOnly,
+      className:         "rw-form"
+
+    }, requiredEle, label, input, error, instructions);
+
+    return ret;
+  }
+});
diff --git a/skyquake/framework/widgets/text-input/rw.text-input.js b/skyquake/framework/widgets/text-input/rw.text-input.js
new file mode 100644
index 0000000..62e1bcc
--- /dev/null
+++ b/skyquake/framework/widgets/text-input/rw.text-input.js
@@ -0,0 +1,280 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+var React = require('react');
+var ButtonEventListenerMixin = require('../mixins/ButtonEventListener.js');
+var validator = require('validator');
+
+/**
+ *  A text input component.
+ *  It's props values and a brief description below
+ *
+ *  value: Holds the initial text content of the input.
+ *  label: The text content of the label.
+ *  requiredText: The text content of the "if required" message.
+ *  errorText: The text content of the error message.
+ *  placeholder: The initial placeholder text of the input
+ *  ClassName: Css Classes applied to the element.
+ *  size: The size of the element.
+ *  minWidth: Minimum width of the element.
+ *  maxWidth: Maximum width of the element.
+ *  isRequired: A boolean indicating whether or not the input is required.
+ *  isDisabled: A boolean indicating the disabled state of the element.
+ *  isReadOnly: A boolean indicating whether or not the input is read only.
+ *  pattern: A regex putting constraints on what the user input can be.
+ *  maxLength: The hard limit on how many characters can be in the input.
+ **/
+module.exports = React.createClass({
+  displayName: "TextInput",
+  mixins:[ButtonEventListenerMixin],
+  propTypes: {
+    value:           React.PropTypes.string,
+    label:           React.PropTypes.string,
+    requiredText:    React.PropTypes.string,
+    errorText:       React.PropTypes.string,
+    placeholder:     React.PropTypes.string,
+    className:       React.PropTypes.string,
+    size:            React.PropTypes.string,
+    minWidth:        React.PropTypes.number,
+    maxWidth:        React.PropTypes.number,
+    isRequired:      React.PropTypes.bool,
+    isDisabled:      React.PropTypes.bool,
+    isReadOnly:      React.PropTypes.bool,
+    pattern:         React.PropTypes.string,
+    maxLength:       React.PropTypes.number,
+    instructions:    React.PropTypes.string
+  },
+
+
+  /**
+   * Sets the default input state.
+   * If there is no description for the variable, assume it's the same as it's props counterpart.
+   *
+   * value: The current text contents of the input.
+   * isActive: Boolean to indicate if input is active.
+   * isHovered: Boolean to indicate if the input is being hovered over.
+   * isFocused: Boolean to indicate if the input has been focused.
+   * isDisabled: Boolean to indicate if input has been disabled.
+   *
+   * @returns {{sizeOfButton: (*|string), isActive: boolean, isHovered: boolean, isFocused: boolean, isDisabled: (*|boolean),
+   * isReadOnly: (*|.textInput.isReadOnly|.exports.propTypes.isReadOnly|.exports.getInitialState.isReadOnly|boolean),
+   * isRequired: (*|.textInput.isRequired|.exports.propTypes.isRequired|.exports.getInitialState.isRequired|isRequired|null),
+   * isValid: null, isSuccess: null}}
+   */
+  getInitialState: function() {
+    return {
+      value:                this.props.value || '',
+      label:                this.props.label || "",
+      requiredText:         this.props.requiredText || "Required",
+      instructionsText:     this.props.instructions || "",
+      errorText:            "",
+      size:                this.props.size || '',
+      isActive:            false,
+      isHovered:           false,
+      isFocused:           false,
+      isDisabled:     this.props.isDisabled || false,
+      isReadOnly:     this.props.isReadOnly || false,
+      isRequired:     this.props.isRequired || null,
+      isValid:        null,      // Three way bool. Valid: true.   Invalid: false. Not acted on: null.
+      isSuccess:      null       // Three way bool. Success: true. Error: false.   Not acted on: null.
+    }
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * "nextProps" and "nextState" hold the state and property variables as they will be after the update.
+   * "this.props" and "this.state" hold the state and property variables as they were before the update.
+   * returns true if the state have changed. Otherwise returns false.
+   * @param nextProps
+   * @param nextState
+   * @returns {boolean}
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = this.state.isReadOnly + this.state.isDisabled + this.state.isActive + this.state.isFocused +
+      this.state.isHovered + this.state.isValid + this.state.isSuccess + this.state.value;
+    var nextStateString = nextState.isReadOnly + nextState.isDisabled + nextState.isActive + nextState.isFocused +
+      nextState.isHovered + nextState.isValid + nextState.isSuccess + nextState.value;
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    return true;
+  },
+
+  /**
+   * Makes sure that when the user types new input, the contents of the input changes accordingly.
+   *
+   * @param event
+   */
+  handleChange: function(event) {
+    this.setState({value:event.target.value});
+  },
+
+  /**
+   * Makes sure that when the user types new input, the contents of the input changes accordingly.
+   *
+   * @param event
+   */
+  handleValidation: function(event) {
+    if (this.props.isRequired) {
+      if (validator.isNull(event.target.value) || event.target.value == '') {
+        this.setState({
+          errorText: this.props.errorText,
+          isValid: false
+        });
+      } else {
+        this.setState({
+          errorText: '',
+          isValid: true
+        });
+      }
+    }
+    if (this.props.pattern) {
+      if (!validator.matches(event.target.value, this.props.pattern)) {
+        this.setState({
+          errorText: this.props.errorText,
+          isValid: false
+        });
+      } else {
+        this.setState({
+          errorText: '',
+          isValid: true
+        });
+      }
+    }
+
+  },
+
+  /**
+   * Returns a string reflecting the current state of the input.
+   * If the component state "isDisabled" is true, returns a string "disabled".
+   * If the component state "isReadOnly" is true, returns a string "readonly".
+   * Otherwise returns a string containing a word for each state that has been set to true.
+   * (ie "active focused" if the states active and focused are true, but hovered is false).
+   * @returns {string}
+   */
+  setComponentState: function() {
+    var ret = "";
+    if (this.state.isDisabled) {
+      return "disabled";
+    }
+    if (this.state.isReadOnly) {
+      return "readonly";
+    }
+    if (this.state.isActive) {
+      ret += "active ";
+    }
+    if (this.state.isHovered) {
+      ret += "hovered ";
+    }
+    if (this.state.isFocused) {
+      ret += "focused ";
+    }
+    return ret;
+  },
+
+  /**
+   * Renders the Text Input component.
+   *
+   **/
+  render: function() {
+    var value = this.state.value;
+    var input = null;
+    var input_style = {};
+    var input_state = this.setComponentState();
+
+    // The following if statements translates the min/max width from a number into a string.
+    if (this.props.minWidth) {
+      input_style['min-width'] = String(this.props.minWidth) + 'px';
+    }
+    if (this.props.maxWidth) {
+      input_style['max-width'] = String(this.props.maxWidth) + 'px';
+    }
+
+    // The input element.
+    input = React.createElement("input", {
+        ref:               "inputRef",
+        "data-state":      input_state,
+        "data-validate":   this.state.isValid,
+        value:             value,
+        placeholder:       this.props.placeholder,
+        pattern:           this.props.pattern,
+        maxLength:         this.props.maxLength,
+        required:          this.state.isRequired,
+        disabled:          this.state.isDisabled,
+        readOnly:          this.state.isReadOnly,
+        onChange:          this.handleChange,
+        onClick:           this.onClick,
+        onMouseUp:         this.onMouseUp,
+        onMouseDown:       this.onMouseDown,
+        onMouseOver:       this.onMouseOver,
+        onMouseEnter:      this.onMouseEnter,
+        onMouseLeave:      this.onMouseLeave,
+        onMouseOut:        this.onMouseOut,
+        onTouchCancel:     this.onTouchCancel,
+        onTouchEnd:        this.onTouchEnd,
+        onTouchMove:       this.onTouchMove,
+        onTouchStart:      this.onTouchStart,
+        onKeyDown:         this.onKeyDown,
+        onKeyPress:        this.onKeyPress,
+        onKeyUp:           this.handleValidation,
+        onFocus:           this.onFocus,
+        onBlur:            this.handleValidation,
+        className:         (this.props.className || "rw-textinput"),
+        tabIndex:          0
+      },
+      null
+    );
+
+    // The "if required" element. It displays a label if the element is required.
+    if(this.props.isRequired == true){
+      var requiredEle = React.createElement("small", {className: "rw-form__required-label"}, this.state.requiredText);
+    }
+
+    //
+    if(this.state.isValid == false) {
+      var validations  = React.createElement("svg", {className: "rw-form__icon"}, this.state.errorText);
+    }
+    // The label element associated with the input.
+    var label = React.createElement("label", {className: "rw-form__label"}, this.state.label, requiredEle, input, validations);
+
+    // The "error" element.  It pops up as a message if there is an error with the input.
+    if(this.state.errorText != "") {
+      var error = React.createElement("p", {className: "rw-form__message-error"}, this.state.errorText);
+    }
+
+    //
+    if(this.state.instructionsText != ""){
+      var instructions = React.createElement("p", {className: "rw-form__instructions"}, this.state.instructionsText)
+    }
+
+
+
+    // The parent element for all.
+    var ret = React.createElement("div", {
+      "data-state":      input_state,
+      required:          this.state.isRequired,
+      disabled:          this.state.isDisabled,
+      readOnly:          this.state.isReadOnly,
+      "data-validate":   this.state.isValid,
+      className:         "rw-form"
+    }, label, error, instructions);
+
+    return ret;
+  }
+});
diff --git a/skyquake/framework/widgets/topology/topologyL2Graph.jsx b/skyquake/framework/widgets/topology/topologyL2Graph.jsx
new file mode 100644
index 0000000..fe4360b
--- /dev/null
+++ b/skyquake/framework/widgets/topology/topologyL2Graph.jsx
@@ -0,0 +1,253 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import d3 from 'd3';
+import DashboardCard from '../dashboard_card/dashboard_card.jsx';
+
+export default class TopologyL2Graph extends React.Component {
+    constructor(props) {
+        super(props);
+        this.data = props.data;
+        this.selectedID = 0;
+        this.nodeCount = 0;
+        this.network_coding = {}
+        this.nodeEvent = props.nodeEvent || null;
+    }
+    componentDidMount(){
+        var weight = 400;
+        var handler = this;
+        this.force = d3.layout.force()
+            .size([this.props.width, this.props.height])
+            .charge(-weight)
+            .linkDistance(weight)
+            .on("tick", this.tick.bind(this));
+
+        this.drag = this.force.drag()
+            .on("dragstart", function(d) {
+                handler.dragstart(d, handler);
+            });
+
+    }
+    componentWillUnmount() {
+        d3.select('svg').remove();
+    }
+    componentWillReceiveProps(props) {
+        if(!this.svg) {
+            // NOTE: We may need to revisit how D3 accesses DOM elements
+             this.svg = d3.select(document.querySelector('#topologyL2')).append("svg")
+            .attr("width", this.props.width)
+            .attr("height", this.props.height)
+            .classed("topology", true);
+        }
+
+        if (props.data.links.length > 0) {
+            this.network_coding = this.create_group_coding(props.data.network_ids.sort());
+            this.update(props.data);
+        }
+    }
+
+    create_group_coding(group_ids) {
+        var group_coding = {};
+        group_ids.forEach(function(element, index, array) {
+            group_coding[element] = index+1;
+        });
+        return group_coding;
+    }
+    getNetworkCoding(network_id) {
+        var group = this.network_coding[network_id];
+        if (group != undefined) {
+            return group;
+        } else {
+            return 0;
+        }
+    }
+
+    drawLegend(graph) {
+        // Hack to prevent multiple legends being displayed
+        this.svg.selectAll(".legend").remove();
+
+        var showBox = false;
+        var svg = this.svg;
+        var item_count = (graph.network_ids) ? graph.network_ids.length : 0;
+        var pos = {
+            anchorX: 5,
+            anchorY: 5,
+            height: 40,
+            width: 200,
+            items_y: 35,
+            items_x: 7,
+            item_height: 25
+        };
+        pos.height += item_count * pos.item_height;
+        var legend_translate = "translate("+pos.anchorX+","+pos.anchorY+")";
+
+        var legend = svg.append("g")
+            .attr("class", "legend")
+            .attr("transform", legend_translate);
+
+        var legend_box = (showBox) ? legend.append("rect")
+                .attr("x", 0)
+                .attr("y", 0)
+                .attr("height", pos.height)
+                .attr("width", pos.width)
+                .style("stroke", "black")
+                .style("fill", "none") : null;
+
+        legend.append("text")
+            .attr("x", 5)
+            .attr("y", 15)
+            .text("Network color mapping:");
+
+        legend.selectAll("g").data(graph.network_ids)
+            .enter()
+            .append("g")
+            .each(function(d, i) {
+                var colors = ["green", "orange", "red" ];
+                var g = d3.select(this);
+                var group_number = i+1;
+                g.attr('class', "node-group-" + group_number);
+
+                g.append("circle")
+                    .attr("cx", pos.items_x + 3)
+                    .attr("cy", pos.items_y + i * pos.item_height)
+                    .attr("r", 6);
+
+                g.append("text")
+                    .attr("x", pos.items_x + 25)
+                    .attr("y", pos.items_y + (i * pos.item_height + 4))
+                    .attr("height", 20)
+                    .attr("width", 80)
+                    .text(d);
+            })
+    }
+
+    update(graph) {
+        var svg = this.svg;
+        var handler = this;
+        this.force
+            .nodes(graph.nodes)
+            .links(graph.links)
+            .start();
+
+        this.link = svg.selectAll(".link")
+            .data(graph.links)
+            .enter().append("line")
+                .attr("class", "link");
+
+        this.gnodes = svg.selectAll('g.gnode')
+            .data(graph.nodes)
+            .enter()
+            .append('g')
+            .classed('gnode', true)
+            .attr('data-network', function(d) { return d.network; })
+            .attr('class', function(d) {
+                return d3.select(this).attr('class') + ' node-group-'+ handler.getNetworkCoding(d.network);
+            });
+
+        this.node = this.gnodes.append("circle")
+            .attr("class", "node")
+            .attr("r", this.props.radius)
+            .on("dblclick", function(d) {
+                handler.dblclick(d, handler)
+            })
+            .call(this.drag)
+            .on('click', function(d) {
+                handler.click.call(this, d, handler)
+            });
+        var labels = this.gnodes.append("text")
+            .attr("text-anchor", "middle")
+            .attr("fill", "black")
+            .attr("font-size", "12")
+            .attr("y", "-10")
+            .text(function(d) { return d.name; });
+        this.drawLegend(graph);
+    }
+
+    tick = () => {
+        this.link.attr("x1", function(d) { return d.source.x; })
+               .attr("y1", function(d) { return d.source.y; })
+               .attr("x2", function(d) { return d.target.x; })
+               .attr("y2", function(d) { return d.target.y; });
+
+        this.gnodes.attr("transform", function(d) {
+            return 'translate(' + [d.x, d.y] + ')';
+        });
+
+    }
+
+    click(d, topo) {
+        console.log("TopologyL2Graph.click called");
+        // 'This' is the svg circle element
+        var gnode = d3.select(this.parentNode);
+
+        topo.svg.selectAll("text").transition()
+            .duration(topo.props.nodeText.transitionTime)
+            .attr("font-size", topo.props.nodeText.size)
+            .attr("fill", topo.props.nodeText.color)
+
+        // Set focus node text properties
+        d3.select(this.parentNode).selectAll('text').transition()
+            .duration(topo.props.nodeText.transitionTime)
+            .attr("font-size", topo.props.nodeText.focus.size)
+            .attr("fill", topo.props.nodeText.focus.color);
+
+        // Perform detail view
+        topo.selectedID = d.id;
+        if (topo.nodeEvent) {
+            topo.nodeEvent(d.id);
+        }
+        // set record view as listener
+    }
+
+    dblclick(d, topo) {
+        this.d3.select(this).classed("fixed", d.fixed = false);
+    }
+
+    dragstart(d) {
+        //d3.select(this).classed("fixed", d.fixed = true);
+    }
+
+    render() {
+        return ( <DashboardCard showHeader={true} title="Topology L2 Graph">
+                <div id="topologyL2"></div>
+                </DashboardCard>)
+    }
+}
+
+TopologyL2Graph.defaultProps = {
+    width: 700,
+    height: 500,
+    maxLabel: 150,
+    duration: 500,
+    radius: 6,
+    data: {
+        nodes: [],
+        links: [],
+        network_ids: []
+    },
+    nodeText: {
+        size: 12,
+        color: 'black',
+        focus: {
+            size: 14,
+            color: 'blue'
+        },
+        transitionTime: 250
+    }
+}
diff --git a/skyquake/framework/widgets/topology/topologyTree.jsx b/skyquake/framework/widgets/topology/topologyTree.jsx
new file mode 100644
index 0000000..5e0d895
--- /dev/null
+++ b/skyquake/framework/widgets/topology/topologyTree.jsx
@@ -0,0 +1,256 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import ReactDOM from 'react-dom';
+import d3 from 'd3';
+import DashboardCard from '../dashboard_card/dashboard_card.jsx';
+import _ from 'lodash';
+import $ from 'jquery';
+import './topologyTree.scss';
+
+export default class TopologyTree extends React.Component {
+    constructor(props) {
+        super(props);
+        this.data = props.data;
+        this.selectedID = 0;
+        this.nodeCount = 0;
+        this.size = this.wrapperSize();
+        this.tree = d3.layout.tree()
+            .size([this.props.treeHeight, this.props.treeWidth]);
+        this.diagonal = d3.svg.diagonal()
+            .projection(function(d) { return [d.y, d.x]; });
+        this.svg = null;
+    }
+    componentWillReceiveProps(props) {
+        let self = this;
+        if(!this.svg) {
+            let zoom = d3.behavior.zoom()
+                .translate([this.props.maxLabel, 0])
+                .scaleExtent([this.props.minScale, this.props.maxScale])
+                .on("zoom", self.zoom);
+            let svg = this.selectParent().append("svg")
+                    .attr("width", this.size.width)
+                    .attr("height", this.size.height)
+                .append("g")
+                    .call(zoom)
+                .append("g")
+                    .attr("transform", "translate(" + this.props.maxLabel + ",0)");
+
+            svg.append("rect")
+                .attr("class", "overlay")
+                .attr("width", this.size.width)
+                .attr("height", this.size.height);
+            // this.svg = d3.select()
+            this.svg = svg;
+            this.props.selectNode(props.data);
+        }
+        if(props.data.hasOwnProperty('type') && !this.props.hasSelected) {
+            this.selectedID = props.data.id;
+            //Commenting out to prevent transmitter push error
+            //this.props.selectNode(props.data);
+        }
+        if(this.svg) {
+          this.update(_.cloneDeep(props.data));
+          // this.selectedID = props.data.id;
+        }
+    }
+
+    wrapperSize() {
+        if (this.props.useDynamicWrapperSize) {
+            try {
+                let wrapper = $(".topologyTreeGraph-body");
+
+                return {
+                    width: wrapper.width(),
+                    height: wrapper.height()
+                }
+            } catch (e) {
+                console.log("ERROR: cannot get width and/or height from element."+
+                    " Using props for width and height. e=", e);
+                return {
+                    width: this.props.width,
+                    height: this.props.height
+                }
+            }
+        } else {
+            return {
+                width: this.props.width,
+                height: this.props.height
+            }
+        }
+    }
+    selectParent() {
+        return d3.select(document.querySelector('#topology'));
+    }
+    computeRadius(d) {
+        // if(d.parameters && d.parameters.vcpu) {
+        //     return this.props.radius + d.parameters.vcpu.total;
+        // } else {
+            return this.props.radius;
+        // }
+    }
+    click = (d) => {
+        this.props.selectNode(d);
+        this.selectedID = d.id;
+        // if (d.children){
+        //     d._children = d.children;
+        //     d.children = null;
+        // }
+        // else{
+        //     d.children = d._children;
+        //     d._children = null;
+        // }
+        // this.update(d);
+    }
+    zoom = () => {
+        this.svg.attr("transform", "translate(" + d3.event.translate +
+            ")scale(" + d3.event.scale + ")");
+    }
+    update = (source) => {
+        // Compute the new tree layout.
+        var svg = this.svg;
+        var nodes = this.tree.nodes(source).reverse();
+        var links = this.tree.links(nodes);
+        var self = this;
+
+        // Normalize for fixed-depth.
+        nodes.forEach(function(d) { d.y = d.depth * self.props.maxLabel; });
+        // Update the nodes…
+        var node = svg.selectAll("g.node")
+            .data(nodes, function(d){
+                return d.id || (d.id = ++self.nodeCount);
+            });
+        // Enter any new nodes at the parent's previous position.
+        var nodeEnter = node.enter()
+            .append("g")
+            .attr("class", "node")
+            .attr("transform", function(d){
+                return "translate(" + source.y0 + "," + source.x0 + ")"; })
+            .on("click", this.click);
+
+        nodeEnter.append("circle")
+            .attr("r", 0)
+            .style("fill", function(d){
+                return d._children ? "lightsteelblue" : "white";
+            });
+
+        nodeEnter.append("text")
+            .attr("x", function(d){
+                var spacing = self.computeRadius(d) + 5;
+                    return d.children || d._children ? -spacing : spacing; })
+            .attr("transform", function(d, i) {
+                    return "translate(0," + ((i%2) ? 15 : -15) + ")"; })
+            .attr("dy", "3")
+            .attr("text-anchor", function(d){
+                    return d.children || d._children ? "end" : "start";
+                })
+            .text(function(d){ return d.name; })
+            .style("fill-opacity", 0);
+
+        // Transition nodes to their new position.
+        var nodeUpdate = node
+            .transition()
+            .duration(this.props.duration)
+            .attr("transform", function(d) {
+                return "translate(" + d.y + "," + d.x + ")"; });
+
+        nodeUpdate.select("circle")
+            .attr("r", function(d){ return self.computeRadius(d); })
+            .style("fill", function(d) {
+                return d.id == self.selectedID ? "green" : "lightsteelblue"; });
+
+        nodeUpdate.select("text")
+            .style("fill-opacity", 1)
+            .style("font-weight", function(d) {
+                return d.id == self.selectedID ? "900" : "inherit";
+            })
+            .attr("transform", function(d, i) {
+                return "translate(0," + ((i%2) ? 15 : -15) + ")" +
+                    (d.id == self.selectedID ? "scale(1.125)" : "scale(1)");
+            });
+
+        // Transition exiting nodes to the parent's new position.
+        var nodeExit = node.exit()
+            .transition()
+            .duration(this.props.duration)
+            .attr("transform", function(d) {
+                return "translate(" + source.y + "," + source.x + ")"; })
+            .remove();
+
+        nodeExit.select("circle").attr("r", 0);
+        nodeExit.select("text").style("fill-opacity", 0);
+
+        // Update the links…
+        var link = svg.selectAll("path.link")
+            .data(links, function(d){ return d.target.id; });
+
+        // Enter any new links at the parent's previous position.
+        link.enter().insert("path", "g")
+            .attr("class", "link")
+            .attr("d", function(d){
+                var o = {x: source.x0, y: source.y0};
+                return self.diagonal({source: o, target: o});
+            });
+
+        // Transition links to their new position.
+        link
+            .transition()
+            .duration(this.props.duration)
+            .attr("d", self.diagonal);
+
+        // Transition exiting nodes to the parent's new position.
+        link.exit()
+            .transition()
+            .duration(this.props.duration)
+            .attr("d", function(d){
+                var o = {x: source.x, y: source.y};
+                return self.diagonal({source: o, target: o});
+            })
+            .remove();
+
+        // Stash the old positions for transition.
+        nodes.forEach(function(d){
+            d.x0 = d.x;
+            d.y0 = d.y;
+        });
+    }
+    render() {
+        let html = (
+            <DashboardCard  className="topologyTreeGraph" showHeader={true} title="Topology Tree"
+                headerExtras={this.props.headerExtras} >
+                <div id="topology"></div>
+            </DashboardCard>
+        );
+        return html;
+    }
+}
+
+TopologyTree.defaultProps = {
+    treeWidth: 800,
+    treeHeight: 500,
+    width: 800,
+    height: 800,
+    minScale: 0.5,
+    maxScale: 2,
+    maxLabel: 150,
+    duration: 500,
+    radius: 5,
+    useDynamicWrapperSize: false,
+    data: {}
+}
diff --git a/skyquake/framework/widgets/topology/topologyTree.scss b/skyquake/framework/widgets/topology/topologyTree.scss
new file mode 100644
index 0000000..f975f8e
--- /dev/null
+++ b/skyquake/framework/widgets/topology/topologyTree.scss
@@ -0,0 +1,8 @@
+.topologyTreeGraph {
+    min-width:800px;
+
+    .overlay {
+      fill:none;
+      pointer-events: all;
+    }
+}
diff --git a/skyquake/framework/widgets/transmit-receive/transmit-receive.js b/skyquake/framework/widgets/transmit-receive/transmit-receive.js
new file mode 100644
index 0000000..ff03af1
--- /dev/null
+++ b/skyquake/framework/widgets/transmit-receive/transmit-receive.js
@@ -0,0 +1,104 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require("react");
+var MIXINS = require("../mixins/ButtonEventListener.js");
+
+
+
+
+/**
+ *  Transmit and Receive Component
+ *  It's props values and a brief description below
+ *
+ *
+ *
+ **/
+module.exports = React.createClass({
+  displayName: 'TransmitReceive',
+  mixins:MIXINS,
+  propTypes: {
+    component_data:React.PropTypes.shape({
+      incoming:React.PropTypes.shape({
+        bytes:React.PropTypes.number,
+        packets:React.PropTypes.number,
+        label:React.PropTypes.string, 
+        "byte-rate":React.PropTypes.number,
+        "packet-rate":React.PropTypes.number
+      }),
+      outgoing:React.PropTypes.shape({
+        bytes:React.PropTypes.number,
+        packets:React.PropTypes.number,
+        label:React.PropTypes.string, 
+        "byte-rate":React.PropTypes.number,
+        "packet-rate":React.PropTypes.number
+      })
+    })
+  },
+
+  /**
+   * Defines default state.
+   *
+
+   */
+  getInitialState: function() {
+  },
+
+
+  /**
+   *  Called when props are changed.  Syncs props with state.
+   */
+  componentWillReceiveProps: function(nextProps) {
+
+  },
+
+  /**
+   * Calls the render on the gauge object once the component first mounts
+   */
+  componentDidMount: function() {
+    this.canvasRender(this.state);
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * Note, this is where the render step occures for the gauge object.
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+
+  },
+
+  /**
+   * Renders the Gauge Component
+   * Returns the canvas element the gauge will be housed in.
+   * @returns {*}
+   */
+  render: function() {
+    var gaugeDOM = React.createElement("div", null,
+      React.createElement("canvas",
+        {className: "rwgauge", style:
+          {width:'100%',height:'100%','maxWidth':this.state.width + 'px','maxHeight':'240px'},
+          ref:'gaugeDOM'
+        }
+      )
+    )
+
+
+
+    return gaugeDOM;
+  }
+});
diff --git a/skyquake/framework/widgets/transmit-receive/transmit-receive.jsx b/skyquake/framework/widgets/transmit-receive/transmit-receive.jsx
new file mode 100644
index 0000000..f8ea287
--- /dev/null
+++ b/skyquake/framework/widgets/transmit-receive/transmit-receive.jsx
@@ -0,0 +1,85 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+import './transmit-receive.scss'
+class TransmitReceive extends React.Component {
+  constructor(props) {
+    super(props)
+  }
+  // getDigits(number) {
+  //   return Math.log(number) * Math.LOG10E + 1 | 0;
+  // }
+  getUnits(number) {
+    if (number < Math.pow(10,3)) {
+      return [number, ''];
+    } else if (number < Math.pow(10,6)) {
+      return [(number / Math.pow(10,3)), 'K'];
+    } else if (number < Math.pow(10,9)) {
+      return [(number / Math.pow(10,6)), 'M'];
+    } else if (number < Math.pow(10,12)) {
+      return [(number / Math.pow(10,9)), 'G'];
+    } else if (number < Math.pow(10,15)) {
+      return [(number / Math.pow(10,12)), 'T'];
+    } else if (number < Math.pow(10,18)) {
+      return [(number / Math.pow(10,15)), 'P'];
+    } else if (number < Math.pow(10,21)) {
+      return [(number / Math.pow(10,18)), 'E'];
+    } else if (number < Math.pow(10,24)) {
+      return [(number / Math.pow(10,21)), 'Z'];
+    } else if (number < Math.pow(10,27)) {
+      return [(number / Math.pow(10,24)), 'Z'];
+    } else {
+      return [(number / Math.pow(10,27)), 'Y'];
+    }
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    if (JSON.stringify(this.props.data) === JSON.stringify(nextProps.data)) {
+      return false;
+    }
+    return true;
+  }
+
+  render() {
+    let html;
+
+    html = (
+            <div className="transmit-receive-container">
+              <div className="transmit-container">
+                <div className="label">{this.props.data.outgoing.label}</div>
+                <div className="measure">{this.getUnits(this.props.data.outgoing.bytes)[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.outgoing.bytes)[1]}B</div>
+                <div className="measure">{this.getUnits(this.props.data.outgoing.packets)[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.outgoing.packets)[1]}P</div>
+                <div className="measure">{this.getUnits(this.props.data.outgoing['byte-rate'])[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.outgoing['byte-rate'])[1]}BPS</div>
+                <div className="measure">{this.getUnits(this.props.data.outgoing['packet-rate'])[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.outgoing['packet-rate'])[1]}PPS</div>
+              </div>
+              <div className="receive-container">
+                <div className="label">{this.props.data.incoming.label}</div>
+                <div className="measure">{this.getUnits(this.props.data.incoming.bytes)[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.incoming.bytes)[1]}B</div>
+                <div className="measure">{this.getUnits(this.props.data.incoming.packets)[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.incoming.packets)[1]}P</div>
+                <div className="measure">{this.getUnits(this.props.data.incoming['byte-rate'])[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.incoming['byte-rate'])[1]}BPS</div>
+                <div className="measure">{this.getUnits(this.props.data.incoming['packet-rate'])[0].toFixed(1)}</div><div className="units">{this.getUnits(this.props.data.incoming['packet-rate'])[1]}PPS</div>
+              </div>
+            </div>
+            );
+    return html;
+  }
+}
+
+
+export default TransmitReceive;
diff --git a/skyquake/framework/widgets/transmit-receive/transmit-receive.scss b/skyquake/framework/widgets/transmit-receive/transmit-receive.scss
new file mode 100644
index 0000000..c1336a6
--- /dev/null
+++ b/skyquake/framework/widgets/transmit-receive/transmit-receive.scss
@@ -0,0 +1,49 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+.transmit-receive-container {
+      flex: 1 0;
+      display: flex;
+      flex-direction: row;
+      justify-content: space-around;
+      font-weight:bold;
+      text-align:center;
+      margin: 10px 0px;
+      padding: 0px 10px 0px 10px;
+      background-color:rgb(230,230,230);
+
+      .transmit-container, .receive-container {
+        padding:10px;
+        background-color:rgb(219,219,219);
+      }
+      .transmit-container {
+        margin-right:3px;
+      }
+      .units {
+        font-size:.5rem;
+        margin-bottom:5px;
+
+      }
+      .measure {
+        font-size:1.2rem;
+      }
+      .label {
+        font-size:.8rem;
+        margin-bottom:10px;
+      }
+ }
diff --git a/skyquake/framework/widgets/uptime/uptime.jsx b/skyquake/framework/widgets/uptime/uptime.jsx
new file mode 100644
index 0000000..39b5faf
--- /dev/null
+++ b/skyquake/framework/widgets/uptime/uptime.jsx
@@ -0,0 +1,173 @@
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+import React from 'react';
+
+function debugDump(funcName, state, props) {
+      console.log("UpTime." + funcName + ": called");
+      console.log(" -> props = ", props);
+      console.log(" -> state = ", state);
+    }
+
+/**
+ * Uptime Compoeent
+ * Accepts two properties:
+ * initialtime {number} number of milliseconds.
+ * run {boolean} determines whether the uptime ticker should run
+ */
+export default class UpTime extends React.Component {
+
+    constructor(props) {
+      super(props);
+      if (props.debugMode) {
+        console.log("UpTime.constructor called");
+      }
+      let initialTime = Math.floor(props.initialTime);
+      this.state = {
+        run: props.run,
+        initialTime: initialTime,
+        time: this.handleConvert(this.calculateDuration({
+          intialTime: initialTime
+        })),
+        noisySeconds: props.noisySeconds,
+        debugMode: props.debugMode
+      }
+      this.tick;
+      this.handleConvert = this.handleConvert.bind(this);
+      this.updateTime = this.updateTime.bind(this);
+      if (props.debugMode) {
+        debugDump("constructor", this.state, props);
+      }
+    }
+
+    componentWillReceiveProps(nextProps) {
+      if (this.state.debugMode) {
+        debugDump("componentWillReceiveProps", this.state, nextProps);
+      }
+      let initialTime = Math.floor(nextProps.initialTime);
+      if (initialTime > this.state.initialTime) {
+        initialTime = this.state.initialTime;
+      }
+      this.state = {
+        initialTime: initialTime,
+        time: this.handleConvert(this.calculateDuration({
+          intialTime: initialTime
+        }))
+      }
+    }
+
+    calculateDuration(args={}) {
+      let initialTime = args.initialTime;
+      if (!initialTime) {
+        initialTime =  this.state && this.state.initialTime
+          ? this.state.initialTime
+          : 0;
+      }
+      let timeNow = args.timeNow ? args.timeNow : Date.now();
+      if (initialTime) {
+        return Math.floor((timeNow/ 1000)) - initialTime;
+      } else {
+        return 0;
+      }
+    }
+
+    handleConvert(input) {
+      var ret = {
+        days: 0,
+        hours: 0,
+        minutes: 0,
+        seconds: 0
+      };
+      if (input == "inactive" || typeof(input) === 'undefined') {
+        ret.seconds = -1;
+      } else if (input !== "" && input != "Expired") {
+        input = Math.floor(input);
+        ret.seconds = input % 60;
+        ret.minutes = Math.floor(input / 60) % 60;
+        ret.hours = Math.floor(input / 3600) % 24;
+        ret.days = Math.floor(input / 86400);
+      }
+      return ret;
+    }
+
+    toString() {
+        var self = this;
+        var ret = "";
+        var unitsRendered = [];
+
+        if (self.state.time.days > 0) {
+          ret += self.state.time.days + "d:";
+          unitsRendered.push("d");
+        }
+
+        if (self.state.time.hours > 0 || unitsRendered.length > 0) {
+          ret += self.state.time.hours + "h:";
+          unitsRendered.push("h");
+        }
+
+        if (self.state.time.minutes > 0 || unitsRendered.length > 0) {
+          ret += self.state.time.minutes + "m:";
+          unitsRendered.push("m");
+        }
+
+        if (self.props.noisySeconds || unitsRendered.length == 0
+            || unitsRendered.indexOf('m') == 0)
+        {
+            // console.log(" -> toString adding seconds: ", self.state.time.seconds);
+            ret += self.state.time.seconds + "s";
+        }
+
+        if (ret.endsWith(':')) {
+          ret = ret.slice(0,-1);
+        }
+        if (ret.length > 0) {
+          return ret;
+        } else {
+          return "--";
+        }
+    }
+
+    updateTime() {
+      if (this.state.initialTime) {
+        this.setState({
+          time: this.handleConvert(this.calculateDuration())
+        });
+      }
+    }
+
+    componentDidMount() {
+      var self = this;
+      if (self.state.run) {
+        clearInterval(self.tick);
+        self.tick = setInterval(self.updateTime, 250);
+      }
+    }
+
+    componentWillUnmount() {
+      clearInterval(this.tick);
+    }
+
+    render() {
+      return (<span>{this.toString()}</span>);
+    }
+}
+UpTime.defaultProps = {
+  noisySeconds: false,
+  caller: '',
+  systemId: '' // System identifyer for uptime (e.g.: VM, NS, or process)
+}
+