[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