[CORD-1558]  metro-net GUI development

Change-Id: Id85e7cec9d52717777c388f7bfde712bb72850e9
diff --git a/.gitignore b/.gitignore
index 9f11b75..ce1f7da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,6 @@
 .idea/
+.DS_Store
+node_modules/
+xos/.DS_Store
+xos/gui/.DS_Store
+xos/gui/src/.DS_Store
\ No newline at end of file
diff --git a/xos/gui/.dockerignore b/xos/gui/.dockerignore
new file mode 100644
index 0000000..de807ff
--- /dev/null
+++ b/xos/gui/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+typings
+npm-debug.log
\ No newline at end of file
diff --git a/xos/gui/.gitignore b/xos/gui/.gitignore
new file mode 100644
index 0000000..e484a0d
--- /dev/null
+++ b/xos/gui/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+dist/
+.tmp/
+.idea/
+.DS_Store
+src/.DS_Store
+typings/
\ No newline at end of file
diff --git a/xos/gui/Dockerfile b/xos/gui/Dockerfile
new file mode 100644
index 0000000..911bd15
--- /dev/null
+++ b/xos/gui/Dockerfile
@@ -0,0 +1,24 @@
+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
+RUN npm run build
diff --git a/xos/gui/README.md b/xos/gui/README.md
new file mode 100644
index 0000000..a6b801f
--- /dev/null
+++ b/xos/gui/README.md
@@ -0,0 +1,24 @@
+# metro-net-gui
+
+## Installation
+
+Having a profile deployed is required. To use the GUI, include the following in the profile manifest
+being used in `cord/build/platform-install/profile_manifests`.
+
+```
+enabled_gui_extensions:
+  - name: metro-net-gui
+    path: orchestration/metro-net/xos/gui
+    extra-files:
+        - app/style/style.css
+        - mapconstants.js
+```
+
+## Features
+
+ - Maps all UserNetworkInterface locations, and displays the status of created ELine connections
+ - Allows for the creation of new and modification of exisiting ELine connections using the map
+ 
+## Interface
+
+![Metro-net-gui Screenshot](http://i.imgur.com/f4YxuyV.png)
\ No newline at end of file
diff --git a/xos/gui/conf/app/README.md b/xos/gui/conf/app/README.md
new file mode 100755
index 0000000..0cfab6e
--- /dev/null
+++ b/xos/gui/conf/app/README.md
@@ -0,0 +1,47 @@
+# XOS-GUI Config
+
+### Note that the configurations defined in this folder are for development only, they are most likely to be overrided by a volume mount defined in `service-profile`
+
+## App Config
+
+This configuration will specifiy the rest API base url and the Websocket address.
+Here is it's structure:
+
+```
+angular.module('app')
+  .constant('AppConfig', {
+    apiEndpoint: '/xos/api',
+    websocketClient: '/'
+  });
+
+```
+
+## Style Config
+
+This configuration will contain branding information, such as title, logo and navigation items.
+Here is it's structure:
+
+```
+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/xos/gui/conf/app/app.config.dev.js b/xos/gui/conf/app/app.config.dev.js
new file mode 100755
index 0000000..a5be15d
--- /dev/null
+++ b/xos/gui/conf/app/app.config.dev.js
@@ -0,0 +1,5 @@
+angular.module('app')
+  .constant('AppConfig', {
+    apiEndpoint: 'http://xos.dev:3000/api',
+    websocketClient: 'http://xos.dev:3000',
+  });
diff --git a/xos/gui/conf/app/app.config.production.js b/xos/gui/conf/app/app.config.production.js
new file mode 100755
index 0000000..a7e7611
--- /dev/null
+++ b/xos/gui/conf/app/app.config.production.js
@@ -0,0 +1,5 @@
+angular.module('metro-net-gui')
+  .constant('AppConfig', {
+    apiEndpoint: '/xos/api',
+    websocketClient: '/',
+  });
diff --git a/xos/gui/conf/app/app.config.test.js b/xos/gui/conf/app/app.config.test.js
new file mode 100755
index 0000000..86cfcb1
--- /dev/null
+++ b/xos/gui/conf/app/app.config.test.js
@@ -0,0 +1,5 @@
+angular.module('app')
+  .constant('AppConfig', {
+      apiEndpoint: 'http://xos-test:3000/api',
+      websocketClient: 'http://xos-test:3000'
+  });
diff --git a/xos/gui/conf/app/mapconstants.local.js b/xos/gui/conf/app/mapconstants.local.js
new file mode 100644
index 0000000..e616079
--- /dev/null
+++ b/xos/gui/conf/app/mapconstants.local.js
@@ -0,0 +1,4 @@
+angular.module('app')
+  .constant('MapConfig', {
+    marker: './xos/extensions/metro-net-gui/app/img/co.png',
+  });
\ No newline at end of file
diff --git a/xos/gui/conf/app/mapconstants.production.js b/xos/gui/conf/app/mapconstants.production.js
new file mode 100644
index 0000000..d64dca7
--- /dev/null
+++ b/xos/gui/conf/app/mapconstants.production.js
@@ -0,0 +1,4 @@
+angular.module('app')
+  .constant('MapConfig', {
+    marker: './extensions/metro-net-gui/app/img/co.png',
+  });
diff --git a/xos/gui/conf/app/style.config.cord.js b/xos/gui/conf/app/style.config.cord.js
new file mode 100755
index 0000000..6f7ebab
--- /dev/null
+++ b/xos/gui/conf/app/style.config.cord.js
@@ -0,0 +1,22 @@
+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/xos/gui/conf/app/style.config.opencloud.js b/xos/gui/conf/app/style.config.opencloud.js
new file mode 100755
index 0000000..9693c5d
--- /dev/null
+++ b/xos/gui/conf/app/style.config.opencloud.js
@@ -0,0 +1,14 @@
+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/xos/gui/conf/browsersync-dist.conf.js b/xos/gui/conf/browsersync-dist.conf.js
new file mode 100755
index 0000000..fa45845
--- /dev/null
+++ b/xos/gui/conf/browsersync-dist.conf.js
@@ -0,0 +1,12 @@
+const conf = require('./gulp.conf');
+
+module.exports = function () {
+  return {
+    server: {
+      baseDir: [
+        conf.paths.dist
+      ]
+    },
+    open: false
+  };
+};
diff --git a/xos/gui/conf/browsersync.conf.js b/xos/gui/conf/browsersync.conf.js
new file mode 100755
index 0000000..e227cfb
--- /dev/null
+++ b/xos/gui/conf/browsersync.conf.js
@@ -0,0 +1,22 @@
+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
+    };
+};
\ No newline at end of file
diff --git a/xos/gui/conf/gulp.conf.js b/xos/gui/conf/gulp.conf.js
new file mode 100755
index 0000000..d5a4d2e
--- /dev/null
+++ b/xos/gui/conf/gulp.conf.js
@@ -0,0 +1,38 @@
+'use strict';
+
+const path = require('path');
+const gutil = require('gulp-util');
+
+exports.ngModule = 'app';
+
+exports.paths = {
+  src: 'src',
+  dist: 'dist/extensions/metro-net-gui',
+  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/xos/gui/conf/karma-auto.conf.js b/xos/gui/conf/karma-auto.conf.js
new file mode 100755
index 0000000..a5ea084
--- /dev/null
+++ b/xos/gui/conf/karma-auto.conf.js
@@ -0,0 +1,61 @@
+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/xos/gui/conf/karma.conf.js b/xos/gui/conf/karma.conf.js
new file mode 100755
index 0000000..2db6132
--- /dev/null
+++ b/xos/gui/conf/karma.conf.js
@@ -0,0 +1,57 @@
+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/xos/gui/conf/proxy.js b/xos/gui/conf/proxy.js
new file mode 100644
index 0000000..02f9d5e
--- /dev/null
+++ b/xos/gui/conf/proxy.js
@@ -0,0 +1,18 @@
+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
+};
\ No newline at end of file
diff --git a/xos/gui/conf/webpack-dist.conf.js b/xos/gui/conf/webpack-dist.conf.js
new file mode 100755
index 0000000..12aa031
--- /dev/null
+++ b/xos/gui/conf/webpack-dist.conf.js
@@ -0,0 +1,107 @@
+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` },
+      { from: `./conf/app/mapconstants.production.js`, to: `mapconstants.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: '/xos/'
+    }),
+  ],
+  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'
+    ],
+      alias: {
+          "ngprogress": path.resolve(__dirname, '../node_modules/ngprogress/build/ngProgress.js')
+      }
+  },
+  entry: {
+    app: `./${conf.path.src('index')}`,
+    vendor: Object.keys(pkg.dependencies)
+  },
+  ts: {
+    configFileName: 'tsconfig.json'
+  },
+  tslint: {
+    configuration: require('../tslint.json')
+  }
+};
+
diff --git a/xos/gui/conf/webpack-test.conf.js b/xos/gui/conf/webpack-test.conf.js
new file mode 100755
index 0000000..a87e383
--- /dev/null
+++ b/xos/gui/conf/webpack-test.conf.js
@@ -0,0 +1,66 @@
+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/xos/gui/conf/webpack.conf.js b/xos/gui/conf/webpack.conf.js
new file mode 100755
index 0000000..0a16b7d
--- /dev/null
+++ b/xos/gui/conf/webpack.conf.js
@@ -0,0 +1,100 @@
+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` },
+      { from: `./conf/app/mapconstants.local.js`, to: `mapconstants.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/xos/gui/gulp_tasks/browsersync.js b/xos/gui/gulp_tasks/browsersync.js
new file mode 100755
index 0000000..945a88d
--- /dev/null
+++ b/xos/gui/gulp_tasks/browsersync.js
@@ -0,0 +1,21 @@
+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/xos/gui/gulp_tasks/karma.js b/xos/gui/gulp_tasks/karma.js
new file mode 100755
index 0000000..5b90572
--- /dev/null
+++ b/xos/gui/gulp_tasks/karma.js
@@ -0,0 +1,27 @@
+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/xos/gui/gulp_tasks/misc.js b/xos/gui/gulp_tasks/misc.js
new file mode 100755
index 0000000..d1e70ec
--- /dev/null
+++ b/xos/gui/gulp_tasks/misc.js
@@ -0,0 +1,38 @@
+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/xos/gui/gulp_tasks/webpack.js b/xos/gui/gulp_tasks/webpack.js
new file mode 100755
index 0000000..ec8e8b1
--- /dev/null
+++ b/xos/gui/gulp_tasks/webpack.js
@@ -0,0 +1,49 @@
+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/xos/gui/gulpfile.js b/xos/gui/gulpfile.js
new file mode 100755
index 0000000..04d30c0
--- /dev/null
+++ b/xos/gui/gulpfile.js
@@ -0,0 +1,30 @@
+const gulp = require('gulp');
+const HubRegistry = require('gulp-hub');
+const browserSync = require('browser-sync');
+
+const conf = require('./conf/gulp.conf');
+// const ngConstant = require('gulp-ng-constant');
+
+// 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/xos/gui/package.json b/xos/gui/package.json
new file mode 100644
index 0000000..1800dfd
--- /dev/null
+++ b/xos/gui/package.json
@@ -0,0 +1,120 @@
+{
+  "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",
+    "ngmap": "^1.18.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"
+    ]
+  }
+}
diff --git a/xos/gui/src/app/components/eline-side.component.html b/xos/gui/src/app/components/eline-side.component.html
new file mode 100644
index 0000000..09cf33e
--- /dev/null
+++ b/xos/gui/src/app/components/eline-side.component.html
@@ -0,0 +1,46 @@
+<div>
+  <h1>ELine Overview</h1>
+  <form ng-submit="vm.saveEline(vm.mng.eline)">
+    <div class="form-group" ng-hide="vm.mng.createMode">
+      <label>ID</label><br/>
+      <p>{{vm.mng.eline.id}}</p>
+    </div>
+    <div class="form-group">
+      <label for="name">Name</label>
+      <input required class="form-control" id="name" type="text" ng-value="vm.mng.eline.name" ng-model="vm.mng.eline.name">
+    </div>
+    <div class="form-group" ng-hide="vm.mng.createMode">
+      <label>Backend Status</label><br/>
+      <p>{{vm.mng.eline.backend_status}}</p>
+    </div>
+    <div class="form-group">
+      <label for="cpi1">Connect point 1 ID</label>
+      <input required class="form-control" id="cpi1" type="text" ng-value="vm.mng.eline.connect_point_1_id" ng-model="vm.mng.eline.connect_point_1_id">
+    </div>
+    <div class="form-group">
+      <label for="cpi2">Connect point 2 ID</label>
+      <input required class="form-control" id="cpi2" type="text" ng-value="vm.mng.eline.connect_point_2_id" ng-model="vm.mng.eline.connect_point_2_id">
+    </div>
+    <div class="form-group">
+      <label for="bwps">Bandwidth Profile</label>
+      <select required class="form-control"
+              id="bwps"
+              ng-model="vm.mng.eline.bwp"
+              ng-options="bwp.name as bwp.name for bwp in vm.mng.bwps"
+      >
+      </select>
+    </div>
+    <div class="form-group">
+      <label for="sitename">CORD Site Name</label>
+      <input required class="form-control" id="sitename" type="text" ng-value="vm.mng.eline.cord_site_name" ng-model="vm.mng.eline.cord_site_name">
+    </div>
+    <div class="form-group">
+      <label for="vlanids">Vlan IDs</label>
+      <input required class="form-control" id="vlanids" type="text" ng-value="vm.mng.eline.vlanids" ng-model="vm.mng.eline.vlanids">
+    </div>
+    <div class="form-group" style="text-align: center">
+      <button type="submit" class="btn btn-success btn-block">Save Changes</button>
+    </div>
+  </form>
+  <button type="button" class="btn btn-danger btn-block" ng-click="vm.mng.elinePanel({}, vm.mng.eline, false)">Close</button>
+</div>
\ No newline at end of file
diff --git a/xos/gui/src/app/components/eline-side.component.ts b/xos/gui/src/app/components/eline-side.component.ts
new file mode 100644
index 0000000..ca017c4
--- /dev/null
+++ b/xos/gui/src/app/components/eline-side.component.ts
@@ -0,0 +1,39 @@
+let self;
+
+class ElineSide {
+
+  static $inject = ['XosSidePanel', 'XosModelStore', '$http', '$log', 'toastr'];
+
+  constructor(
+    private XosSidePanel: any,
+    private XosModelStore: any,
+    private $http: any,
+    private $log: any,
+    private toastr: any,
+  ) {
+    self = this;
+  }
+
+  public saveEline(item: any) {
+    let path = item.path;
+    delete item.path;
+    item.$save().then((res) => {
+      item.path = path;
+      this.toastr.success(`${item.name} successfully saved!`);
+    })
+      .catch((error) => {
+        this.toastr.error(`Error while saving ${item.name}: ${error.specific_error}`);
+      });
+  }
+
+
+}
+
+export const elineSide: angular.IComponentOptions = {
+  template: require('./eline-side.component.html'),
+  controllerAs: 'vm',
+  controller: ElineSide,
+  bindings: {
+    mng: '='
+  }
+};
diff --git a/xos/gui/src/app/components/mngMap.component.html b/xos/gui/src/app/components/mngMap.component.html
new file mode 100644
index 0000000..e41422f
--- /dev/null
+++ b/xos/gui/src/app/components/mngMap.component.html
@@ -0,0 +1,61 @@
+<div class = "row">
+  <div class = "col-xs-12">
+    <h1>Metronet Map</h1>
+    <div map-lazy-load="https://maps.googleapis.com/maps/api/js?key=AIzaSyA3rQOp26I5a21VQhwLal8Z1x3XGHjXfm4">
+      <ng-map
+        default-style="false"
+        class="metronet"
+        id="foobar"
+        center="0,0"
+        disable-default-u-i="true"
+        map-type-id="ROADMAP"
+        zoom-control="true"
+        min-zoom="2"
+        styles="{{vm.mapStyles}}"
+      >
+        <!--Markers-->
+        <marker
+          ng-repeat="uni in vm.unis"
+          id="marker-{{uni.id}}"
+          position="{{uni.latlng.toString()}}"
+          icon="{{vm.MapConfig.marker}}"
+          on-click="vm.showUni(uni)"
+        >
+        </marker>
+
+        <!--Marker Info Window-->
+
+        <info-window id = "uni-info">
+          <div class = "marker-info">
+            <h4>{{vm.current_uni.name}}</h4>
+            <p>
+              <b>LatLng: </b>{{vm.current_uni.latlng.toString()}}<br/>
+              <b>Cpe id: </b>{{vm.current_uni.cpe_id}}<br/>
+              <b>Tenant: </b>{{vm.current_uni.tenant}}<br/>
+            </p>
+            <button ng-show="vm.canCreateEline" ng-click="vm.createConnection(vm.current_uni)()">Create connection</button>
+            <button ng-show="!vm.canCreateEline && !uni.eline_start" ng-click="vm.finishConnection(vm.current_uni)">Finish connection</button>
+          </div>
+        </info-window>
+
+        <!--Connections-->
+
+        <shape
+          ng-repeat="eline in vm.elines"
+          name="polyline"
+          id="eline-{{eline.id}}"
+          path="{{eline.path}}"
+          stroke-color="{{vm.colorLine(eline.backend_status)}}"
+          stroke-opacity="1.0"
+          stroke-weight="5"
+          on-click="vm.elinePanel({{eline}}, true)"
+        >
+        </shape>
+
+      </ng-map>
+
+    </div>
+
+  </div>
+</div>
+<!--"https://maps.googleapis.com/maps/api/js?key=AIzaSyA3rQOp26I5a21VQhwLal8Z1x3XGHjXfm4"-->
\ No newline at end of file
diff --git a/xos/gui/src/app/components/mngMap.component.ts b/xos/gui/src/app/components/mngMap.component.ts
new file mode 100644
index 0000000..4cde609
--- /dev/null
+++ b/xos/gui/src/app/components/mngMap.component.ts
@@ -0,0 +1,180 @@
+import {NgMap} from 'ngmap';
+import {Subscription} from 'rxjs/Subscription';
+import * as _ from 'lodash';
+
+declare var google;
+
+let self;
+
+export class MngMap {
+
+  static $inject = [
+    'NgMap',
+    'XosModelStore',
+    'AppConfig',
+    '$resource',
+    'XosSidePanel',
+    'XosModelDiscoverer',
+    'ModelRest',
+    'MapConfig',
+  ];
+
+  public unis = [];
+  public elines = [];
+  public cpilatlng = new Map();
+  public paths = [];
+  public bwps = [];
+  public map;
+  public panelOpen = false;
+  public createMode = false;
+  public canCreateEline = true;
+  public eline;
+  public current_uni;
+  public mapStyles = [{'featureType': 'administrative', 'elementType': 'labels.text.fill', 'stylers': [{'color': '#444444'}]}, {'featureType': 'landscape', 'elementType': 'all', 'stylers': [{'color': '#f2f2f2'}]}, {'featureType': 'poi', 'elementType': 'all', 'stylers': [{'visibility': 'off'}]}, {'featureType': 'road', 'elementType': 'all', 'stylers': [{'saturation': -100}, {'lightness': 45}]}, {'featureType': 'road.highway', 'elementType': 'all', 'stylers': [{'visibility': 'simplified'}]}, {'featureType': 'road.arterial', 'elementType': 'labels.icon', 'stylers': [{'visibility': 'off'}]}, {'featureType': 'transit', 'elementType': 'all', 'stylers': [{'visibility': 'off'}]}, {'featureType': 'water', 'elementType': 'all', 'stylers': [{'color': '#9ce1fc'}, {'visibility': 'on'}]}];
+
+  private uniSubscription: Subscription;
+  private elineSubscription: Subscription;
+  private bwpSubscription: Subscription;
+
+  constructor(
+    private NgMap: any,
+    private XosModelStore: any,
+    private AppConfig: any,
+    private $resource: any,
+    private XosSidePanel: any,
+    private XosModelDiscoverer: any,
+    private ModelRest: any,
+    private MapConfig: any,
+  ) {
+    self = this;
+  }
+
+  $onInit() {
+    this.NgMap.getMap().then(map => {
+      this.map = map;
+      this.uniSubscription = this.XosModelStore.query('UserNetworkInterface', '/metronet/usernetworkinterfaces/').subscribe(
+        res => {
+          this.unis = res;
+          this.renderMap(map);
+        }
+      );
+      this.elineSubscription = this.XosModelStore.query('ELine', '/metronet/elines/').subscribe(
+        res => {
+          this.elines = res;
+          this.createPaths();
+          this.renderMap(map);
+        }
+      );
+      this.bwpSubscription = this.XosModelStore.query('BandwidthProfile', '/metronet/bandwidthprofiles/').subscribe(
+        res => {
+          this.bwps = res;
+        }
+      );
+    });
+  }
+
+  $onDestroy() {
+    if (this.uniSubscription) {
+      this.uniSubscription.unsubscribe();
+    }
+  }
+
+  public renderMap(map: NgMap) {
+
+    let bounds = new google.maps.LatLngBounds();
+
+    for (let i = 0; i < self.unis.length; i++) {
+      self.unis[i].eline_start = false;
+      let curr = JSON.parse(self.unis[i].latlng);
+      this.cpilatlng.set(self.unis[i].cpe_id, curr);
+      let latlng = new google.maps.LatLng(curr[0], curr[1]);
+      bounds.extend(latlng);
+    }
+    map.setCenter(bounds.getCenter());
+    map.fitBounds(bounds);
+
+  }
+
+  public createPaths() {
+    this.elines.forEach((eline: any) => {
+      let latlng_start = this.cpilatlng.get(eline.connect_point_1_id);
+      let latlng_end = this.cpilatlng.get(eline.connect_point_2_id);
+      eline.path = [latlng_start, latlng_end];
+    });
+
+  }
+
+  public colorLine(eline_status : any) {
+    let status = parseInt(eline_status, 10);
+    switch (status) {
+      case 0:
+        return '#f39c12';
+      case 1:
+        return '#2ecc71';
+      default:
+        return '#e74c3c';
+    }
+
+  }
+
+  public showUni(e: any, uni: any) {
+    self.current_uni = uni;
+    self.map.showInfoWindow('uni-info', this);
+  }
+
+  // do not display backend status or ID in create mode
+
+  public elinePanel(e: any, eline: any, exists: boolean) {
+    self.panelOpen = !self.panelOpen;
+    if (exists) {
+      self.eline = _.find(self.elines, {id: eline.id});
+    }
+    self.XosSidePanel.toggleComponent('elineSide', {eline: self.eline, bwplist: self.bwps, mng: self}, false);
+    if (!self.panelOpen && self.createMode) {
+      self.createMode = false;
+      self.canCreateEline = true;
+      self.current_uni.eline_start = false;
+    }
+
+  }
+
+  public createConnection(uni: any) {
+    return () => {
+      self.canCreateEline = false;
+      self.createMode = true;
+      uni.eline_start = true;
+      self.current_uni = uni;
+      self.eline = {
+        name: uni.name,
+        uni1name: uni.name,
+        connect_point_1_id: uni.cpe_id,
+      };
+      self.elinePanel({}, self.eline, false);
+    };
+
+  }
+
+  public finishConnection(uni: any) {
+    self.eline.connect_point_2_id = uni.cpe_id;
+    if (self.eline.name === self.eline.uni1name) {
+      self.eline.name = self.eline.name + uni.name;
+    }
+    delete self.eline.uni1name;
+    const resource = this.ModelRest.getResource('/metronet/elines/');
+    let res = new resource({});
+    for (let attr in self.eline) {
+      if (true) {
+        res[attr] = self.eline[attr];
+      }
+
+    }
+    self.eline = res;
+  }
+
+}
+
+export const mngMap: angular.IComponentOptions = {
+  template: require('./mngMap.component.html'),
+  controllerAs: 'vm',
+  controller: MngMap,
+};
diff --git a/xos/gui/src/app/img/co.png b/xos/gui/src/app/img/co.png
new file mode 100644
index 0000000..3f07a6a
--- /dev/null
+++ b/xos/gui/src/app/img/co.png
Binary files differ
diff --git a/xos/gui/src/app/style/style.css b/xos/gui/src/app/style/style.css
new file mode 100644
index 0000000..59cdbe1
--- /dev/null
+++ b/xos/gui/src/app/style/style.css
@@ -0,0 +1,12 @@
+ng-map, [map-lazy-load]{
+    height: 600px;
+    width: 100%;
+}
+
+.marker-info > *{
+    color: #000000;
+}
+
+body > ui-view > xos > div > xos-side-panel > section > div:nth-child(1) {
+    display: none;
+}
\ No newline at end of file
diff --git a/xos/gui/src/app/style/style.scss b/xos/gui/src/app/style/style.scss
new file mode 100644
index 0000000..2506519
--- /dev/null
+++ b/xos/gui/src/app/style/style.scss
@@ -0,0 +1,8 @@
+ng-map, [map-lazy-load]{
+  height: 600px;
+  width: 100%;
+}
+
+.marker-info > p, .marker-info > h4{
+  color: #000000;
+}
\ No newline at end of file
diff --git a/xos/gui/src/index.html b/xos/gui/src/index.html
new file mode 100644
index 0000000..a76a8eb
--- /dev/null
+++ b/xos/gui/src/index.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en" ng-app="metro-net-gui">
+<head>
+  <meta charset="UTF-8">
+  <title>Metro Net GUI</title>
+  <link href="/xos/loader.css" rel="stylesheet">
+  <link href="/xos/app.css" rel="stylesheet">
+  <link href="./app/style/style.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>
+  <script src="/mapconstants.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/xos/gui/src/index.ts b/xos/gui/src/index.ts
new file mode 100644
index 0000000..e9f070b
--- /dev/null
+++ b/xos/gui/src/index.ts
@@ -0,0 +1,34 @@
+/// <reference path="../typings/index.d.ts" />
+import * as angular from 'angular';
+
+import 'angular-ui-router';
+import 'angular-resource';
+import 'angular-cookies';
+
+import 'ngmap';
+
+import routesConfig from './routes';
+import {mngMap} from './app/components/mngMap.component';
+import {elineSide} from './app/components/eline-side.component';
+
+angular.module('metro-net-gui', [
+    'ui.router',
+    'app',
+    'ngMap'
+  ])
+  .config(routesConfig)
+  .component('mngMap', mngMap)
+  .component('elineSide', elineSide)
+  .run(function(
+    $log: ng.ILogService,
+    $state: ng.ui.IStateService,
+    XosNavigationService: any,
+    XosComponentInjector: any) {
+    $log.info('[metro-net-gui] App is running');
+
+    XosNavigationService.add({
+      label: 'Metronet GUI',
+      state: 'xos.metro-net-gui',
+    });
+
+  });
diff --git a/xos/gui/src/routes.ts b/xos/gui/src/routes.ts
new file mode 100644
index 0000000..51d7217
--- /dev/null
+++ b/xos/gui/src/routes.ts
@@ -0,0 +1,12 @@
+export default routesConfig;
+
+function routesConfig($stateProvider: angular.ui.IStateProvider, $locationProvider: angular.ILocationProvider) {
+  $locationProvider.html5Mode(false).hashPrefix('');
+
+  $stateProvider
+    .state('xos.metro-net-gui', {
+      url: 'metro-net-gui',
+      parent: 'xos',
+      component: 'mngMap'
+    });
+}
diff --git a/xos/gui/tsconfig.json b/xos/gui/tsconfig.json
new file mode 100644
index 0000000..c516fb7
--- /dev/null
+++ b/xos/gui/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/xos/gui/tslint.json b/xos/gui/tslint.json
new file mode 100644
index 0000000..04d33e1
--- /dev/null
+++ b/xos/gui/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/xos/gui/typings.json b/xos/gui/typings.json
new file mode 100644
index 0000000..a830275
--- /dev/null
+++ b/xos/gui/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/xos/metro-net-onboard.yaml b/xos/metro-net-onboard.yaml
index 4608141..a686343 100644
--- a/xos/metro-net-onboard.yaml
+++ b/xos/metro-net-onboard.yaml
@@ -19,4 +19,4 @@
           synchronizer_run: metronetwork-synchronizer.py
           tosca_custom_types: metronet.yaml
           tosca_resource: tosca/resources/metronetservice.py
-          #admin: admin.py
+          #admin: admin.py
\ No newline at end of file
diff --git a/xos/synchronizer/metronetwork_from_api_config b/xos/synchronizer/metronetwork_from_api_config
index 0d6764c..a8f9639 100644
--- a/xos/synchronizer/metronetwork_from_api_config
+++ b/xos/synchronizer/metronetwork_from_api_config
@@ -1,6 +1,6 @@
 # Sets options for the synchronizer
 [observer]
-name=metronetwork
+name=metro-net
 dependency_graph=/opt/xos/synchronizers/metro-net/model-deps
 steps_dir=/opt/xos/synchronizers/metro-net/steps
 sys_dir=/opt/xos/synchronizers/metro-net/sys
diff --git a/xos/synchronizer/metronetwork_synchronizer_config b/xos/synchronizer/metronetwork_synchronizer_config
index 86847b9..1671354 100644
--- a/xos/synchronizer/metronetwork_synchronizer_config
+++ b/xos/synchronizer/metronetwork_synchronizer_config
@@ -22,11 +22,11 @@
 nova_enabled=True
 
 [observer]
-name=metronetwork
-dependency_graph=/opt/xos/synchronizers/metronetwork/model-deps
-steps_dir=/opt/xos/synchronizers/metronetwork/steps
-sys_dir=/opt/xos/synchronizers/metronetwork/sys
-deleters_dir=/opt/xos/synchronizers/metronetwork/deleters
+name=metro-net
+dependency_graph=/opt/xos/synchronizers/metro-net/model-deps
+steps_dir=/opt/xos/synchronizers/metro-net/steps
+sys_dir=/opt/xos/synchronizers/metro-net/sys
+deleters_dir=/opt/xos/synchronizers/metro-net/deleters
 log_file=console
 driver=None
 pretend=False