Merge "add overriding state to navigation"
diff --git a/generator-xos-gui-extension/.gitignore b/generator-xos-gui-extension/.gitignore
new file mode 100644
index 0000000..9ba488b
--- /dev/null
+++ b/generator-xos-gui-extension/.gitignore
@@ -0,0 +1,2 @@
+*.DS_Store
+node_modules/
diff --git a/generator-xos-gui-extension/generators/README.md b/generator-xos-gui-extension/generators/README.md
new file mode 100644
index 0000000..c13ead2
--- /dev/null
+++ b/generator-xos-gui-extension/generators/README.md
@@ -0,0 +1,11 @@
+#generator-xos-gui-extension
+
+## About
+This is a Yeoman generator intended to aid with the development of new XOS GUI Extensions. It generates a new GUI extension
+with only one component. 
+
+## Installation
+Run the following command in this directory and follow the prompts.
+```
+yo xos-gui-extension
+```  
\ No newline at end of file
diff --git a/generator-xos-gui-extension/generators/index.js b/generator-xos-gui-extension/generators/index.js
new file mode 100644
index 0000000..aecfda2
--- /dev/null
+++ b/generator-xos-gui-extension/generators/index.js
@@ -0,0 +1,136 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+'use strict';
+
+var Generator = require('yeoman-generator');
+var Path = require('path');
+
+var extensionName, extensionFolder;
+
+module.exports = Generator.extend({
+
+  prompting: {
+
+    init() {
+      this.log('---------------------------');
+      this.log('XOS GUI Extension Generator');
+      this.log('---------------------------');
+    },
+
+    name() {
+      var done = this.async();
+      this.log(`Your extension will be created in the current working directory, ${this.destinationRoot()}`);
+      this.prompt({
+        type: 'input',
+        name: 'name',
+        message: 'Enter the name of your XOS GUI Extension',
+        default: 'new-gui-extension',
+        filter: (str) => {
+          var newstr = str.replace(' ', '-');
+          return newstr;
+        }
+      }).then((answers) => {
+        extensionName = answers.name;
+        done();
+      });
+    },
+  },
+
+  writing: {
+
+    noTmplRoot() {
+      this.log('Creating non-templated files in the extension root...');
+      this.fs.copy(this.templatePath('.dockerignore'), this.destinationPath(`${extensionName}/.dockerignore`));
+      this.fs.copy(this.templatePath('.gitignore'), this.destinationPath(`${extensionName}/.gitignore`));
+      this.fs.copy(this.templatePath('Dockerfile'), this.destinationPath(`${extensionName}/Dockerfile`));
+      this.fs.copy(this.templatePath('gulpfile.js'), this.destinationPath(`${extensionName}/gulpfile.js`));
+      this.fs.copy(this.templatePath('package.json'), this.destinationPath(`${extensionName}/package.json`));
+      this.fs.copy(this.templatePath('tsconfig.json'), this.destinationPath(`${extensionName}/tsconfig.json`));
+      this.fs.copy(this.templatePath('tslint.json'), this.destinationPath(`${extensionName}/tslint.json`));
+      this.fs.copy(this.templatePath('typings.json'), this.destinationPath(`${extensionName}/typings.json`));
+    },
+
+    tmplRoot() {
+      this.log('Creating templated files in the extension root...');
+      this.fs.copyTpl(
+        this.templatePath('README.md'),
+        this.destinationPath(`${extensionName}/README.md`),
+        {
+          name: extensionName
+        });
+      this.fs.copyTpl(
+        this.templatePath('xos-sample-gui-extension.yaml'),
+        this.destinationPath(`${extensionName}/${extensionName}.yml`),
+        {
+          name: extensionName
+        });
+      this.fs.copyTpl(
+        this.templatePath('Dockerfile'),
+        this.destinationPath(`${extensionName}/Dockerfile`),
+        {
+          name: extensionName
+        });
+    },
+
+    conf() {
+      this.log('Creating conf...');
+      this.fs.copyTpl(
+        this.templatePath('conf'), 
+        this.destinationPath(`${extensionName}/conf`),
+        {
+          name: extensionName
+        });
+    },
+
+    gulp() {
+      this.log('Creating gulp_tasks...');
+      this.fs.copy(this.templatePath('gulp_tasks'), this.destinationPath(`${extensionName}/gulp_tasks`));
+    },
+
+    src() {
+      this.log('Creating src...');
+      this.fs.copyTpl(
+        this.templatePath('src'),
+        this.destinationPath(`${extensionName}/src`),
+        {
+          name: extensionName
+        });
+    }
+  },
+
+  install (){
+    var done = this.async();
+    this.prompt({
+      type: 'confirm',
+      name: 'deps',
+      message: 'Install dependencies?',
+      default: false
+    }).then((answers) => {
+      if(answers.deps){
+        process.chdir(`${extensionName}/`);
+        this.installDependencies({
+          npm: true,
+          bower: false,
+          yarn: false
+        });
+      }
+      done();
+    });
+  }
+
+});
diff --git a/generator-xos-gui-extension/generators/templates/.dockerignore b/generator-xos-gui-extension/generators/templates/.dockerignore
new file mode 100644
index 0000000..de807ff
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+typings
+npm-debug.log
\ No newline at end of file
diff --git a/generator-xos-gui-extension/generators/templates/.gitignore b/generator-xos-gui-extension/generators/templates/.gitignore
new file mode 100644
index 0000000..217665e
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+dist/
+.tmp/
+.idea/
+*.DS_Store
+typings/
diff --git a/generator-xos-gui-extension/generators/templates/Dockerfile b/generator-xos-gui-extension/generators/templates/Dockerfile
new file mode 100644
index 0000000..9670776
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/Dockerfile
@@ -0,0 +1,49 @@
+# <%= name %>
+FROM xosproject/xos-gui-extension-builder:candidate
+
+# Set environment vars
+ENV CODE_SOURCE .
+ENV CODE_DEST /var/www
+ENV VHOST /var/www/dist
+
+# Add the app deps
+COPY ${CODE_SOURCE}/package.json ${CODE_DEST}/package.json
+COPY ${CODE_SOURCE}/typings.json ${CODE_DEST}/typings.json
+
+# Install Deps
+WORKDIR ${CODE_DEST}
+RUN npm install
+RUN npm run typings
+
+# Build the app
+COPY ${CODE_SOURCE}/conf ${CODE_DEST}/conf
+COPY ${CODE_SOURCE}/gulp_tasks ${CODE_DEST}/gulp_tasks
+COPY ${CODE_SOURCE}/src ${CODE_DEST}/src
+COPY ${CODE_SOURCE}/gulpfile.js ${CODE_DEST}/gulpfile.js
+COPY ${CODE_SOURCE}/tsconfig.json ${CODE_DEST}/tsconfig.json
+COPY ${CODE_SOURCE}/tslint.json ${CODE_DEST}/tslint.json
+
+# Label image
+ARG org_label_schema_schema_version=1.0
+ARG org_label_schema_name=xos-tosca
+ARG org_label_schema_version=unknown
+ARG org_label_schema_vcs_url=unknown
+ARG org_label_schema_vcs_ref=unknown
+ARG org_label_schema_build_date=unknown
+ARG org_opencord_vcs_commit_date=unknown
+ARG org_opencord_component_xos_gui_vcs_ref=unknown
+ARG org_opencord_component_xos_gui_vcs_url=unknown
+ARG org_opencord_component_xos_gui_version=unknown
+
+LABEL org.label-schema.schema-version=$org_label_schema_schema_version \
+      org.label-schema.name=$org_label_schema_name \
+      org.label-schema.version=$org_label_schema_version \
+      org.label-schema.vcs-url=$org_label_schema_vcs_url \
+      org.label-schema.vcs-ref=$org_label_schema_vcs_ref \
+      org.label-schema.build-date=$org_label_schema_build_date \
+      org.opencord.vcs-commit-date=$org_opencord_vcs_commit_date \
+      org.opencord.component.xos-gui.vcs-ref=$org_opencord_component_xos_gui_vcs_ref \
+      org.opencord.component.xos-gui.vcs-url=$org_opencord_component_xos_gui_vcs_url \
+      org.opencord.component.xos-gui.version=$org_opencord_component_xos_gui_version
+
+RUN npm run build
diff --git a/generator-xos-gui-extension/generators/templates/README.md b/generator-xos-gui-extension/generators/templates/README.md
new file mode 100644
index 0000000..a2263bb
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/README.md
@@ -0,0 +1,105 @@
+# <%= name %>
+
+This is a temporary README included with the creation of your extension intended to help guide development. Feel free to
+overrwite with your own changes.
+
+If you chose to install dependencies during setup, you can run `npm start` to view the extension in your web browser,
+other wise run `npm install` before `npm start`.
+
+## Installation with platform-install
+
+To install an extension, add the following to the profile manifest `.yml` file intended to be deployed in`build/platform-install/`
+, and deploy with `ansible-playbook -i inventory/{PROFILE_NAME} deploy-xos-playbook.yml`
+
+```
+enabled_gui_extensions:
+  - name: <%= name %>
+    path: orchestration/<%= name %>
+```
+
+_NOTE: the `name` field must match the subdirectory specified in `conf/app/gulp.conf.js` (eg: `dist/extensions/<%= name %>`)_
+
+## Development Tips
+
+### Including Extra Files
+
+Additional necessary files (such as stylesheets or config files) can be added to the profile manifest as follows, with `<%= name %>/src/` as the root.
+
+```yaml
+enabled_gui_extensions:
+  - name: <%= name %>
+    path: orchestration/<%= name %>
+    extra_files:
+      -  app/style/style.css
+```
+
+### Generating config files
+
+During development, you may find it necessary to create separate config files in order to include other files used in
+your extension (such as images). The path to your extension may vary depending on whether you are running it locally 
+(`./xos/extensions/extension-name`) vs. on a container in production (`./extensions/extension-name`).
+
+You can create separate `customconfig.local.js` and `customconfig.production.js` files in the `conf/` folder, and then edit the 
+following portion of the appropriate `webpack.conf.js` file as follows:
+
+```js
+new CopyWebpackPlugin([
+      { from: `./conf/app/app.config.${env}.js`, to: `app.config.js` },
+      { from: `./conf/app/style.config.${brand}.js`, to: `style.config.js` },
+      // add your file here
+      { from: `./conf/app/customconfig.local.js`, to: `customconfig.js`}
+    ])
+```
+
+`webpack.conf.js` will be used in a local development environment, such as when running `npm start`
+
+`webpack-dist.conf.js` will be used in a production container after deploying a profile.
+
+### Handy XOS Components and Services
+
+#### XosNavigationService
+Used to create custom navigation links in the left navigation panel.
+
+#### XosModelStore
+Provides easy access to model ngResources provided by an XOS service. Can be used as follows:
+
+```typescript
+import {Subscription} from 'rxjs/Subscription';
+export class ExampleComponent {
+    static $inject = ['XosModelStore'];
+    public resource;
+    private modelSubscription : Subscription;
+    constructor(
+      private XosModelStore: any,
+    ){}
+    
+    $onInit() {
+        this.modelSubscription = this.XosModelStore.query('SampleModel', '/sampleservice/SampleModels').subscribe(
+          res => {
+            this.resource = res;
+          }
+        );
+    }
+}
+export const exampleComponent : angular.IComponentOptions = {
+  template: require('./example.component.html'),
+  controllerAs: 'vm',
+  controller: ExampleComponent
+}
+```
+
+#### XosKeyboardShortcut
+Allows for the creation of custom user keyboard shortcuts. See the provided `components/demo.ts` as an example.
+
+#### XosComponentInjector
+Allows for the injection of components into the XOS GUI by specifying a target element ID. Useful IDs include:
+* `#dashboard-component-container`: the dashboard as seen on the XOS home
+* `#side-panel-container`: a side panel that can slide out from the right. However, there is also a `XosSidePanel` 
+service that can make development easier.
+
+#### XosSidePanel
+Makes the injection of a custom side panel somewhat easier (no need to specify a target)
+
+#### XosConfirm
+Allows for the creation of confirmation modal dialogs to confirm whether or not to execute a selected action.
+
diff --git a/generator-xos-gui-extension/generators/templates/conf/app/README.md b/generator-xos-gui-extension/generators/templates/conf/app/README.md
new file mode 100755
index 0000000..6c45737
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/app/README.md
@@ -0,0 +1,45 @@
+# XOS-GUI Config
+
+### Note: The configurations defined in this folder are for development only, they are most likely to be overridden by a volume mount defined in `service-profile`
+
+## App Config
+
+This configuration will specify the REST API base URL and the WebSocket address.
+
+```
+angular.module('app')
+  .constant('AppConfig', {
+    apiEndpoint: '/xos/api',
+    websocketClient: '/'
+  });
+
+```
+
+## Style Config
+
+This configuration will contain branding information, such as title, logo and navigation items.
+
+```
+angular.module('app')
+  .constant('StyleConfig', {
+    projectName: 'CORD',
+    favicon: 'cord-favicon.png',
+    background: 'cord-bg.jpg',
+    payoff: 'Your VNF orchestrator',
+    logo: 'cord-logo.png',
+    routes: [
+        {
+            label: 'Slices',
+            state: 'xos.core.slices'
+        },
+        {
+            label: 'Instances',
+            state: 'xos.core.instances'
+        },
+        {
+            label: 'Nodes',
+            state: 'xos.core.nodes'
+        }
+    ]
+});
+```
\ No newline at end of file
diff --git a/generator-xos-gui-extension/generators/templates/conf/app/app.config.dev.js b/generator-xos-gui-extension/generators/templates/conf/app/app.config.dev.js
new file mode 100755
index 0000000..4872661
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/app/app.config.dev.js
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+angular.module('app')
+  .constant('AppConfig', {
+    apiEndpoint: 'http://xos.dev:3000/api',
+    websocketClient: 'http://xos.dev:3000'
+  });
diff --git a/generator-xos-gui-extension/generators/templates/conf/app/app.config.production.js b/generator-xos-gui-extension/generators/templates/conf/app/app.config.production.js
new file mode 100755
index 0000000..de6179f
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/app/app.config.production.js
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+angular.module('app')
+  .constant('AppConfig', {
+    apiEndpoint: '/xos/api',
+    websocketClient: '/'
+  });
diff --git a/generator-xos-gui-extension/generators/templates/conf/app/app.config.test.js b/generator-xos-gui-extension/generators/templates/conf/app/app.config.test.js
new file mode 100755
index 0000000..37034fb
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/app/app.config.test.js
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+angular.module('app')
+  .constant('AppConfig', {
+      apiEndpoint: 'http://xos-test:3000/api',
+      websocketClient: 'http://xos-test:3000'
+  });
diff --git a/generator-xos-gui-extension/generators/templates/conf/app/style.config.cord.js b/generator-xos-gui-extension/generators/templates/conf/app/style.config.cord.js
new file mode 100755
index 0000000..022938b
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/app/style.config.cord.js
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+angular.module('app')
+  .constant('StyleConfig', {
+    projectName: 'CORD',
+    favicon: 'cord-favicon.png',
+    background: 'cord-bg.jpg',
+    payoff: 'Your VNF orchestrator',
+    logo: 'cord-logo.png',
+    routes: [
+        {
+            label: 'Slices',
+            state: 'xos.core.slices'
+        },
+        {
+            label: 'Instances',
+            state: 'xos.core.instances'
+        },
+        {
+            label: 'Nodes',
+            state: 'xos.core.nodes'
+        }
+    ]
+});
diff --git a/generator-xos-gui-extension/generators/templates/conf/app/style.config.opencloud.js b/generator-xos-gui-extension/generators/templates/conf/app/style.config.opencloud.js
new file mode 100755
index 0000000..2c1776e
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/app/style.config.opencloud.js
@@ -0,0 +1,32 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+angular.module('app')
+  .constant('StyleConfig', {
+    projectName: 'OpenCloud',
+    favicon: 'opencloud-favicon.png',
+    background: 'opencloud-bg.jpg',
+    payoff: 'Your OS resource manager',
+    logo: 'opencloud-logo.png',
+    routes: [
+        {
+            label: 'Slices',
+            state: 'xos.core.slices'
+        }
+    ]
+});
diff --git a/generator-xos-gui-extension/generators/templates/conf/browsersync-dist.conf.js b/generator-xos-gui-extension/generators/templates/conf/browsersync-dist.conf.js
new file mode 100755
index 0000000..704b46c
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/browsersync-dist.conf.js
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const conf = require('./gulp.conf');
+
+module.exports = function () {
+  return {
+    server: {
+      baseDir: [
+        conf.paths.dist
+      ]
+    },
+    open: false
+  };
+};
diff --git a/generator-xos-gui-extension/generators/templates/conf/browsersync.conf.js b/generator-xos-gui-extension/generators/templates/conf/browsersync.conf.js
new file mode 100755
index 0000000..7e3fbc2
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/browsersync.conf.js
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const conf = require('./gulp.conf');
+const proxy = require('./proxy');
+
+module.exports = function () {
+  return {
+    server: {
+      baseDir: [
+        conf.paths.tmp,
+        conf.paths.src
+      ],
+      middleware: function (req, res, next) {
+        if (req.url.indexOf('xosapi') !== -1 || req.url.indexOf('xos') !== -1 || req.url.indexOf('socket') !== -1) {
+          proxy.api.web(req, res);
+        }
+        else {
+          next();
+        }
+      }
+    },
+    open: false
+  };
+};
diff --git a/generator-xos-gui-extension/generators/templates/conf/gulp.conf.js b/generator-xos-gui-extension/generators/templates/conf/gulp.conf.js
new file mode 100755
index 0000000..87b6849
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/gulp.conf.js
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+'use strict';
+
+/**
+ *  This file contains the variables used in other gulp files
+ *  which defines tasks
+ *  By design, we only put there very generic config values
+ *  which are used in several places to keep good readability
+ *  of the tasks
+ */
+
+const path = require('path');
+const gutil = require('gulp-util');
+
+exports.ngModule = 'app';
+
+/**
+ *  The main paths of your project handle these with care
+ */
+exports.paths = {
+  src: 'src',
+  dist: 'dist/extensions/<%= name %>',
+  appConfig: 'conf/app',
+  tmp: '.tmp',
+  e2e: 'e2e',
+  tasks: 'gulp_tasks'
+};
+
+exports.path = {};
+for (const pathName in exports.paths) {
+  if (exports.paths.hasOwnProperty(pathName)) {
+    exports.path[pathName] = function pathJoin() {
+      const pathValue = exports.paths[pathName];
+      const funcArgs = Array.prototype.slice.call(arguments);
+      const joinArgs = [pathValue].concat(funcArgs);
+      return path.join.apply(this, joinArgs);
+    };
+  }
+}
+
+/**
+ *  Common implementation for an error handler of a Gulp plugin
+ */
+exports.errorHandler = function (title) {
+  return function (err) {
+    gutil.log(gutil.colors.red(`[${title}]`), err.toString());
+    this.emit('end');
+  };
+};
diff --git a/generator-xos-gui-extension/generators/templates/conf/karma-auto.conf.js b/generator-xos-gui-extension/generators/templates/conf/karma-auto.conf.js
new file mode 100755
index 0000000..f4ce829
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/karma-auto.conf.js
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const conf = require('./gulp.conf');
+const pkg = require('../package.json');
+
+module.exports = function (config) {
+  const configuration = {
+    basePath: '../',
+    singleRun: false,
+    autoWatch: true,
+    logLevel: 'INFO',
+    junitReporter: {
+      outputDir: 'test-reports'
+    },
+    browsers: [
+      'PhantomJS',
+      // 'Chrome'
+    ],
+    frameworks: [
+      'jasmine',
+      'es6-shim'
+    ],
+    files: [
+      'node_modules/es6-shim/es6-shim.js',
+      conf.path.src('index.spec.js'),
+      conf.path.src('**/*.html')
+    ],
+    preprocessors: {
+      [conf.path.src('index.spec.js')]: [
+        'webpack'
+      ],
+      [conf.path.src('**/*.html')]: [
+        'ng-html2js'
+      ]
+    },
+    ngHtml2JsPreprocessor: {
+      stripPrefix: `${conf.paths.src}/`
+    },
+    reporters: ['mocha', 'coverage'],
+    coverageReporter: {
+      type: 'html',
+      dir: 'coverage/'
+    },
+    webpack: require('./webpack-test.conf'),
+    webpackMiddleware: {
+      noInfo: true
+    },
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-junit-reporter'),
+      require('karma-coverage'),
+      require('karma-phantomjs-launcher'),
+      require('karma-chrome-launcher'),
+      require('karma-phantomjs-shim'),
+      require('karma-ng-html2js-preprocessor'),
+      require('karma-webpack'),
+      require('karma-es6-shim'),
+      require('karma-mocha-reporter')
+    ]
+  };
+
+  config.set(configuration);
+};
diff --git a/generator-xos-gui-extension/generators/templates/conf/karma.conf.js b/generator-xos-gui-extension/generators/templates/conf/karma.conf.js
new file mode 100755
index 0000000..c8bc5f5
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/karma.conf.js
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const conf = require('./gulp.conf');
+
+module.exports = function (config) {
+  const configuration = {
+    basePath: '../',
+    singleRun: true,
+    autoWatch: false,
+    logLevel: 'INFO',
+    junitReporter: {
+      outputDir: 'test-reports'
+    },
+    browsers: [
+      'PhantomJS'
+    ],
+    frameworks: [
+      'jasmine',
+      'es6-shim'
+    ],
+    files: [
+      'node_modules/es6-shim/es6-shim.js',
+      conf.path.src('index.spec.js'),
+      conf.path.src('**/*.html')
+    ],
+    preprocessors: {
+      [conf.path.src('index.spec.js')]: [
+        'webpack'
+      ],
+      [conf.path.src('**/*.html')]: [
+        'ng-html2js'
+      ]
+    },
+    ngHtml2JsPreprocessor: {
+      stripPrefix: `${conf.paths.src}/`
+    },
+    reporters: ['progress', 'coverage'],
+    coverageReporter: {
+      type: 'html',
+      dir: 'coverage/'
+    },
+    webpack: require('./webpack-test.conf'),
+    webpackMiddleware: {
+      noInfo: true
+    },
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-junit-reporter'),
+      require('karma-coverage'),
+      require('karma-phantomjs-launcher'),
+      require('karma-phantomjs-shim'),
+      require('karma-ng-html2js-preprocessor'),
+      require('karma-webpack'),
+      require('karma-es6-shim')
+    ]
+  };
+
+  config.set(configuration);
+};
diff --git a/generator-xos-gui-extension/generators/templates/conf/proxy.js b/generator-xos-gui-extension/generators/templates/conf/proxy.js
new file mode 100644
index 0000000..ab11963
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/proxy.js
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const httpProxy = require('http-proxy');
+
+const target = process.env.PROXY || '192.168.46.100';
+
+const apiProxy = httpProxy.createProxyServer({
+  target: `http://${target}`
+});
+
+apiProxy.on('error', (error, req, res) => {
+  res.writeHead(500, {
+    'Content-Type': 'text/plain'
+  });
+  console.error('[Proxy]', error);
+});
+
+module.exports = {
+  api: apiProxy
+};
diff --git a/generator-xos-gui-extension/generators/templates/conf/webpack-dist.conf.js b/generator-xos-gui-extension/generators/templates/conf/webpack-dist.conf.js
new file mode 100755
index 0000000..c870043
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/webpack-dist.conf.js
@@ -0,0 +1,121 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const webpack = require('webpack');
+const conf = require('./gulp.conf');
+const path = require('path');
+
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const pkg = require('../package.json');
+const autoprefixer = require('autoprefixer');
+const BaseHrefWebpackPlugin = require('base-href-webpack-plugin').BaseHrefWebpackPlugin;
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+const env = process.env.NODE_ENV || 'production';
+const brand = process.env.BRAND || 'cord';
+
+module.exports = {
+  module: {
+    loaders: [
+      {
+        test: /.json$/,
+        loaders: [
+          'json'
+        ]
+      },
+      {
+        test: /\.(css|scss)$/,
+        loaders: ExtractTextPlugin.extract({
+          fallbackLoader: 'style',
+          loader: 'css?minimize!sass!postcss'
+        })
+      },
+      {
+        test: /\.ts$/,
+        exclude: /node_modules/,
+        loaders: [
+          'ng-annotate',
+          'ts'
+        ]
+      },
+      {
+        test: /.html$/,
+        loaders: [
+          'html?' + JSON.stringify({
+            attrs: ["img:src", "img:ng-src"]
+          })
+        ]
+      },
+      {
+        test: /\.(png|woff|woff2|eot|ttf|svg|jpg|gif|jpeg)$/,
+        loader: 'url-loader?limit=100000'
+      }
+    ]
+  },
+  plugins: [
+    new CopyWebpackPlugin([
+      { from: `./conf/app/app.config.${env}.js`, to: `app.config.js` },
+      { from: `./conf/app/style.config.${brand}.js`, to: `style.config.js` },
+    ]),
+    new webpack.optimize.OccurrenceOrderPlugin(),
+    new webpack.NoErrorsPlugin(),
+    new HtmlWebpackPlugin({
+      inject: true,
+      template: conf.path.src('index.html')
+    }),
+    new webpack.optimize.UglifyJsPlugin({
+      compress: {unused: true, dead_code: true, warnings: false}, // eslint-disable-line camelcase
+      mangle: false // NOTE mangling was breaking the build
+    }),
+    new ExtractTextPlugin('index-[contenthash].css'),
+    new webpack.optimize.CommonsChunkPlugin({name: 'vendor'}),
+    new webpack.ProvidePlugin({
+      $: "jquery",
+      jQuery: "jquery"
+    }),
+    new BaseHrefWebpackPlugin({
+      baseHref: '/spa/'
+    }),
+  ],
+  postcss: () => [autoprefixer],
+  output: {
+    path: path.join(process.cwd(), conf.paths.dist),
+    publicPath: "/xos/", // enable apache proxying on the head node
+    filename: '[name].js'
+  },
+  resolve: {
+    extensions: [
+      '',
+      '.webpack.js',
+      '.web.js',
+      '.js',
+      '.ts'
+    ]
+  },
+  entry: {
+    app: `./${conf.path.src('index')}`,
+    vendor: Object.keys(pkg.dependencies)
+  },
+  ts: {
+    configFileName: 'tsconfig.json'
+  },
+  tslint: {
+    configuration: require('../tslint.json')
+  }
+};
+
diff --git a/generator-xos-gui-extension/generators/templates/conf/webpack-test.conf.js b/generator-xos-gui-extension/generators/templates/conf/webpack-test.conf.js
new file mode 100755
index 0000000..c0115ab
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/webpack-test.conf.js
@@ -0,0 +1,84 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+module.exports = {
+  module: {
+    preLoaders: [
+      {
+        test: /\.ts$/,
+        exclude: /node_modules/,
+        loader: 'tslint'
+      }
+    ],
+    loaders: [
+      {
+        test: /.json$/,
+        loaders: [
+          'json'
+        ]
+      },
+      {
+        test: /\.ts$/,
+        exclude: /node_modules/,
+        loaders: [
+          'ng-annotate',
+          'ts'
+        ]
+      },
+      {
+        test: /.html$/,
+        loaders: [
+          'html?' + JSON.stringify({
+            attrs: ["img:src", "img:ng-src"]
+          })
+        ]
+      },
+      {
+        test: /\.(css|scss)$/,
+        loaders: [
+          'style',
+          'css',
+          'sass',
+          'postcss'
+        ]
+      },
+      {
+        test: /\.(png|woff|woff2|eot|ttf|svg|jpg|gif|jpeg)$/,
+        loader: 'url-loader?limit=100000'
+      }
+    ]
+  },
+  plugins: [],
+  debug: true,
+  devtool: 'source-map',
+  resolve: {
+    extensions: [
+      '',
+      '.webpack.js',
+      '.web.js',
+      '.js',
+      '.ts'
+    ]
+  },
+  ts: {
+    configFileName: 'tsconfig.json'
+  },
+  tslint: {
+    configuration: require('../tslint.json')
+  }
+};
diff --git a/generator-xos-gui-extension/generators/templates/conf/webpack.conf.js b/generator-xos-gui-extension/generators/templates/conf/webpack.conf.js
new file mode 100755
index 0000000..d70e771
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/conf/webpack.conf.js
@@ -0,0 +1,117 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const webpack = require('webpack');
+const conf = require('./gulp.conf');
+const path = require('path');
+
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const autoprefixer = require('autoprefixer');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+const env = process.env.NODE_ENV || 'production';
+const brand = process.env.BRAND || 'cord';
+
+module.exports = {
+  module: {
+    preLoaders: [
+      {
+        test: /\.ts$/,
+        exclude: /node_modules/,
+        loader: 'tslint'
+      }
+    ],
+
+    loaders: [
+      {
+        test: /.json$/,
+        loaders: [
+          'json'
+        ]
+      },
+      {
+        test: /\.(css|scss)$/,
+        loaders: [
+          'style',
+          'css',
+          'sass',
+          'postcss'
+        ]
+      },
+      {
+        test: /\.ts$/,
+        exclude: /node_modules/,
+        loaders: [
+          'ng-annotate',
+          'ts'
+        ]
+      },
+      {
+        test: /.html$/,
+        loaders: [
+          'html?' + JSON.stringify({
+            attrs: ["img:src", "img:ng-src"]
+          })
+        ]
+      },
+      {
+        test: /\.(png|woff|woff2|eot|ttf|svg|jpg|gif|jpeg)$/,
+        loader: 'url-loader?limit=100000'
+      }
+    ]
+  },
+  plugins: [
+    new CopyWebpackPlugin([
+      { from: `./conf/app/app.config.${env}.js`, to: `app.config.js` },
+      { from: `./conf/app/style.config.${brand}.js`, to: `style.config.js` },
+    ]),
+    new webpack.optimize.OccurrenceOrderPlugin(),
+    new webpack.NoErrorsPlugin(),
+    new HtmlWebpackPlugin({
+      template: conf.path.src('index.html')
+    })
+  ],
+  postcss: () => [autoprefixer],
+  debug: true,
+  devtool: 'source-map',
+  output: {
+    path: path.join(process.cwd(), conf.paths.tmp),
+    filename: 'index.js'
+  },
+  resolve: {
+    extensions: [
+      '',
+      '.webpack.js',
+      '.web.js',
+      '.js',
+      '.ts'
+    ]
+  },
+  entry: `./${conf.path.src('index')}`,
+  ts: {
+    configFileName: 'tsconfig.json'
+  },
+  tslint: {
+    configuration: require('../tslint.json')
+  },
+  stats: {
+    colors: true,
+    modules: true,
+    reasons: true,
+    errorDetails: true
+  }
+};
diff --git a/generator-xos-gui-extension/generators/templates/gulp_tasks/browsersync.js b/generator-xos-gui-extension/generators/templates/gulp_tasks/browsersync.js
new file mode 100755
index 0000000..0e4ada2
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/gulp_tasks/browsersync.js
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const gulp = require('gulp');
+const browserSync = require('browser-sync');
+const spa = require('browser-sync-spa');
+
+const browserSyncConf = require('../conf/browsersync.conf');
+const browserSyncDistConf = require('../conf/browsersync-dist.conf');
+
+browserSync.use(spa());
+
+gulp.task('browsersync', browserSyncServe);
+gulp.task('browsersync:dist', browserSyncDist);
+
+function browserSyncServe(done) {
+  browserSync.init(browserSyncConf());
+  done();
+}
+
+function browserSyncDist(done) {
+  browserSync.init(browserSyncDistConf());
+  done();
+}
diff --git a/generator-xos-gui-extension/generators/templates/gulp_tasks/karma.js b/generator-xos-gui-extension/generators/templates/gulp_tasks/karma.js
new file mode 100755
index 0000000..9955d1a
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/gulp_tasks/karma.js
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const path = require('path');
+
+const gulp = require('gulp');
+const karma = require('karma');
+
+gulp.task('karma:single-run', karmaSingleRun);
+gulp.task('karma:auto-run', karmaAutoRun);
+
+function karmaFinishHandler(done) {
+  return failCount => {
+    done(failCount ? new Error(`Failed ${failCount} tests.`) : null);
+  };
+}
+
+function karmaSingleRun(done) {
+  process.env.NODE_ENV = 'test';
+  const configFile = path.join(process.cwd(), 'conf', 'karma.conf.js');
+  const karmaServer = new karma.Server({configFile}, karmaFinishHandler(done));
+  karmaServer.start();
+}
+
+function karmaAutoRun(done) {
+  process.env.NODE_ENV = 'test';
+  const configFile = path.join(process.cwd(), 'conf', 'karma-auto.conf.js');
+  const karmaServer = new karma.Server({configFile}, karmaFinishHandler(done));
+  karmaServer.start();
+}
diff --git a/generator-xos-gui-extension/generators/templates/gulp_tasks/misc.js b/generator-xos-gui-extension/generators/templates/gulp_tasks/misc.js
new file mode 100755
index 0000000..2c9fbb0
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/gulp_tasks/misc.js
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const path = require('path');
+
+const gulp = require('gulp');
+const del = require('del');
+const filter = require('gulp-filter');
+const rename = require('gulp-rename');
+const replace = require('gulp-replace');
+
+const conf = require('../conf/gulp.conf');
+
+gulp.task('clean', clean);
+gulp.task('other', other);
+
+function clean() {
+  return del([`${conf.paths.dist}/*`, conf.paths.tmp]);
+}
+
+function other() {
+  const fileFilter = filter(file => file.stat.isFile());
+
+  return gulp.src([
+    path.join(conf.paths.src, '/**/*'),
+    path.join(`!${conf.paths.src}`, '/**/*.{scss,ts,html}')
+  ])
+    .pipe(fileFilter)
+    .pipe(gulp.dest(conf.paths.dist));
+}
+
+function other() {
+  const fileFilter = filter(file => file.stat.isFile());
+
+  return gulp.src([
+    path.join(conf.paths.src, '/**/*'),
+    path.join(`!${conf.paths.src}`, '/**/*.{scss,ts,html}')
+  ])
+    .pipe(fileFilter)
+    .pipe(gulp.dest(conf.paths.dist));
+}
diff --git a/generator-xos-gui-extension/generators/templates/gulp_tasks/webpack.js b/generator-xos-gui-extension/generators/templates/gulp_tasks/webpack.js
new file mode 100755
index 0000000..ccb64c0
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/gulp_tasks/webpack.js
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const gulp = require('gulp');
+const gutil = require('gulp-util');
+
+const webpack = require('webpack');
+const webpackConf = require('../conf/webpack.conf');
+const webpackDistConf = require('../conf/webpack-dist.conf');
+const gulpConf = require('../conf/gulp.conf');
+const browsersync = require('browser-sync');
+
+gulp.task('webpack:dev', done => {
+  webpackWrapper(false, webpackConf, done);
+});
+
+gulp.task('webpack:watch', done => {
+  webpackWrapper(true, webpackConf, done);
+});
+
+gulp.task('webpack:dist', done => {
+  process.env.NODE_ENV = 'production';
+  webpackWrapper(false, webpackDistConf, done);
+});
+
+function webpackWrapper(watch, conf, done) {
+  const webpackBundler = webpack(conf);
+
+  const webpackChangeHandler = (err, stats) => {
+    if (err) {
+      gulpConf.errorHandler('Webpack')(err);
+    }
+    gutil.log(stats.toString({
+      colors: true,
+      chunks: false,
+      hash: false,
+      version: false
+    }));
+    if (done) {
+      done();
+      done = null;
+    } else {
+      browsersync.reload();
+    }
+  };
+
+  if (watch) {
+    webpackBundler.watch(200, webpackChangeHandler);
+  } else {
+    webpackBundler.run(webpackChangeHandler);
+  }
+}
diff --git a/generator-xos-gui-extension/generators/templates/gulpfile.js b/generator-xos-gui-extension/generators/templates/gulpfile.js
new file mode 100755
index 0000000..a15e854
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/gulpfile.js
@@ -0,0 +1,47 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+const gulp = require('gulp');
+const HubRegistry = require('gulp-hub');
+const browserSync = require('browser-sync');
+
+const conf = require('./conf/gulp.conf');
+
+// Load some files into the registry
+const hub = new HubRegistry([conf.path.tasks('*.js')]);
+
+// Tell gulp to use the tasks just loaded
+gulp.registry(hub);
+
+gulp.task('build', gulp.series(gulp.parallel('other', 'webpack:dist')));
+gulp.task('test', gulp.series('karma:single-run'));
+gulp.task('test:auto', gulp.series('karma:auto-run'));
+gulp.task('serve', gulp.series('webpack:watch', 'watch', 'browsersync'));
+gulp.task('serve:dist', gulp.series('default', 'browsersync:dist'));
+gulp.task('default', gulp.series('clean', 'build'));
+gulp.task('watch', watch);
+
+function reloadBrowserSync(cb) {
+  browserSync.reload();
+  cb();
+}
+
+function watch(done) {
+  gulp.watch(conf.path.tmp('index.html'), reloadBrowserSync);
+  done();
+}
diff --git a/generator-xos-gui-extension/generators/templates/package.json b/generator-xos-gui-extension/generators/templates/package.json
new file mode 100644
index 0000000..32a0676
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/package.json
@@ -0,0 +1,119 @@
+{
+  "version": "3.0.0",
+  "dependencies": {
+    "angular": "1.6.3",
+    "angular-animate": "1.6.3",
+    "angular-cookies": "1.6.3",
+    "angular-resource": "1.6.3",
+    "angular-toastr": "2.1.1",
+    "angular-ui-bootstrap": "2.5.0",
+    "angular-ui-router": "1.0.0-beta.1",
+    "bootstrap": "3.3.7",
+    "bootstrap-sass": "3.3.7",
+    "d3": "3.5.17",
+    "jquery": "3.1.1",
+    "lodash": "4.17.4",
+    "ngprogress": "1.1.1",
+    "oclazyload": "1.1.0",
+    "pluralize": "3.1.0",
+    "rxjs": "5.2.0",
+    "socket.io-client": "1.7.3"
+  },
+  "devDependencies": {
+    "angular-mocks": "1.6.4",
+    "autoprefixer": "6.7.7",
+    "babel-eslint": "6.1.2",
+    "babel-loader": "6.4.1",
+    "babel-plugin-istanbul": "2.0.3",
+    "base-href-webpack-plugin": "1.0.0",
+    "bluebird": "^3.5.0",
+    "browser-sync": "2.18.8",
+    "browser-sync-spa": "1.0.3",
+    "copy-webpack-plugin": "4.0.1",
+    "css-loader": "0.23.1",
+    "del": "2.2.2",
+    "es6-shim": "0.35.3",
+    "eslint": "3.19.0",
+    "eslint-config-angular": "0.5.0",
+    "eslint-config-xo-space": "0.12.0",
+    "eslint-loader": "1.7.1",
+    "eslint-plugin-angular": "1.6.4",
+    "eslint-plugin-babel": "3.3.0",
+    "extract-text-webpack-plugin": "2.0.0-beta.3",
+    "file-loader": "0.9.0",
+    "gitbook-cli": "^2.3.0",
+    "gulp": "gulpjs/gulp#4ed9a4a3275559c73a396eff7e1fde3824951ebb",
+    "gulp-angular-filesort": "1.1.1",
+    "gulp-angular-templatecache": "1.9.1",
+    "gulp-filter": "4.0.0",
+    "gulp-htmlmin": "1.3.0",
+    "gulp-hub": "frankwallis/gulp-hub#d461b9c700df9010d0a8694e4af1fb96d9f38bf4",
+    "gulp-insert": "0.5.0",
+    "gulp-ng-annotate": "1.1.0",
+    "gulp-rename": "1.2.2",
+    "gulp-replace": "0.5.4",
+    "gulp-sass": "2.3.2",
+    "gulp-util": "3.0.8",
+    "html-loader": "0.4.5",
+    "html-webpack-plugin": "2.28.0",
+    "http-proxy": "1.16.2",
+    "istanbul-instrumenter-loader": "2.0.0",
+    "jasmine": "2.5.3",
+    "jasmine-jquery": "2.1.1",
+    "jasmine-spec-reporter": "^4.0.0",
+    "json-loader": "0.5.4",
+    "karma": "1.6.0",
+    "karma-angular-filesort": "1.0.2",
+    "karma-chrome-launcher": "2.0.0",
+    "karma-coverage": "1.1.1",
+    "karma-es6-shim": "1.0.0",
+    "karma-jasmine": "1.1.0",
+    "karma-junit-reporter": "1.2.0",
+    "karma-mocha-reporter": "2.2.3",
+    "karma-ng-html2js-preprocessor": "0.2.2",
+    "karma-phantomjs-launcher": "1.0.4",
+    "karma-phantomjs-shim": "1.4.0",
+    "karma-webpack": "1.8.1",
+    "ng-annotate-loader": "0.0.10",
+    "node-sass": "3.13.1",
+    "phantomjs-prebuilt": "2.1.14",
+    "postcss-loader": "0.8.2",
+    "remap-istanbul": "0.9.5",
+    "resolve-url-loader": "1.6.1",
+    "sass-loader": "3.2.3",
+    "style-loader": "0.13.2",
+    "ts-loader": "0.8.2",
+    "tslint": "3.15.1",
+    "tslint-loader": "2.1.5",
+    "typescript": "2.2.2",
+    "typings": "1.5.0",
+    "url-loader": "0.5.8",
+    "webpack": "2.1.0-beta.20"
+  },
+  "scripts": {
+    "postinstall": "npm run typings",
+    "build": "gulp",
+    "start": "gulp serve",
+    "typings": "typings install",
+    "serve:dist": "gulp serve:dist",
+    "serve:dist:watch": "gulp serve:dist:watch",
+    "test": "gulp test",
+    "test:auto": "gulp test:auto",
+    "test:e2e": "protractor conf/protractor.conf.js",
+    "config": "gulp config",
+    "lint": "tslint -c ./tslint.json 'src/**/*.ts'"
+  },
+  "eslintConfig": {
+    "globals": {
+      "expect": true
+    },
+    "root": true,
+    "env": {
+      "browser": true,
+      "jasmine": true
+    },
+    "extends": [
+      "xo-space/esnext"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/generator-xos-gui-extension/generators/templates/src/app/components/demo.html b/generator-xos-gui-extension/generators/templates/src/app/components/demo.html
new file mode 100644
index 0000000..a9dcb86
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/src/app/components/demo.html
@@ -0,0 +1,26 @@
+
+<!--
+Copyright 2017-present Open Networking Foundation
+
+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.
+-->
+
+
+<div class="row">
+  <div class="col-xs-12">
+    <h1><%= name %></h1>
+  </div>
+  <div class="col-xs-12">
+    <p>This is a demo component generated with the creation of this GUI extension.</p>
+  </div>
+</div>
\ No newline at end of file
diff --git a/generator-xos-gui-extension/generators/templates/src/app/components/demo.ts b/generator-xos-gui-extension/generators/templates/src/app/components/demo.ts
new file mode 100644
index 0000000..c4230a4
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/src/app/components/demo.ts
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+class DemoComponent {
+  static $inject = ['XosSidePanel', 'XosKeyboardShortcut'];
+
+  constructor(
+    private XosSidePanel: any,
+    private XosKeyboardShortcut: any
+  ) {
+    this.XosKeyboardShortcut.registerKeyBinding({
+      key: 'v',
+      description: 'Alert popup',
+      cb: () => {
+        alert('This binding is provided by the extension <%= name %>');
+      },
+    }, 'view');
+  }
+
+  togglePanel() {
+    this.XosSidePanel.toggleComponent('xosAlert', {config: {type: 'info'}, show: true}, 'This content is being toggled by the extension <%= name %>');
+  }
+
+}
+
+export const xosDemoComponent: angular.IComponentOptions = {
+  template: require('./demo.html'),
+  controllerAs: 'vm',
+  controller: DemoComponent
+};
diff --git a/generator-xos-gui-extension/generators/templates/src/index.html b/generator-xos-gui-extension/generators/templates/src/index.html
new file mode 100644
index 0000000..619c817
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/src/index.html
@@ -0,0 +1,35 @@
+
+<!--
+Copyright 2017-present Open Networking Foundation
+
+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.
+-->
+
+
+<!DOCTYPE html>
+<html lang="en" ng-app="<%= name %>">
+<head>
+  <meta charset="UTF-8">
+  <title><%= name %></title>
+  <link href="/xos/loader.css" rel="stylesheet">
+  <link href="/xos/app.css" rel="stylesheet">
+</head>
+<body>
+  <div ui-view></div>
+  <script src="/xos/vendor.js"></script>
+  <script src="/xos/app.js"></script>
+  <script src="/xos/loader.js"></script>
+  <script src="/xos/app.config.js"></script>
+  <script src="/xos/style.config.js"></script>
+</body>
+</html>
diff --git a/generator-xos-gui-extension/generators/templates/src/index.ts b/generator-xos-gui-extension/generators/templates/src/index.ts
new file mode 100644
index 0000000..e33bae4
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/src/index.ts
@@ -0,0 +1,47 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+/// <reference path="../typings/index.d.ts" />
+import * as angular from 'angular';
+
+import 'angular-ui-router';
+import 'angular-resource';
+import 'angular-cookies';
+import routesConfig from './routes';
+import {xosDemoComponent} from './app/components/demo';
+
+
+
+angular.module('<%= name %>', [
+    'ui.router',
+    'app'
+  ])
+  .config(routesConfig)
+  .component('demo', xosDemoComponent)
+  .run(function(
+    $log: ng.ILogService,
+    $state: ng.ui.IStateService,
+    XosNavigationService: any) {
+    $log.info('[<%= name %>] App is running');
+
+    XosNavigationService.add({
+      label: '<%= name %>',
+      state: 'xos.<%= name %>.demo',
+    });
+
+  });
diff --git a/generator-xos-gui-extension/generators/templates/src/routes.ts b/generator-xos-gui-extension/generators/templates/src/routes.ts
new file mode 100644
index 0000000..97f2925
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/src/routes.ts
@@ -0,0 +1,35 @@
+
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * 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.
+ */
+
+
+export default routesConfig;
+
+function routesConfig($stateProvider: angular.ui.IStateProvider, $locationProvider: angular.ILocationProvider) {
+  $locationProvider.html5Mode(false).hashPrefix('');
+
+  $stateProvider
+    .state('xos.<%= name %>', {
+      url: '<%= name %>',
+      abstract: true,
+      template: '<div ui-view></div>'
+    })
+    .state('xos.<%= name %>.demo', {
+      url: '/demo',
+      parent: 'xos.<%= name %>',
+      component: 'demo'
+    });
+}
diff --git a/generator-xos-gui-extension/generators/templates/tsconfig.json b/generator-xos-gui-extension/generators/templates/tsconfig.json
new file mode 100644
index 0000000..c516fb7
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/tsconfig.json
@@ -0,0 +1,19 @@
+{
+  "compilerOptions": {
+    "sourceMap": true,
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "removeComments": false,
+    "noImplicitAny": false
+  },
+  "compileOnSave": false,
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.tsx"
+  ],
+  "exclude": [
+    "typings/**",
+    "conf/app/**",
+    "node_modules"
+  ]
+}
diff --git a/generator-xos-gui-extension/generators/templates/tslint.json b/generator-xos-gui-extension/generators/templates/tslint.json
new file mode 100644
index 0000000..04d33e1
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/tslint.json
@@ -0,0 +1,84 @@
+{
+  "rules": {
+    "ban": [true,
+      ["_", "extend"],
+      ["_", "isNull"],
+      ["_", "isDefined"]
+    ],
+    "class-name": true,
+    "comment-format": [true,
+      "check-space"
+    ],
+    "curly": true,
+    "eofline": true,
+    "forin": true,
+    "indent": [true, "spaces"],
+    "interface-name": true,
+    "jsdoc-format": true,
+    "label-position": true,
+    "label-undefined": true,
+    "max-line-length": [false, 140],
+    "member-ordering": [true,
+      "public-before-private",
+      "static-before-instance",
+      "variables-before-functions"
+    ],
+    "no-arg": true,
+    "no-bitwise": true,
+    "no-console": [true,
+      "debug",
+      "info",
+      "time",
+      "timeEnd",
+      "trace",
+      "log",
+      "error"
+    ],
+    "no-construct": true,
+    "no-constructor-vars": false,
+    "no-debugger": true,
+    "no-duplicate-key": true,
+    "no-duplicate-variable": true,
+    "no-empty": true,
+    "no-eval": true,
+    "no-string-literal": false,
+    "no-switch-case-fall-through": true,
+    "trailing-comma": true,
+    "no-trailing-whitespace": true,
+    "no-unused-expression": true,
+    "no-unused-variable": true,
+    "no-unreachable": true,
+    "no-use-before-declare": true,
+    "no-var-requires": true,
+    "one-line": [true,
+      "check-open-brace",
+      "check-catch",
+      "check-whitespace"
+    ],
+    "quotemark": [true, "single"],
+    "radix": true,
+    "semicolon": true,
+    "triple-equals": [true, "allow-null-check"],
+    "typedef": [true,
+      "callSignature",
+      "indexSignature",
+      "parameter",
+      "propertySignature",
+      "variableDeclarator"
+    ],
+    "typedef-whitespace": [true,
+      ["callSignature", "noSpace"],
+      ["catchClause", "noSpace"],
+      ["indexSignature", "space"]
+    ],
+    "use-strict": false,
+    "variable-name": false,
+    "whitespace": [true,
+      "check-branch",
+      "check-decl",
+      "check-operator",
+      "check-separator",
+      "check-type"
+    ]
+  }
+}
diff --git a/generator-xos-gui-extension/generators/templates/typings.json b/generator-xos-gui-extension/generators/templates/typings.json
new file mode 100644
index 0000000..a830275
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/typings.json
@@ -0,0 +1,18 @@
+{
+  "globalDependencies": {
+    "angular": "registry:dt/angular#1.5.0+20161208205636",
+    "angular-cookies": "registry:dt/angular-cookies#1.4.0+20160317120654",
+    "angular-mocks": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular-mocks.d.ts#dc9dabe74a5be62613b17a3605309783a12ff28a",
+    "angular-resource": "registry:dt/angular-resource#1.5.0+20161114123626",
+    "angular-ui-router": "registry:dt/angular-ui-router#1.1.5+20160707113237",
+    "es6-shim": "registry:dt/es6-shim#0.31.2+20160602141504",
+    "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#dc9dabe74a5be62613b17a3605309783a12ff28a",
+    "jasmine-jquery": "registry:dt/jasmine-jquery#1.5.8+20161128184045",
+    "jquery": "registry:dt/jquery#1.10.0+20161119044246",
+    "require": "registry:dt/require#2.1.20+20160316155526",
+    "socket.io-client": "registry:dt/socket.io-client#1.4.4+20160317120654"
+  },
+  "dependencies": {
+    "angular-toastr": "registry:dt/angular-toastr#1.6.0+20160708003927"
+  }
+}
diff --git a/generator-xos-gui-extension/generators/templates/xos-sample-gui-extension.yaml b/generator-xos-gui-extension/generators/templates/xos-sample-gui-extension.yaml
new file mode 100644
index 0000000..576ea3f
--- /dev/null
+++ b/generator-xos-gui-extension/generators/templates/xos-sample-gui-extension.yaml
@@ -0,0 +1,31 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: <%= name %>
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+
+    # UI Extension
+    <%= name %>:
+      type: tosca.nodes.XOSGuiExtension
+      properties:
+        files: /xos/extensions/<%= name %>/vendor.js, /xos/extensions/<%= name %>/app.js
diff --git a/generator-xos-gui-extension/package.json b/generator-xos-gui-extension/package.json
new file mode 100644
index 0000000..fa28798
--- /dev/null
+++ b/generator-xos-gui-extension/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "generator-xos-gui-extension",
+  "version": "1.0.0",
+  "description": "Yeoman generator for creating new XOS GUI Extensions",
+  "keywords": [
+    "yeoman-generator"
+  ],
+  "author": "Open Networking Foundation",
+  "license": "Apache-2.0",
+  "files": [
+    "generators"
+  ],
+  "dependencies": {
+    "yeoman-generator": "^1.1.1"
+  }
+}
diff --git a/src/app/core/debug/debug-model.spec.ts b/src/app/core/debug/debug-model.spec.ts
index 46b6344..b081de0 100644
--- a/src/app/core/debug/debug-model.spec.ts
+++ b/src/app/core/debug/debug-model.spec.ts
@@ -91,7 +91,7 @@
 
       dateFields.forEach(f => {
         const date = isolatedScope.parseField(f, model[f]);
-        expect(date).toEqual('Thu Aug 17 2017 15:45:20 GMT-0700 (PDT)');
+        expect(date).toEqual(new Date(model[f] * 1000).toString());
       });
     });
 
diff --git a/src/app/core/debug/debug-summary.html b/src/app/core/debug/debug-summary.html
index 9807ac9..7e185da 100644
--- a/src/app/core/debug/debug-summary.html
+++ b/src/app/core/debug/debug-summary.html
@@ -41,5 +41,12 @@
                 <i class="fa fa-remove text-danger" ng-hide="vm.debugStatus.modelsTab"></i>
             </td>
         </tr>
+        <tr>
+            <td>Notifications</td>
+            <td class="text-right">
+                <i class="fa fa-check text-success" ng-show="vm.debugStatus.notifications"></i>
+                <i class="fa fa-remove text-danger" ng-hide="vm.debugStatus.notifications"></i>
+            </td>
+        </tr>
     </tbody>
 </table>
\ No newline at end of file
diff --git a/src/app/core/debug/debug.service.spec.ts b/src/app/core/debug/debug.service.spec.ts
index a26d8cc..99f9484 100644
--- a/src/app/core/debug/debug.service.spec.ts
+++ b/src/app/core/debug/debug.service.spec.ts
@@ -53,6 +53,13 @@
     expect(service.status.events).toBeTruthy();
   });
 
+  it('should read the notification status from localStorage', () => {
+    spyOn(window.localStorage, 'getItem')
+      .and.returnValue(null);
+    service = new XosDebugService($log, $scope, XosKeyboardShortcut);
+    expect(service.status.notifications).toBeTruthy();
+  });
+
   it('should disable the global debug status', () => {
     spyOn(window.localStorage, 'getItem')
       .and.returnValue('true');
diff --git a/src/app/core/debug/debug.service.ts b/src/app/core/debug/debug.service.ts
index f23fa06..aee1064 100644
--- a/src/app/core/debug/debug.service.ts
+++ b/src/app/core/debug/debug.service.ts
@@ -20,12 +20,13 @@
   global: boolean;
   events: boolean;
   modelsTab: boolean;
+  notifications: boolean;
 }
 
 export interface IXosDebugService {
   status: IXosDebugStatus;
   setupShortcuts(): void;
-  toggleDebug(type: 'global' | 'events' | 'modelsTab'): void;
+  toggleDebug(type: 'global' | 'events' | 'modelsTab' | 'notifications'): void;
 }
 
 export class XosDebugService implements IXosDebugService {
@@ -35,7 +36,8 @@
   public status: IXosDebugStatus = {
     global: false,
     events: false,
-    modelsTab: false
+    modelsTab: false,
+    notifications: true
   };
 
   constructor (
@@ -51,6 +53,9 @@
 
     const debugModelsTab = window.localStorage.getItem('debug-modelsTab');
     this.status.modelsTab = (debugModelsTab === 'true');
+
+    const notifications = window.localStorage.getItem('debug-notifications');
+    this.status.notifications = (notifications !== null ? notifications === 'true' : true);
   }
 
   public setupShortcuts(): void {
@@ -65,9 +70,15 @@
       cb: () => this.toggleDebug('events'),
       description: 'Toggle debug messages for WS events in browser console'
     }, 'global');
+
+    this.XosKeyboardShortcut.registerKeyBinding({
+        key: 'S',
+        cb: () => this.toggleDebug('notifications'),
+        description: 'Toggle notifications'
+    }, 'global');
   }
 
-  public toggleDebug(type: 'global' | 'events' | 'modelsTab'): void {
+  public toggleDebug(type: 'global' | 'events' | 'modelsTab' | 'notifications'): void {
     if (window.localStorage.getItem(`debug-${type}`) === 'true') {
       this.$log.info(`[XosDebug] Disabling ${type} debug`);
       window.localStorage.setItem(`debug-${type}`, 'false');
diff --git a/src/app/core/header/header.spec.ts b/src/app/core/header/header.spec.ts
index eb236b1..a4a7d90 100644
--- a/src/app/core/header/header.spec.ts
+++ b/src/app/core/header/header.spec.ts
@@ -24,6 +24,7 @@
 import 'angular-mocks';
 import {xosHeader, INotification} from './header';
 import {Subject} from 'rxjs';
+import {IXosDebugService} from '../debug/debug.service';
 
 let element, scope: angular.IRootScopeService, compile: ng.ICompileService, isolatedScope;
 const events = new Subject();
@@ -79,6 +80,17 @@
   registerKeyBinding: jasmine.createSpy('registerKeyBinding')
 };
 
+const MockXosDebug: IXosDebugService = {
+  status: {
+    global: false,
+    events: false,
+    modelsTab: false,
+    notifications: true
+  },
+  setupShortcuts: jasmine.createSpy('debug.createShortcuts'),
+  toggleDebug: jasmine.createSpy('debug.toggleDebug')
+};
+
 describe('header component', () => {
   beforeEach(() => {
     angular
@@ -96,7 +108,8 @@
       .value('StyleConfig', {
         logo: 'cord-logo.png',
       })
-      .value('SearchService', {});
+      .value('SearchService', {})
+      .value('XosDebug', MockXosDebug);
 
     angular.mock.module('xosHeader');
   });
@@ -146,7 +159,16 @@
     });
   });
 
-  it('should display a toastr for a new notification', () => {
+  it('should not display a toastr for a new notification (if notifications are disabled)', () => {
+      MockXosDebug.status.notifications = false;
+      sendEvent(infoNotification);
+      scope.$digest();
+
+      expect(MockToastr.info).not.toHaveBeenCalled();
+  });
+
+  it('should display a toastr for a new notification (if notifications are enabled)', () => {
+    MockXosDebug.status.notifications = true;
     sendEvent(infoNotification);
     scope.$digest();
 
diff --git a/src/app/core/header/header.ts b/src/app/core/header/header.ts
index a465f8f..26f6fd0 100644
--- a/src/app/core/header/header.ts
+++ b/src/app/core/header/header.ts
@@ -28,6 +28,7 @@
 import {IXosKeyboardShortcutService} from '../services/keyboard-shortcut';
 import {Subscription} from 'rxjs';
 import {IXosConfigHelpersService} from '../services/helpers/config.helpers';
+import {IXosDebugService} from '../debug/debug.service';
 
 export interface INotification extends IWSEvent {
   viewed?: boolean;
@@ -47,7 +48,8 @@
     'StyleConfig',
     'SearchService',
     'XosKeyboardShortcut',
-    'ConfigHelpers'
+    'ConfigHelpers',
+    'XosDebug'
   ];
   public notifications: INotification[] = [];
   public newNotifications: INotification[] = [];
@@ -73,7 +75,8 @@
     private StyleConfig: IXosStyleConfig,
     private SearchService: IXosSearchService,
     private XosKeyboardShortcut: IXosKeyboardShortcutService,
-    private ConfigHelpers: IXosConfigHelpersService
+    private ConfigHelpers: IXosConfigHelpersService,
+    private XosDebugService: IXosDebugService
   ) {
 
   }
@@ -123,6 +126,12 @@
         (event: IWSEvent) => {
           this.$scope.$evalAsync(() => {
 
+            if (!this.XosDebugService.status.notifications) {
+              // NOTE: notifications can be disabled
+              return;
+            }
+
+
             if (event.model === 'Diag') {
               // NOTE skip notifications for Diag model
               return;
diff --git a/src/app/datasources/helpers/store.helpers.spec.ts b/src/app/datasources/helpers/store.helpers.spec.ts
index f3b2321..2f617bc 100644
--- a/src/app/datasources/helpers/store.helpers.spec.ts
+++ b/src/app/datasources/helpers/store.helpers.spec.ts
@@ -26,6 +26,7 @@
 import {ConfigHelpers} from '../../core/services/helpers/config.helpers';
 import {AuthService} from '../rest/auth.rest';
 import {IXosModeldefsCache} from './modeldefs.service';
+import {XosFormHelpers} from '../../core/form/form-helpers';
 
 let service: IStoreHelpersService;
 let subject: BehaviorSubject<any>;
@@ -42,6 +43,7 @@
       .service('ModelRest', ModelRest) // NOTE evaluate mock
       .service('StoreHelpers', StoreHelpers)
       .service('AuthService', AuthService)
+      .service('XosFormHelpers', XosFormHelpers)
       .value('XosModeldefsCache', {
         get: jasmine.createSpy('XosModeldefsCache.get'),
         getApiUrlFromModel: jasmine.createSpy('XosModeldefsCache.getApiUrlFromModel')
diff --git a/src/app/datasources/rest/model.rest.spec.ts b/src/app/datasources/rest/model.rest.spec.ts
index d545ac2..da4f2f4 100644
--- a/src/app/datasources/rest/model.rest.spec.ts
+++ b/src/app/datasources/rest/model.rest.spec.ts
@@ -22,6 +22,7 @@
 import 'angular-cookies';
 import {IXosResourceService} from './model.rest';
 import {xosDataSources} from '../index';
+import {IXosFormHelpersService} from '../../core/form/form-helpers';
 
 let service: IXosResourceService;
 let resource: ng.resource.IResourceClass<any>;
@@ -34,6 +35,10 @@
   websocketClient: 'http://xos-test:3000'
 };
 
+const MockFormHelpers: IXosFormHelpersService = {
+  _getFieldFormat: () => 'date'
+};
+
 describe('The ModelRest service', () => {
 
   beforeEach(angular.mock.module(xosDataSources));
@@ -41,7 +46,8 @@
   beforeEach(() => {
 
     angular.module(xosDataSources)
-      .constant('AppConfig', MockAppCfg);
+      .constant('AppConfig', MockAppCfg)
+      .value('XosFormHelpers', MockFormHelpers);
 
     angular.mock.module(xosDataSources);
   });
@@ -99,4 +105,31 @@
     $scope.$apply();
     httpBackend.flush();
   });
+
+  describe('when saving a model', () => {
+
+    let item, date;
+    const timestamp = 1509552402000;
+
+    beforeEach(() => {
+      httpBackend.expectPOST(`${MockAppCfg.apiEndpoint}/core/test`)
+        .respond((method, url, req) => {
+          return [200, req];
+        });
+      resource = service.getResource('/core/test');
+      date = new Date(timestamp);
+      item = new resource({date: date.toString()});
+    });
+
+    xit('should convert dates to timestamps', (done) => {
+      item.$save()
+        .then(res => {
+          expect(res.date).toEqual(timestamp);
+          done();
+        });
+      $scope.$apply();
+      httpBackend.flush();
+      done();
+    });
+  });
 });
diff --git a/src/app/datasources/rest/model.rest.ts b/src/app/datasources/rest/model.rest.ts
index 6c72d99..f390616 100644
--- a/src/app/datasources/rest/model.rest.ts
+++ b/src/app/datasources/rest/model.rest.ts
@@ -15,37 +15,48 @@
  * limitations under the License.
  */
 
-
+import * as _ from 'lodash';
 import {IXosAppConfig} from '../../../index';
+import {IXosFormHelpersService} from '../../core/form/form-helpers';
+
 export interface IXosResourceService {
   getResource(url: string): ng.resource.IResourceClass<any>;
 }
 
 export class ModelRest implements IXosResourceService {
-  static $inject = ['$resource', 'AppConfig'];
+  static $inject = ['$resource', 'AppConfig', 'XosFormHelpers'];
 
   /** @ngInject */
   constructor(
     private $resource: ng.resource.IResourceService,
-    private AppConfig: IXosAppConfig
+    private AppConfig: IXosAppConfig,
+    private XosFormHelpers: IXosFormHelpersService
   ) {
 
   }
 
   public getResource(url: string): ng.resource.IResourceClass<ng.resource.IResource<any>> {
+    const self = this;
     const resource: angular.resource.IResourceClass<any> = this.$resource(`${this.AppConfig.apiEndpoint}${url}/:id/`, {id: '@id'}, {
       update: { method: 'PUT' },
       query: {
         method: 'GET',
         isArray: true,
         transformResponse: (res) => {
-          // FIXME chameleon return everything inside "items"
           return res.items ? res.items : res;
         }
       }
     });
 
     resource.prototype.$save = function() {
+
+      // NOTE converting dates back to timestamp
+      _.forEach(Object.keys(this), (k: string) => {
+        if (self.XosFormHelpers._getFieldFormat(this[k]) === 'date') {
+          this[k] = new Date(this[k]).getTime();
+        }
+      });
+
       if (this.id) {
         return this.$update();
       } else {
diff --git a/src/app/datasources/stores/model.store.spec.ts b/src/app/datasources/stores/model.store.spec.ts
index 09dc3f9..ce3e2cd 100644
--- a/src/app/datasources/stores/model.store.spec.ts
+++ b/src/app/datasources/stores/model.store.spec.ts
@@ -28,6 +28,7 @@
 import {AuthService} from '../rest/auth.rest';
 import {XosDebouncer} from '../../core/services/helpers/debounce.helper';
 import {IXosModeldefsCache} from '../helpers/modeldefs.service';
+import {XosFormHelpers} from '../../core/form/form-helpers';
 
 let service: IXosModelStoreService;
 let httpBackend: ng.IHttpBackendService;
@@ -70,6 +71,7 @@
       .service('XosModelStore', XosModelStore)
       .service('ConfigHelpers', ConfigHelpers) // TODO mock
       .service('AuthService', AuthService)
+      .service('XosFormHelpers', XosFormHelpers)
       .constant('AppConfig', MockAppCfg)
       .value('XosModeldefsCache', {
         get: jasmine.createSpy('XosModeldefsCache.get').and.returnValue({}),
diff --git a/src/app/views/dashboard/dashboard.ts b/src/app/views/dashboard/dashboard.ts
index 45bd138..285c2df 100644
--- a/src/app/views/dashboard/dashboard.ts
+++ b/src/app/views/dashboard/dashboard.ts
@@ -18,37 +18,53 @@
 
 import {IXosModelStoreService} from '../../datasources/stores/model.store';
 import {IXosAuthService} from '../../datasources/rest/auth.rest';
+import {Subscription} from 'rxjs/Subscription';
+
+
 class DashboardController {
-  static $inject = ['$scope', '$state', 'XosModelStore', 'AuthService'];
+  static $inject = [
+    '$log',
+    '$scope',
+    '$state',
+    'XosModelStore',
+    'AuthService'
+  ];
 
   public nodes: number;
   public slices: number;
   public instances: number;
 
+  private nodeSubscription: Subscription;
+  private sliceSubscription: Subscription;
+  private instanceSubscription: Subscription;
+
   constructor(
+    private $log: ng.ILogService,
     private $scope: ng.IScope,
     private $state: ng.ui.IStateService,
     private store: IXosModelStoreService,
     private auth: IXosAuthService
   ) {
 
+    this.$log.info(`[XosDashboardView] Setup`);
+
     if (!this.auth.isAuthenticated()) {
       this.$state.go('login');
     }
     else {
-      this.store.query('Node')
+      this.nodeSubscription = this.store.query('Node')
         .subscribe((event) => {
           this.$scope.$evalAsync(() => {
             this.nodes = event.length;
           });
         });
-      this.store.query('Instance')
+      this.instanceSubscription = this.store.query('Instance')
         .subscribe((event) => {
           this.$scope.$evalAsync(() => {
             this.instances = event.length;
           });
         });
-      this.store.query('Slice')
+      this.sliceSubscription = this.store.query('Slice')
         .subscribe((event) => {
           this.$scope.$evalAsync(() => {
             this.slices = event.length;
@@ -59,6 +75,12 @@
       this.slices = 0;
     }
   }
+
+  $onDestroy () {
+    this.nodeSubscription.unsubscribe();
+    this.instanceSubscription.unsubscribe();
+    this.sliceSubscription.unsubscribe();
+  }
 }
 
 export const xosDashboard: angular.IComponentOptions = {
diff --git a/src/index.ts b/src/index.ts
index 2a287d5..68920b0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -83,11 +83,12 @@
   .component('xos', main)
   .provider('XosConfig', function(){
     // save the last visited state before reload
-    const lastVisitedUrl = window.location.hash.replace('#', '');
+    let lastVisitedUrl = window.location.hash.replace('#', '');
     this.$get = [() => {
-      return {
-        lastVisitedUrl
-      };
+      if (lastVisitedUrl === '/login' || lastVisitedUrl === '/loader') {
+        lastVisitedUrl = '/dashboard';
+      }
+      return {lastVisitedUrl};
     }] ;
     return this;
   })
@@ -129,11 +130,15 @@
     // if the user is authenticated
     $log.info(`[XOS] Is user authenticated? ${AuthService.isAuthenticated()}`);
     if (AuthService.isAuthenticated()) {
+      $log.info(`[XOS] Redirect to "loader"`);
       $state.go('loader');
+      $rootScope.$apply();
     }
     else {
       AuthService.clearUser();
+      $log.info(`[XOS] Redirect to "login"`);
       $state.go('login');
+      $rootScope.$apply();
     }
 
     // register keyboard shortcut