merged master
Change-Id: I2498e2572b7bad691bc705e81e8fb2758115340f
diff --git a/views/ngXosLib/karma.conf.ci.js b/views/ngXosLib/karma.conf.ci.js
index 3bd017c..0726edd 100644
--- a/views/ngXosLib/karma.conf.ci.js
+++ b/views/ngXosLib/karma.conf.ci.js
@@ -18,8 +18,7 @@
// hack to avoid testing backbone implementation (they need to be removed)
viewFiles = viewFiles
.filter(f => f.indexOf('xosAdminSite') === -1)
- .filter(f => f.indexOf('xosCord') === -1)
- .filter(f => f.indexOf('xosHpc') === -1);
+ .filter(f => f.indexOf('xosCord') === -1);
viewFiles = viewFiles.filter(f => f.indexOf('js') >= 0).filter(f => f.match(/^xos[A-Z][a-z]+/)).map(f => `${viewDir}${f}`);
@@ -99,7 +98,7 @@
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
- reporters: ['dots', 'junit', 'coverage'],
+ reporters: ['dots', 'mocha', 'junit', 'coverage'],
junitReporter: {
outputDir: 'test-result',
diff --git a/views/ngXosLib/xosHelpers/spec/ui/table.test.js b/views/ngXosLib/xosHelpers/spec/ui/table.test.js
index 321a476..0ecdd2f 100644
--- a/views/ngXosLib/xosHelpers/spec/ui/table.test.js
+++ b/views/ngXosLib/xosHelpers/spec/ui/table.test.js
@@ -292,6 +292,7 @@
{categories: ['Film', 'Music']}
];
scope.config = {
+ filter: 'field',
columns: [
{
label: 'Categories',
@@ -342,11 +343,17 @@
});
it('should format data using the formatter property', () => {
- let td1 = $(element).find('tbody tr:first-child')[0];
+ let td1 = $(element).find('tbody:last-child tr:first-child')[0];
expect($(td1).text().trim()).toEqual('Formatted Content');
// the custom formatted should receive the entire object, otherwise is not so custom
expect(formatterFn).toHaveBeenCalledWith({categories: ['Film', 'Music']});
});
+
+ it('should not render the filter field', () => {
+ // displayed value is different from model val, filter would not work
+ let filter = $(element).find('tbody tr td')[0];
+ expect($(filter)).not.toContainElement('input');
+ });
});
describe('and is icon', () => {
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js
index 89ef192..4d9169b 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js
@@ -42,7 +42,7 @@
* label: 'Field Label',
* type: 'string' // options are: [date, boolean, number, email, string, select],
* validators: {
- * minlength: number,
+ * minlength: number,
maxlength: number,
required: boolean,
min: number,
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/table/table.component.js b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/table/table.component.js
index 94e2ab3..1e60458 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/table/table.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/table/table.component.js
@@ -369,7 +369,7 @@
<tr>
<td ng-repeat="col in vm.columns">
<input
- ng-if="col.type !== 'boolean' && col.type !== 'array' && col.type !== 'object'"
+ ng-if="col.type !== 'boolean' && col.type !== 'array' && col.type !== 'object' && col.type !== 'custom'"
class="form-control"
placeholder="Type to search by {{col.label}}"
type="text"
diff --git a/views/ngXosViews/hpc/.bowerrc b/views/ngXosViews/hpc/.bowerrc
new file mode 100644
index 0000000..e491038
--- /dev/null
+++ b/views/ngXosViews/hpc/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "src/vendor/"
+}
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/.eslintrc b/views/ngXosViews/hpc/.eslintrc
new file mode 100644
index 0000000..c852748
--- /dev/null
+++ b/views/ngXosViews/hpc/.eslintrc
@@ -0,0 +1,42 @@
+{
+ "ecmaFeatures": {
+ "blockBindings": true,
+ "forOf": true,
+ "destructuring": true,
+ "arrowFunctions": true,
+ "templateStrings": true
+ },
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true
+ },
+ "plugins": [
+ //"angular"
+ ],
+ "rules": {
+ "quotes": [2, "single"],
+ "camelcase": [1, {"properties": "always"}],
+ "no-underscore-dangle": 1,
+ "eqeqeq": [2, "smart"],
+ "no-alert": 1,
+ "key-spacing": [1, { "beforeColon": false, "afterColon": true }],
+ "indent": [2, 2],
+ "no-irregular-whitespace": 1,
+ "eol-last": 0,
+ "max-nested-callbacks": [2, 4],
+ "comma-spacing": [1, {"before": false, "after": true}],
+ "no-trailing-spaces": [1, { skipBlankLines: true }],
+ "no-unused-vars": [1, {"vars": "all", "args": "after-used"}],
+ "new-cap": 0,
+
+ //"angular/ng_module_name": [2, '/^xos\.*[a-z]*$/'],
+ //"angular/ng_controller_name": [2, '/^[a-z].*Ctrl$/'],
+ //"angular/ng_service_name": [2, '/^[A-Z].*Service$/'],
+ //"angular/ng_directive_name": [2, '/^[a-z]+[[A-Z].*]*$/'],
+ //"angular/ng_di": [0, "function or array"]
+ },
+ "globals" :{
+ "angular": true
+ }
+}
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/.gitignore b/views/ngXosViews/hpc/.gitignore
new file mode 100644
index 0000000..567aee4
--- /dev/null
+++ b/views/ngXosViews/hpc/.gitignore
@@ -0,0 +1,6 @@
+dist/
+src/vendor
+.tmp
+node_modules
+npm-debug.log
+dist/
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/bower.json b/views/ngXosViews/hpc/bower.json
new file mode 100644
index 0000000..085650b
--- /dev/null
+++ b/views/ngXosViews/hpc/bower.json
@@ -0,0 +1,33 @@
+{
+ "name": "xos-hpc",
+ "version": "0.0.0",
+ "authors": [
+ "Matteo Scandolo <matteo.scandolo@gmail.com>"
+ ],
+ "description": "The hpc view",
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "static/js/vendor/",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ },
+ "devDependencies": {
+ "jquery": "2.1.4",
+ "angular-mocks": "1.4.7",
+ "angular": "1.4.7",
+ "angular-ui-router": "0.2.15",
+ "angular-cookies": "1.4.7",
+ "angular-animate": "1.4.7",
+ "angular-resource": "1.4.7",
+ "lodash": "~4.11.1",
+ "bootstrap-css": "3.3.6",
+ "angular-chart.js": "~0.10.2",
+ "d3": "~3.5.17",
+ "angular-recursion": "~1.0.5"
+ }
+}
diff --git a/views/ngXosViews/hpc/gulp/build.js b/views/ngXosViews/hpc/gulp/build.js
new file mode 100644
index 0000000..3cefc6b
--- /dev/null
+++ b/views/ngXosViews/hpc/gulp/build.js
@@ -0,0 +1,164 @@
+'use strict';
+
+// BUILD
+//
+// The only purpose of this gulpfile is to build a XOS view and copy the correct files into
+// .html => dashboards
+// .js (minified and concat) => static/js
+//
+// The template are parsed and added to js with angular $templateCache
+
+var gulp = require('gulp');
+var ngAnnotate = require('gulp-ng-annotate');
+var uglify = require('gulp-uglify');
+var templateCache = require('gulp-angular-templatecache');
+var runSequence = require('run-sequence');
+var concat = require('gulp-concat-util');
+var del = require('del');
+var wiredep = require('wiredep');
+var angularFilesort = require('gulp-angular-filesort');
+var _ = require('lodash');
+var eslint = require('gulp-eslint');
+var inject = require('gulp-inject');
+var rename = require('gulp-rename');
+var replace = require('gulp-replace');
+var postcss = require('gulp-postcss');
+var autoprefixer = require('autoprefixer');
+var mqpacker = require('css-mqpacker');
+var csswring = require('csswring');
+
+const TEMPLATE_FOOTER = `
+angular.module('xos.hpc')
+.run(['$location', function(a){
+ a.path('/');
+}])
+`
+
+module.exports = function(options){
+
+ // delete previous builded file
+ gulp.task('clean', function(){
+ return del(
+ [
+ options.dashboards + 'xosHpc.html',
+ options.static + 'css/xosHpc.css'
+ ],
+ {force: true}
+ );
+ });
+
+ // minify css
+ gulp.task('css', function () {
+ var processors = [
+ autoprefixer({browsers: ['last 1 version']}),
+ mqpacker,
+ csswring
+ ];
+
+ gulp.src([
+ `${options.css}**/*.css`,
+ `!${options.css}dev.css`
+ ])
+ .pipe(postcss(processors))
+ .pipe(gulp.dest(options.tmp + '/css/'));
+ });
+
+ // copy css in correct folder
+ gulp.task('copyCss', ['wait'], function(){
+ return gulp.src([`${options.tmp}/css/*.css`])
+ .pipe(concat('xosHpc.css'))
+ .pipe(gulp.dest(options.static + 'css/'))
+ });
+
+ // compile and minify scripts
+ gulp.task('scripts', function() {
+ return gulp.src([
+ options.tmp + '**/*.js'
+ ])
+ .pipe(ngAnnotate())
+ .pipe(angularFilesort())
+ .pipe(concat('xosHpc.js'))
+ .pipe(concat.header('//Autogenerated, do not edit!!!\n'))
+ .pipe(concat.footer(TEMPLATE_FOOTER))
+ .pipe(uglify())
+ .pipe(gulp.dest(options.static + 'js/'));
+ });
+
+ // set templates in cache
+ gulp.task('templates', function(){
+ return gulp.src('./src/templates/*.html')
+ .pipe(templateCache({
+ module: 'xos.hpc',
+ root: 'templates/'
+ }))
+ .pipe(gulp.dest(options.tmp));
+ });
+
+ // copy html index to Django Folder
+ gulp.task('copyHtml', function(){
+ return gulp.src(options.src + 'index.html')
+ // remove dev dependencies from html
+ .pipe(replace(/<!-- bower:css -->(\n^<link.*)*\n<!-- endbower -->/gmi, ''))
+ .pipe(replace(/<!-- bower:js -->(\n^<script.*)*\n<!-- endbower -->/gmi, ''))
+ // injecting minified files
+ .pipe(
+ inject(
+ gulp.src([
+ options.static + 'js/vendor/xosHpcVendor.js',
+ options.static + 'js/xosHpc.js',
+ options.static + 'css/xosHpc.css'
+ ]),
+ {ignorePath: '/../../../xos/core/xoslib'}
+ )
+ )
+ .pipe(rename('xosHpc.html'))
+ .pipe(gulp.dest(options.dashboards));
+ });
+
+ // minify vendor js files
+ gulp.task('wiredep', function(){
+ var bowerDeps = wiredep().js;
+ if(!bowerDeps){
+ return;
+ }
+
+ // remove angular (it's already loaded)
+ _.remove(bowerDeps, function(dep){
+ return dep.indexOf('angular/angular.js') !== -1;
+ });
+
+ return gulp.src(bowerDeps)
+ .pipe(concat('xosHpcVendor.js'))
+ .pipe(uglify())
+ .pipe(gulp.dest(options.static + 'js/vendor/'));
+ });
+
+ gulp.task('lint', function () {
+ return gulp.src(['src/js/**/*.js'])
+ .pipe(eslint())
+ .pipe(eslint.format())
+ .pipe(eslint.failAfterError());
+ });
+
+ gulp.task('wait', function (cb) {
+ // setTimeout could be any async task
+ setTimeout(function () {
+ cb();
+ }, 1000);
+ });
+
+ gulp.task('build', function() {
+ runSequence(
+ 'clean',
+ 'sass',
+ 'templates',
+ 'babel',
+ 'scripts',
+ 'wiredep',
+ 'css',
+ 'copyCss',
+ 'copyHtml',
+ 'cleanTmp'
+ );
+ });
+};
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/gulp/server.js b/views/ngXosViews/hpc/gulp/server.js
new file mode 100644
index 0000000..c748eb6
--- /dev/null
+++ b/views/ngXosViews/hpc/gulp/server.js
@@ -0,0 +1,171 @@
+'use strict';
+
+var gulp = require('gulp');
+var browserSync = require('browser-sync').create();
+var inject = require('gulp-inject');
+var runSequence = require('run-sequence');
+var angularFilesort = require('gulp-angular-filesort');
+var babel = require('gulp-babel');
+var wiredep = require('wiredep').stream;
+var httpProxy = require('http-proxy');
+var del = require('del');
+var sass = require('gulp-sass');
+var fs = require('fs');
+var path = require('path');
+
+const environment = process.env.NODE_ENV;
+
+if(!fs.existsSync(path.join(__dirname, `../../../env/${environment || 'default'}.js`))){
+ if(!environment){
+ throw new Error('You should define a default.js config in /views/env folder.');
+ }
+ else{
+ throw new Error(`Since you are loading a custom environment, you should define a ${environment}.js config in /views/env folder.`);
+ }
+}
+
+var conf = require(path.join(__dirname, `../../../env/${environment || 'default'}.js`));
+
+var proxy = httpProxy.createProxyServer({
+ target: conf.host
+});
+
+
+proxy.on('error', function(error, req, res) {
+ res.writeHead(500, {
+ 'Content-Type': 'text/plain'
+ });
+
+ console.error('[Proxy]', error);
+});
+
+module.exports = function(options){
+
+ gulp.task('browser', function() {
+ browserSync.init({
+ startPath: '#/',
+ snippetOptions: {
+ rule: {
+ match: /<!-- browserSync -->/i
+ }
+ },
+ server: {
+ baseDir: options.src,
+ routes: {
+ '/xos/core/xoslib/static/js/vendor': options.helpers,
+ '/xos/core/static': options.static + '../../static/'
+ },
+ middleware: function(req, res, next){
+ if(
+ req.url.indexOf('/api/') !== -1,
+ req.url.indexOf('/xoslib/hpcview') !== -1
+ ){
+ if(conf.xoscsrftoken && conf.xossessionid){
+ req.headers.cookie = `xoscsrftoken=${conf.xoscsrftoken}; xossessionid=${conf.xossessionid}`;
+ req.headers['x-csrftoken'] = conf.xoscsrftoken;
+ }
+ proxy.web(req, res);
+ }
+ else{
+ next();
+ }
+ }
+ }
+ });
+
+ gulp.watch(options.src + 'js/**/*.js', ['js-watch']);
+ gulp.watch(options.src + 'vendor/**/*.js', ['bower'], function(){
+ browserSync.reload();
+ });
+ gulp.watch(options.src + '**/*.html', function(){
+ browserSync.reload();
+ });
+ gulp.watch(options.css + '**/*.css', function(){
+ browserSync.reload();
+ });
+ gulp.watch(`${options.sass}/**/*.scss`, ['sass'], function(){
+ browserSync.reload();
+ });
+
+ gulp.watch([
+ options.helpers + 'ngXosHelpers.js',
+ options.static + '../../static/xosNgLib.css'
+ ], function(){
+ browserSync.reload();
+ });
+ });
+
+ // compile sass
+ gulp.task('sass', function () {
+ return gulp.src(`${options.sass}/**/*.scss`)
+ .pipe(sass().on('error', sass.logError))
+ .pipe(gulp.dest(options.css));
+ });
+
+ // transpile js with sourceMaps
+ gulp.task('babel', function(){
+ return gulp.src(options.scripts + '**/*.js')
+ .pipe(babel({sourceMaps: true}))
+ .pipe(gulp.dest(options.tmp));
+ });
+
+ // inject scripts
+ gulp.task('injectScript', ['cleanTmp', 'babel'], function(){
+ return gulp.src(options.src + 'index.html')
+ .pipe(
+ inject(
+ gulp.src([
+ options.tmp + '**/*.js',
+ options.helpers + 'ngXosHelpers.js'
+ ])
+ .pipe(angularFilesort()),
+ {
+ ignorePath: [options.src, '/../../ngXosLib']
+ }
+ )
+ )
+ .pipe(gulp.dest(options.src));
+ });
+
+ // inject CSS
+ gulp.task('injectCss', function(){
+ return gulp.src(options.src + 'index.html')
+ .pipe(
+ inject(
+ gulp.src([
+ options.src + 'css/*.css',
+ options.static + '../../static/xosNgLib.css'
+ ]),
+ {
+ ignorePath: [options.src]
+ }
+ )
+ )
+ .pipe(gulp.dest(options.src));
+ });
+
+ // inject bower dependencies with wiredep
+ gulp.task('bower', function () {
+ return gulp.src(options.src + 'index.html')
+ .pipe(wiredep({devDependencies: true}))
+ .pipe(gulp.dest(options.src));
+ });
+
+ gulp.task('js-watch', ['injectScript'], function(){
+ browserSync.reload();
+ });
+
+ gulp.task('cleanTmp', function(){
+ return del([options.tmp + '**/*']);
+ });
+
+ gulp.task('serve', function() {
+ runSequence(
+ 'sass',
+ 'bower',
+ 'injectScript',
+ 'injectCss',
+ ['browser']
+ );
+ });
+};
diff --git a/views/ngXosViews/hpc/gulpfile.js b/views/ngXosViews/hpc/gulpfile.js
new file mode 100644
index 0000000..08df554
--- /dev/null
+++ b/views/ngXosViews/hpc/gulpfile.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var gulp = require('gulp');
+var wrench = require('wrench');
+
+var options = {
+ src: 'src/',
+ css: 'src/css/',
+ sass: 'src/sass/',
+ scripts: 'src/js/',
+ tmp: 'src/.tmp',
+ dist: 'dist/',
+ api: '../../ngXosLib/api/',
+ helpers: '../../../xos/core/xoslib/static/js/vendor/',
+ static: '../../../xos/core/xoslib/static/', // this is the django static folder
+ dashboards: '../../../xos/core/xoslib/dashboards/' // this is the django html folder
+};
+
+wrench.readdirSyncRecursive('./gulp')
+.map(function(file) {
+ require('./gulp/' + file)(options);
+});
+
+gulp.task('default', function () {
+ gulp.start('build');
+});
diff --git a/views/ngXosViews/hpc/karma.conf.js b/views/ngXosViews/hpc/karma.conf.js
new file mode 100644
index 0000000..4123be9
--- /dev/null
+++ b/views/ngXosViews/hpc/karma.conf.js
@@ -0,0 +1,88 @@
+// Karma configuration
+// Generated on Tue Oct 06 2015 09:27:10 GMT+0000 (UTC)
+
+/* eslint indent: [2,2], quotes: [2, "single"]*/
+
+/*eslint-disable*/
+var wiredep = require('wiredep');
+var path = require('path');
+
+var bowerComponents = wiredep( {devDependencies: true} )[ 'js' ].map(function( file ){
+ return path.relative(process.cwd(), file);
+});
+
+module.exports = function(config) {
+/*eslint-enable*/
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: bowerComponents.concat([
+ '../../../xos/core/xoslib/static/js/vendor/ngXosVendor.js',
+ '../../../xos/core/xoslib/static/js/vendor/ngXosHelpers.js',
+ 'src/js/**/*.js',
+ 'spec/**/*.mock.js',
+ 'spec/**/*.test.js',
+ 'src/**/*.html'
+ ]),
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'src/js/**/*.js': ['babel'],
+ 'spec/**/*.test.js': ['babel'],
+ 'src/**/*.html': ['ng-html2js']
+ },
+
+ ngHtml2JsPreprocessor: {
+ stripPrefix: 'src/', //strip the src path from template url (http://stackoverflow.com/questions/22869668/karma-unexpected-request-when-testing-angular-directive-even-with-ng-html2js)
+ moduleName: 'templates' // define the template module name
+ },
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['mocha'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};
diff --git a/views/ngXosViews/hpc/package.json b/views/ngXosViews/hpc/package.json
new file mode 100644
index 0000000..ab43380
--- /dev/null
+++ b/views/ngXosViews/hpc/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "xos-hpc",
+ "version": "1.0.0",
+ "description": "Angular Application for XOS, created with generator-xos",
+ "scripts": {
+ "prestart": "npm install && bower install",
+ "start": "gulp serve",
+ "prebuild": "npm install && bower install",
+ "build": "gulp",
+ "test": "karma start",
+ "test:ci": "karma start --single-run",
+ "lint": "eslint src/js/"
+ },
+ "keywords": [
+ "XOS",
+ "Angular",
+ "XOSlib"
+ ],
+ "author": "Matteo Scandolo",
+ "license": "MIT",
+ "dependencies": {},
+ "devDependencies": {
+ "autoprefixer": "^6.3.3",
+ "browser-sync": "^2.9.11",
+ "css-mqpacker": "^4.0.0",
+ "csswring": "^4.2.1",
+ "del": "^2.0.2",
+ "easy-mocker": "^1.2.0",
+ "eslint": "^1.8.0",
+ "eslint-plugin-angular": "linkmesrl/eslint-plugin-angular",
+ "gulp": "^3.9.0",
+ "gulp-angular-filesort": "^1.1.1",
+ "gulp-angular-templatecache": "^1.8.0",
+ "gulp-babel": "^5.3.0",
+ "gulp-concat": "^2.6.0",
+ "gulp-concat-util": "^0.5.5",
+ "gulp-eslint": "^1.0.0",
+ "gulp-inject": "^3.0.0",
+ "gulp-minify-html": "^1.0.4",
+ "gulp-ng-annotate": "^1.1.0",
+ "gulp-postcss": "^6.0.1",
+ "gulp-rename": "^1.2.2",
+ "gulp-replace": "^0.5.4",
+ "gulp-sass": "^2.2.0",
+ "gulp-uglify": "^1.4.2",
+ "http-proxy": "^1.12.0",
+ "ink-docstrap": "^0.5.2",
+ "jasmine-core": "~2.3.4",
+ "karma": "^0.13.14",
+ "karma-babel-preprocessor": "~5.2.2",
+ "karma-coverage": "^0.5.3",
+ "karma-jasmine": "~0.3.6",
+ "karma-mocha-reporter": "~1.1.1",
+ "karma-ng-html2js-preprocessor": "^0.2.0",
+ "karma-phantomjs-launcher": "~0.2.1",
+ "lodash": "^3.10.1",
+ "phantomjs": "^1.9.19",
+ "proxy-middleware": "^0.15.0",
+ "run-sequence": "^1.1.4",
+ "wiredep": "^3.0.0-beta",
+ "wrench": "^1.5.8"
+ }
+}
diff --git a/views/ngXosViews/hpc/spec/sample.test.js b/views/ngXosViews/hpc/spec/sample.test.js
new file mode 100644
index 0000000..9949b08
--- /dev/null
+++ b/views/ngXosViews/hpc/spec/sample.test.js
@@ -0,0 +1,24 @@
+'use strict';
+
+describe('The Hpc View', () => {
+
+ var scope, element, isolatedScope, httpBackend;
+
+ beforeEach(module('xos.hpc'));
+ beforeEach(module('templates'));
+
+ beforeEach(inject(function($httpBackend, $compile, $rootScope){
+
+ scope = $rootScope.$new();
+ element = angular.element('<hpcs-list></hpcs-list>');
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should define 2 tables', () => {
+ expect(isolatedScope.routerConfig).toBeDefined();
+ expect(isolatedScope.cacheConfig).toBeDefined();
+ });
+
+});
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/src/css/main.css b/views/ngXosViews/hpc/src/css/main.css
new file mode 100644
index 0000000..24f5982
--- /dev/null
+++ b/views/ngXosViews/hpc/src/css/main.css
@@ -0,0 +1,2 @@
+#xosHpc .btn.btn-reload {
+ margin-top: 20px; }
diff --git a/views/ngXosViews/hpc/src/index.html b/views/ngXosViews/hpc/src/index.html
new file mode 100644
index 0000000..c945c27
--- /dev/null
+++ b/views/ngXosViews/hpc/src/index.html
@@ -0,0 +1,35 @@
+<!-- browserSync -->
+<!-- bower:css -->
+<link rel="stylesheet" href="vendor/bootstrap-css/css/bootstrap.min.css" />
+<link rel="stylesheet" href="vendor/angular-chart.js/dist/angular-chart.css" />
+<!-- endbower -->
+<!-- endcss -->
+<!-- inject:css -->
+<link rel="stylesheet" href="/css/main.css">
+<link rel="stylesheet" href="/../../../xos/core/static/xosNgLib.css">
+<!-- endinject -->
+
+<div ng-app="xos.hpc" id="xosHpc" class="container-fluid">
+ <div ui-view></div>
+</div>
+
+<!-- bower:js -->
+<script src="vendor/jquery/dist/jquery.js"></script>
+<script src="vendor/angular/angular.js"></script>
+<script src="vendor/angular-mocks/angular-mocks.js"></script>
+<script src="vendor/angular-ui-router/release/angular-ui-router.js"></script>
+<script src="vendor/angular-cookies/angular-cookies.js"></script>
+<script src="vendor/angular-animate/angular-animate.js"></script>
+<script src="vendor/angular-resource/angular-resource.js"></script>
+<script src="vendor/lodash/lodash.js"></script>
+<script src="vendor/bootstrap-css/js/bootstrap.min.js"></script>
+<script src="vendor/Chart.js/Chart.js"></script>
+<script src="vendor/angular-chart.js/dist/angular-chart.js"></script>
+<script src="vendor/d3/d3.js"></script>
+<script src="vendor/angular-recursion/angular-recursion.js"></script>
+<!-- endbower -->
+<!-- endjs -->
+<!-- inject:js -->
+<script src="/../../../xos/core/xoslib/static/js/vendor/ngXosHelpers.js"></script>
+<script src="/.tmp/main.js"></script>
+<!-- endinject -->
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/src/js/main.js b/views/ngXosViews/hpc/src/js/main.js
new file mode 100644
index 0000000..ca6a5a0
--- /dev/null
+++ b/views/ngXosViews/hpc/src/js/main.js
@@ -0,0 +1,134 @@
+'use strict';
+
+angular.module('xos.hpc', [
+ 'ngResource',
+ 'ngCookies',
+ 'ui.router',
+ 'xos.helpers'
+])
+.config(($stateProvider) => {
+ $stateProvider
+ .state('hpc-list', {
+ url: '/',
+ template: '<hpcs-list></hpcs-list>'
+ });
+})
+.config(function($httpProvider){
+ $httpProvider.interceptors.push('NoHyperlinks');
+})
+.service('Hpc', function($q, $http){
+ this.query = (params) => {
+ const d = $q.defer();
+
+ $http.get('/xoslib/hpcview', {params: params})
+ .then((res) => {
+ d.resolve(res.data);
+ })
+ .catch(d.reject);
+
+ return {$promise: d.promise};
+ };
+})
+.directive('hpcsList', function(){
+ return {
+ restrict: 'E',
+ scope: {},
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/hpc-list.tpl.html',
+ controller: function(Hpc){
+
+ const secondsToHms = d => {
+ d = Number(d);
+ var h = Math.floor(d / 3600);
+ var m = Math.floor(d % 3600 / 60);
+ var s = Math.floor(d % 3600 % 60);
+ return ((h > 0 ? h + 'h ' + (m < 10 ? '0' :'') :'') + m + 'm ' + (s < 10 ? '0' :'') + s + 's');
+ };
+
+ const toDuration = (property) => {
+ return (item) => {
+ if(!angular.isNumber(item[property])){
+ return item[property]
+ }
+ return secondsToHms(item[property]);
+ }
+ };
+
+ this.routerConfig = {
+ filter: 'field',
+ order: true,
+ columns: [
+ {
+ label: 'Name',
+ prop: 'name'
+ },
+ {
+ label: 'Ip Address',
+ prop: 'ip'
+ },
+ {
+ label: 'Record Checker',
+ prop: 'watcher.DNS.msg'
+ },
+ {
+ label: 'Name Servers',
+ prop: 'nameservers',
+ type: 'array'
+ },
+ {
+ label: 'Dns Demux Config Age',
+ prop: 'dnsdemux_config_age',
+ type: 'custom',
+ formatter: toDuration('dnsdemux_config_age')
+ },
+ {
+ label: 'Dns Redir Config Age',
+ prop: 'dnsredir_config_age',
+ type: 'custom',
+ formatter: toDuration('dnsredir_config_age')
+ }
+ ]
+ };
+
+ this.cacheConfig = {
+ filter: 'field',
+ order: true,
+ columns: [
+ {
+ label: 'Name',
+ prop: 'name'
+ },
+ {
+ label: 'Prober',
+ prop: 'watcher.HPC-hb.msg'
+ },
+ {
+ label: 'Fetcher',
+ prop: 'watcher.HPC-fetch.msg'
+ },
+ {
+ label: 'Config Age',
+ prop: 'config_age',
+ type: 'custom',
+ formatter: toDuration('config_age')
+ }
+ ]
+ };
+
+
+ this.fetch = () => {
+ Hpc.query().$promise
+ .then((hpcs) => {
+ this.routers = hpcs[0].dnsdemux;
+ this.caches = hpcs[0].hpc;
+ })
+ .catch((e) => {
+ throw new Error(e);
+ });
+ };
+
+ this.fetch();
+ }
+ };
+});
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/src/sass/main.scss b/views/ngXosViews/hpc/src/sass/main.scss
new file mode 100644
index 0000000..c646843
--- /dev/null
+++ b/views/ngXosViews/hpc/src/sass/main.scss
@@ -0,0 +1,7 @@
+@import '../../../../style/sass/lib/_variables.scss';
+
+#xosHpc {
+ .btn.btn-reload {
+ margin-top: $line-height-computed;
+ }
+}
\ No newline at end of file
diff --git a/views/ngXosViews/hpc/src/templates/hpc-list.tpl.html b/views/ngXosViews/hpc/src/templates/hpc-list.tpl.html
new file mode 100644
index 0000000..138323f
--- /dev/null
+++ b/views/ngXosViews/hpc/src/templates/hpc-list.tpl.html
@@ -0,0 +1,28 @@
+<div class="container-fluid">
+ <div class="row">
+ <div class="col-xs-10">
+ <h1>Request Routers</h1>
+ </div>
+ <div class="col-xs-2 text-right">
+ <a href="" ng-click="vm.fetch()" class="btn btn-primary btn-reload">
+ <i class="glyphicon glyphicon-refresh"></i>
+ Refresh
+ </a>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12">
+ <xos-table config="vm.routerConfig" data="vm.routers"></xos-table>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12">
+ <h1>HyperCache</h1>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-12">
+ <xos-table config="vm.cacheConfig" data="vm.caches"></xos-table>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/xos/configurations/common/wait_for_onboarding_ready.sh b/xos/configurations/common/wait_for_onboarding_ready.sh
index 9606dbb..dbfdde8 100755
--- a/xos/configurations/common/wait_for_onboarding_ready.sh
+++ b/xos/configurations/common/wait_for_onboarding_ready.sh
@@ -17,6 +17,7 @@
echo "$2 is onboarded"
exit 0
fi
+ echo -ne "."
sleep 1
# RUNNING_CONTAINER=`sudo docker ps|grep "xos"|awk '{print $$NF}'`
# if [[ $RUNNING_CONTAINER == "" ]]; then
diff --git a/xos/configurations/cord-pod/docker-compose-bootstrap.yml b/xos/configurations/cord-pod/docker-compose-bootstrap.yml
index 83bb685..93ccb71 100644
--- a/xos/configurations/cord-pod/docker-compose-bootstrap.yml
+++ b/xos/configurations/cord-pod/docker-compose-bootstrap.yml
@@ -40,100 +40,6 @@
max-size: "100k"
max-file: "5"
-#xos_synchronizer_onos:
-# image: xosproject/xos-synchronizer-openstack
-# command: bash -c "python /opt/xos/synchronizers/onos/onos-synchronizer.py -C /opt/xos/synchronizers/onos/onos_synchronizer_config"
-# labels:
-# org.xosproject.kind: synchronizer
-# org.xosproject.target: onos
-# links:
-# - xos_db
-# volumes:
-# - .:/root/setup:ro
-# - ./id_rsa:/opt/xos/synchronizers/onos/onos_key:ro # private key
-# log_driver: "json-file"
-# log_opt:
-# max-size: "100k"
-# max-file: "5"
-
-#xos_synchronizer_vcpe:
-# image: xosproject/xos-synchronizer-openstack
-# command: bash -c "sleep 120; cp /root/setup/node_key /opt/xos/synchronizers/vcpe/; chmod 0600 /opt/xos/synchronizers/vcpe/node_key; python /opt/xos/synchronizers/vcpe/vcpe-synchronizer.py -C /root/setup/files/vcpe_synchronizer_config"
-# labels:
-# org.xosproject.kind: synchronizer
-# org.xosproject.target: vcpe
-# links:
-# - xos_db
-# volumes:
-# - .:/root/setup:ro
-# - ./id_rsa:/opt/xos/synchronizers/vcpe/vcpe_private_key:ro # private key
-# log_driver: "json-file"
-# log_opt:
-# max-size: "100k"
-# max-file: "5"
-
-#xos_synchronizer_vtn:
-# image: xosproject/xos-synchronizer-openstack
-# command: bash -c "sleep 120; python /opt/xos/synchronizers/vtn/vtn-synchronizer.py -C /opt/xos/synchronizers/vtn/vtn_synchronizer_config"
-# labels:
-# org.xosproject.kind: synchronizer
-# org.xosproject.target: vtn
-# links:
-# - xos_db
-# volumes:
-# - .:/root/setup:ro
-# log_driver: "json-file"
-# log_opt:
-# max-size: "100k"
-# max-file: "5"
-
-xos_synchronizer_monitoring_channel:
- image: xosproject/xos-synchronizer-openstack
- command: bash -c "sleep 120; python /opt/xos/synchronizers/monitoring_channel/monitoring_channel_synchronizer.py -C /root/setup/files/monitoring_channel_synchronizer_config"
- labels:
- org.xosproject.kind: synchronizer
- org.xosproject.target: monitoring_channel
- links:
- - xos_db
- volumes:
- - .:/root/setup:ro
- - ./id_rsa:/opt/xos/synchronizers/monitoring_channel/monitoring_channel_private_key:ro # private key
- log_driver: "json-file"
- log_opt:
- max-size: "100k"
- max-file: "5"
-
-#xos_synchronizer_vtr:
-# image: xosproject/xos-synchronizer-openstack
-# command: bash -c "sleep 120; cp /root/setup/node_key /opt/xos/synchronizers/vtr/; chmod 0600 /opt/xos/synchronizers/vtr/node_key; python /opt/xos/synchronizers/vtr/vtr-synchronizer.py -C /root/setup/files/vtr_synchronizer_config"
-# labels:
-# org.xosproject.kind: synchronizer
-# org.xosproject.target: vtr
-# links:
-# - xos_db
-# volumes:
-# - .:/root/setup:ro
-# - ./id_rsa:/opt/xos/synchronizers/vtr/vcpe_private_key:ro # private key
-# log_driver: "json-file"
-# log_opt:
-# max-size: "100k"
-# max-file: "5"
-
-#xos_synchronizer_fabric:
-# image: xosproject/xos-synchronizer-openstack
-# command: bash -c "sleep 120; python /opt/xos/synchronizers/fabric/fabric-synchronizer.py -C /opt/xos/synchronizers/fabric/fabric_synchronizer_config"
-# labels:
-# org.xosproject.kind: synchronizer
-# org.xosproject.target: fabric
-# links:
-# - xos_db
-# volumes:
-# - .:/root/setup:ro
-# log_driver: "json-file"
-# log_opt:
-# max-size: "100k"
-# max-file: "5"
-
xos_bootstrap_ui:
command: python /opt/xos/manage.py runserver 0.0.0.0:81 --insecure --makemigrations
image: xosproject/xos
@@ -146,9 +52,6 @@
- ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
- ./xos_cord_config:/opt/xos/xos_configuration/xos_cord_config:ro
- ../vtn/files/xos_vtn_config:/opt/xos/xos_configuration/xos_vtn_config:ro
-# - ./id_rsa.pub:/opt/xos/synchronizers/onos/onos_key.pub:ro
-# - ./id_rsa.pub:/opt/xos/synchronizers/vcpe/vcpe_public_key:ro
-# - ./id_rsa.pub:/opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key:ro
log_driver: "json-file"
log_opt:
max-size: "100k"
diff --git a/xos/configurations/frontend/Makefile b/xos/configurations/frontend/Makefile
index 8845196..b322ffe 100644
--- a/xos/configurations/frontend/Makefile
+++ b/xos/configurations/frontend/Makefile
@@ -41,7 +41,7 @@
sudo docker-compose -f $(BOOTSTRAP_YML) stop
showlogs:
- sudo docker-compose logs
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) logs
rm: stop
test ! -s $(DOCKER_COMPOSE_YML) || sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) rm
diff --git a/xos/configurations/test-standalone/Makefile b/xos/configurations/test-standalone/Makefile
index 9807e5d..3566d80 100644
--- a/xos/configurations/test-standalone/Makefile
+++ b/xos/configurations/test-standalone/Makefile
@@ -44,16 +44,20 @@
sudo bash -c "echo somekey > key_import/vsg_rsa.pub"
sudo bash -c "echo somekey > key_import/volt_rsa"
sudo bash -c "echo somekey > key_import/volt_rsa.pub"
+ sudo bash -c "echo somekey > key_import/onos_rsa"
+ sudo bash -c "echo somekey > key_import/onos_rsa.pub"
sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run xos_bootstrap_ui python /opt/xos/tosca/run.py None /opt/xos/configurations/common/disable-onboarding.yaml
sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run xos_bootstrap_ui python /opt/xos/tosca/run.py None /opt/xos/onboard/vrouter/vrouter-onboard.yaml
sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run xos_bootstrap_ui python /opt/xos/tosca/run.py None /opt/xos/onboard/volt/volt-onboard.yaml
sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run xos_bootstrap_ui python /opt/xos/tosca/run.py None /opt/xos/onboard/vsg/vsg-onboard.yaml
sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run xos_bootstrap_ui python /opt/xos/tosca/run.py None /opt/xos/onboard/vtr/vtr-onboard.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run xos_bootstrap_ui python /opt/xos/tosca/run.py None /opt/xos/onboard/onos/onos-onboard.yaml
sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run xos_bootstrap_ui python /opt/xos/tosca/run.py None /opt/xos/configurations/common/enable-onboarding.yaml
bash ../common/wait_for_onboarding_ready.sh 9998 services/vrouter
bash ../common/wait_for_onboarding_ready.sh 9998 services/volt
bash ../common/wait_for_onboarding_ready.sh 9998 services/vsg
bash ../common/wait_for_onboarding_ready.sh 9998 services/vtr
+ bash ../common/wait_for_onboarding_ready.sh 9998 services/onos
bash ../common/wait_for_onboarding_ready.sh 9998 xos
bash ../common/wait_for_xos_port.sh 9999
@@ -97,8 +101,8 @@
sudo docker-compose logs
rm: stop
- test ! -s $(DOCKER_COMPOSE_YML) || sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) rm
- sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) rm
+ test ! -s $(DOCKER_COMPOSE_YML) || sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) rm -f
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) rm -f
docker-clean:
sudo docker rm -f $(shell sudo docker ps -aq)
diff --git a/xos/core/xoslib/dashboards/xosHpc.html b/xos/core/xoslib/dashboards/xosHpc.html
index 138a9a1..78a7639 100644
--- a/xos/core/xoslib/dashboards/xosHpc.html
+++ b/xos/core/xoslib/dashboards/xosHpc.html
@@ -1,37 +1,16 @@
-<script src="{{ STATIC_URL }}/js/vendor/underscore-min.js"></script>
-<script src="{{ STATIC_URL }}/js/vendor/backbone.js"></script>
-<script src="{{ STATIC_URL }}/js/vendor/backbone.syphon.js"></script>
-<script src="{{ STATIC_URL }}/js/vendor/backbone.wreqr.js"></script>
-<script src="{{ STATIC_URL }}/js/vendor/backbone.babysitter.js"></script>
-<script src="{{ STATIC_URL }}/js/vendor/backbone.marionette.js"></script>
+<!-- browserSync -->
-<link rel="stylesheet" type="text/css" href="{% static 'suit/css/jquery-ui.min.css' %}" media="all" >
-<link rel="stylesheet" type="text/css" href="{% static 'css/xosTenantDashboard.css' %}" media="all" >
-<link rel="stylesheet" type="text/css" href="{% static 'css/xosAdminSite.css' %}" media="all" >
+<!-- endcss -->
+<!-- inject:css -->
+<link rel="stylesheet" href="/static/css/xosHpc.css">
+<!-- endinject -->
-<script src="{{ STATIC_URL }}/js/xoslib/xos-util.js"></script>
-<script src="{{ STATIC_URL }}/js/xoslib/xos-defaults.js"></script>
-<script src="{{ STATIC_URL }}/js/xoslib/xos-validators.js"></script>
-<script src="{{ STATIC_URL }}/js/xoslib/xos-backbone.js"></script>
-<script src="{{ STATIC_URL }}/js/xoslib/xosHelper.js"></script>
-<script src="{{ STATIC_URL }}/js/xosHpc.js"></script>
-
-<div id="xos-hpc-view-panel"> <!-- contentPanel"> -->
-<div id="contentTitle">
+<div ng-app="xos.hpc" id="xosHpc" class="container-fluid">
+ <div ui-view></div>
</div>
-<div id="contentInner">
-<dic id="warnings"></div>
-
-<h2>Request Routers</h2>
-<div id="xos-hpc-dns"></div>
-
-<br>
-<h2>HyperCache</h2>
-<div id="xos-hpc-hpc"></div>
-
-</div> <!-- end contentInner -->
-</div> <!-- end contentPanel -->
-
-{% include 'xosAdmin.html' %}
+<!-- endjs -->
+<!-- inject:js -->
+<script src="/static/js/xosHpc.js"></script>
+<!-- endinject -->
diff --git a/xos/core/xoslib/static/css/xosHpc.css b/xos/core/xoslib/static/css/xosHpc.css
new file mode 100644
index 0000000..e9458ad
--- /dev/null
+++ b/xos/core/xoslib/static/css/xosHpc.css
@@ -0,0 +1 @@
+#xosHpc .btn.btn-reload{margin-top:20px}
\ No newline at end of file
diff --git a/xos/core/xoslib/static/js/vendor/ngXosHelpers.js b/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
index f511337..eebc675 100644
--- a/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
+++ b/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
@@ -1,2 +1,2 @@
-"use strict";function _toConsumableArray(e){if(Array.isArray(e)){for(var n=0,o=Array(e.length);n<e.length;n++)o[n]=e[n];return o}return Array.from(e)}!function(){angular.module("xos.uiComponents",["chart.js","RecursionHelper"])}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").directive("xosSmartTable",function(){return{restrict:"E",scope:{config:"="},template:'\n <div class="row" ng-show="vm.data.length > 0">\n <div class="col-xs-12 text-right">\n <a href="" class="btn btn-success" ng-click="vm.createItem()">\n Add\n </a>\n </div>\n </div>\n <div class="row">\n <div class="col-xs-12 table-responsive">\n <xos-table config="vm.tableConfig" data="vm.data"></xos-table>\n </div>\n </div>\n <div class="panel panel-default" ng-show="vm.detailedItem">\n <div class="panel-heading">\n <div class="row">\n <div class="col-xs-11">\n <h3 class="panel-title" ng-show="vm.detailedItem.id">Update {{vm.config.resource}} {{vm.detailedItem.id}}</h3>\n <h3 class="panel-title" ng-show="!vm.detailedItem.id">Create {{vm.config.resource}} item</h3>\n </div>\n <div class="col-xs-1">\n <a href="" ng-click="vm.cleanForm()">\n <i class="glyphicon glyphicon-remove pull-right"></i>\n </a>\n </div>\n </div>\n </div>\n <div class="panel-body">\n <xos-form config="vm.formConfig" ng-model="vm.detailedItem"></xos-form>\n </div>\n </div>\n <xos-alert config="{type: \'success\', closeBtn: true}" show="vm.responseMsg">{{vm.responseMsg}}</xos-alert>\n <xos-alert config="{type: \'danger\', closeBtn: true}" show="vm.responseErr">{{vm.responseErr}}</xos-alert>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","LabelFormatter","_","XosFormHelpers",function(e,n,o,i){var t=this;this.responseMsg=!1,this.responseErr=!1,this.tableConfig={columns:[],actions:[{label:"delete",icon:"remove",cb:function(e){t.Resource["delete"]({id:e.id}).$promise.then(function(){o.remove(t.data,function(n){return n.id===e.id}),t.responseMsg=t.config.resource+" with id "+e.id+" successfully deleted"})["catch"](function(n){t.responseErr=n.data.detail||"Error while deleting "+t.config.resource+" with id "+e.id})},color:"red"},{label:"details",icon:"search",cb:function(e){t.detailedItem=e}}],classes:"table table-striped table-bordered table-responsive",filter:"field",order:!0,pagination:{pageSize:10}},this.formConfig={exclude:this.config.hiddenFields,fields:{},formName:this.config.resource+"Form",actions:[{label:"Save",icon:"ok",cb:function(e){var n=void 0,o=!0;e.id?(n=e.$update(),o=!1):n=e.$save(),n.then(function(n){o&&t.data.push(angular.copy(n)),delete t.detailedItem,t.responseMsg=t.config.resource+" with id "+e.id+" successfully saved"})["catch"](function(n){t.responseErr=n.data.detail||"Error while saving "+t.config.resource+" with id "+e.id})},"class":"success"}]},this.cleanForm=function(){delete t.detailedItem},this.createItem=function(){t.detailedItem=new t.Resource},this.Resource=e.get(this.config.resource);var r=function(){t.Resource.query().$promise.then(function(e){if(!e[0])return void(t.data=e);var r=e[0],a=Object.keys(r);o.remove(a,function(e){return"id"===e||"validators"===e}),angular.isArray(t.config.hiddenFields)&&(a=o.difference(a,t.config.hiddenFields));var s=a.map(function(e){return n.format(e)});a.forEach(function(e,n){var o={label:s[n],prop:e};angular.isString(r[e])&&"undefined"!=typeof r[e]&&(o.type=_typeof(r[e])),t.tableConfig.columns.push(o)}),a.forEach(function(e,o){t.formConfig.fields[e]={label:n.format(s[o]).replace(":",""),type:i._getFieldFormat(r[e])}}),t.data=e})};r()}]}})}(),function(){angular.module("xos.uiComponents").directive("xosSmartPie",function(){return{restrict:"E",scope:{config:"="},template:'\n <canvas\n class="chart chart-pie {{vm.config.classes}}"\n chart-data="vm.data" chart-labels="vm.labels"\n chart-legend="{{vm.config.legend}}">\n </canvas>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","$interval","$scope","$timeout","_",function(e,n,o,i,t){var r=this;if(!this.config.resource&&!this.config.data)throw new Error("[xosSmartPie] Please provide a resource or an array of data in the configuration");var a=function(e){return t.groupBy(e,r.config.groupBy)},s=function(e){return t.reduce(Object.keys(e),function(n,o){return n.concat(e[o].length)},[])},l=function(e){return angular.isFunction(r.config.labelFormatter)?r.config.labelFormatter(Object.keys(e)):Object.keys(e)},c=function(e){var n=a(e);r.data=s(n),r.labels=l(n)};this.config.resource?!function(){r.Resource=e.get(r.config.resource);var o=function(){r.Resource.query().$promise.then(function(e){e[0]&&c(e)})};o(),r.config.poll&&n(function(){o()},1e3*r.config.poll)}():o.$watch(function(){return r.config.data},function(e){e&&c(r.config.data)},!0),o.$on("create",function(e,n){console.log("create: "+n.id)}),o.$on("destroy",function(e,n){console.log("destroy: "+n.id)})}]}})}(),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},i=function(i){return i=e(i),i=n(i),i=o(i).replace(/\s\s+/g," ")+":",i.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:i}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").service("XosFormHelpers",["_","LabelFormatter",function(e,n){var o=this;this._isEmail=function(e){var n=/(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;return n.test(e)},this._getFieldFormat=function(n){return angular.isArray(n)?"array":e.isDate(n)||!Number.isNaN(Date.parse(n))&&new Date(n).getTime()>6311808e5?"date":"boolean"==typeof n?"boolean":o._isEmail(n)?"email":angular.isString(n)||null===n?"text":"undefined"==typeof n?"undefined":_typeof(n)},this.buildFormStructure=function(i,t,r){return i=angular.extend(i,t),t=t||{},e.reduce(Object.keys(i),function(e,i){return e[i]={label:t[i]&&t[i].label?t[i].label+":":n.format(i),type:t[i]&&t[i].type?t[i].type:o._getFieldFormat(r[i]),validators:t[i]&&t[i].validators?t[i].validators:{},hint:t[i]&&t[i].hint?t[i].hint:""},t[i]&&t[i].options&&(e[i].options=t[i].options),t[i]&&t[i].properties&&(e[i].properties=t[i].properties),"date"===e[i].type&&(r[i]=new Date(r[i])),"number"===e[i].type&&(r[i]=parseInt(r[i],10)),e},{})},this.parseModelField=function(n){return e.reduce(n,function(e,n){return e[n]={},e},{})}}])}(),function(){function e(){return function(e,n){if(angular.isUndefined(e))return!1;if(null===e||null===n)return e===n;if(angular.isObject(n)||angular.isObject(e))return angular.equals(n,e);if(_.isBoolean(e)||_.isBoolean(n))return 0!==e&&1!==e||(e=!!e),angular.equals(n,e);if(!angular.isString(e)||!angular.isString(n)){if(!angular.isDefined(e.toString)||!angular.isDefined(n.toString))return e===n;e=e.toString(),n=n.toString()}return e=e.toLowerCase()+"",n=n.toLowerCase()+"",-1!==e.indexOf(n)}}angular.module("xos.uiComponents").factory("Comparator",e)}(),function(){angular.module("xos.uiComponents").directive("xosField",["RecursionHelper",function(e){return{restrict:"E",scope:{name:"=",field:"=",ngModel:"="},template:'\n <label ng-if="vm.field.type !== \'object\'">{{vm.field.label}}</label>\n <input\n xos-custom-validator custom-validator="vm.field.validators.custom || null"\n ng-if="vm.field.type !== \'boolean\' && vm.field.type !== \'object\' && vm.field.type !== \'select\'"\n type="{{vm.field.type}}"\n name="{{vm.name}}"\n class="form-control"\n ng-model="vm.ngModel"\n ng-minlength="vm.field.validators.minlength || 0"\n ng-maxlength="vm.field.validators.maxlength || 2000"\n ng-required="vm.field.validators.required || false" />\n <select class="form-control" ng-if ="vm.field.type === \'select\'"\n name = "{{vm.name}}"\n ng-options="item.id as item.label for item in vm.field.options"\n ng-model="vm.ngModel"\n ng-required="vm.field.validators.required || false">\n </select>\n <span class="boolean-field" ng-if="vm.field.type === \'boolean\'">\n <a href="#"\n class="btn btn-success"\n ng-show="vm.ngModel"\n ng-click="vm.ngModel = false">\n <i class="glyphicon glyphicon-ok"></i>\n </a>\n <a href="#"\n class="btn btn-danger"\n ng-show="!vm.ngModel"\n ng-click="vm.ngModel = true">\n <i class="glyphicon glyphicon-remove"></i>\n </a>\n </span>\n <div\n class="panel panel-default object-field"\n ng-if="vm.field.type == \'object\' && (!vm.isEmptyObject(vm.ngModel) || !vm.isEmptyObject(vm.field.properties))"\n >\n <div class="panel-heading">{{vm.field.label}}</div>\n <div class="panel-body">\n <div ng-if="!vm.field.properties" ng-repeat="(k, v) in vm.ngModel">\n <xos-field\n name="k"\n field="{label: vm.formatLabel(k), type: vm.getType(v)}"\n ng-model="v">\n </xos-field>\n </div>\n <div ng-if="vm.field.properties" ng-repeat="(k, v) in vm.field.properties">\n <xos-field\n name="k"\n field="{\n label: v.label || vm.formatLabel(k),\n type: v.type,\n validators: v.validators\n }"\n ng-model="vm.ngModel[k]">\n </xos-field>\n </div>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",compile:function(n){return e.compile(n)},controller:["$attrs","XosFormHelpers","LabelFormatter",function(e,n,o){if(!this.name)throw new Error("[xosField] Please provide a field name");if(!this.field)throw new Error("[xosField] Please provide a field definition");if(!this.field.type)throw new Error("[xosField] Please provide a type in the field definition");if(!e.ngModel)throw new Error("[xosField] Please provide an ng-model");this.getType=n._getFieldFormat,this.formatLabel=o.format,this.isEmptyObject=function(e){return e?0===Object.keys(e).length:!0}}]}}]).directive("xosCustomValidator",function(){return{restrict:"A",scope:{fn:"=customValidator"},require:"ngModel",link:function(e,n,o,i){function t(n){var o=e.fn(n);return angular.isArray(o)?i.$setValidity.apply(i,_toConsumableArray(o)):i.$setValidity("customValidation",o),n}angular.isFunction(e.fn)&&i.$parsers.push(t)}}})}(),function(){angular.module("xos.uiComponents").directive("xosValidation",function(){return{restrict:"E",scope:{field:"=",form:"="},template:'\n <div ng-cloak>\n <xos-alert config="vm.config" show="vm.field.$error.required !== undefined && vm.field.$error.required !== false && (vm.field.$touched || vm.form.$submitted)">\n Field required\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.email !== undefined && vm.field.$error.email !== false && (vm.field.$touched || vm.form.$submitted)">\n This is not a valid email\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.minlength !== undefined && vm.field.$error.minlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too short\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.maxlength !== undefined && vm.field.$error.maxlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too long\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.custom !== undefined && vm.field.$error.custom !== false && (vm.field.$touched || vm.form.$submitted)">\n Field invalid\n </xos-alert>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:function(){this.config={type:"danger"}}}})}(),function(){angular.module("xos.uiComponents").directive("xosTable",function(){return{restrict:"E",scope:{data:"=",config:"="},template:'\n <div ng-show="vm.data.length > 0 && vm.loader == false">\n <div class="row" ng-if="vm.config.filter == \'fulltext\'">\n <div class="col-xs-12">\n <input\n class="form-control"\n placeholder="Type to search.."\n type="text"\n ng-model="vm.query"/>\n </div>\n </div>\n <table ng-class="vm.classes" ng-hide="vm.data.length == 0">\n <thead>\n <tr>\n <th ng-repeat="col in vm.columns">\n {{col.label}}\n <span ng-if="vm.config.order">\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">\n <i class="glyphicon glyphicon-chevron-up"></i>\n </a>\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">\n <i class="glyphicon glyphicon-chevron-down"></i>\n </a>\n </span>\n </th>\n <th ng-if="vm.config.actions">Actions:</th>\n </tr>\n </thead>\n <tbody ng-if="vm.config.filter == \'field\'">\n <tr>\n <td ng-repeat="col in vm.columns">\n <input\n ng-if="col.type !== \'boolean\' && col.type !== \'array\' && col.type !== \'object\'"\n class="form-control"\n placeholder="Type to search by {{col.label}}"\n type="text"\n ng-model="vm.query[col.prop]"/>\n <select\n ng-if="col.type === \'boolean\'"\n class="form-control"\n ng-model="vm.query[col.prop]">\n <option value="">-</option>\n <option value="true">True</option>\n <option value="false">False</option>\n </select>\n </td>\n <td ng-if="vm.config.actions"></td>\n </tr>\n </tbody>\n <tbody>\n <tr ng-repeat="item in vm.data | filter:vm.query:vm.comparator | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">\n <td ng-repeat="col in vm.columns" xos-link-wrapper>\n <span ng-if="!col.type">{{item[col.prop]}}</span>\n <span ng-if="col.type === \'boolean\'">\n <i class="glyphicon"\n ng-class="{\'glyphicon-ok\': item[col.prop], \'glyphicon-remove\': !item[col.prop]}">\n </i>\n </span>\n <span ng-if="col.type === \'date\'">\n {{item[col.prop] | date:\'H:mm MMM d, yyyy\'}}\n </span>\n <span ng-if="col.type === \'array\'">\n {{item[col.prop] | arrayToList}}\n </span>\n <span ng-if="col.type === \'object\'">\n <dl class="dl-horizontal">\n <span ng-repeat="(k,v) in item[col.prop]">\n <dt>{{k}}</dt>\n <dd>{{v}}</dd>\n </span>\n </dl>\n </span>\n <span ng-if="col.type === \'custom\'">\n {{col.formatter(item)}}\n </span>\n <span ng-if="col.type === \'icon\'">\n <i class="glyphicon glyphicon-{{col.formatter(item)}}">\n </i>\n </span>\n </td>\n <td ng-if="vm.config.actions">\n <a href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(item)"\n title="{{action.label}}">\n <i\n class="glyphicon glyphicon-{{action.icon}}"\n style="color: {{action.color}};"></i>\n </a>\n </td>\n </tr>\n </tbody>\n </table>\n <xos-pagination\n ng-if="vm.config.pagination"\n page-size="vm.config.pagination.pageSize"\n total-elements="vm.data.length"\n change="vm.goToPage">\n </xos-pagination>\n </div>\n <div ng-show="(vm.data.length == 0 || !vm.data) && vm.loader == false">\n <xos-alert config="{type: \'info\'}">\n No data to show.\n </xos-alert>\n </div>\n <div ng-show="vm.loader == true">\n <div class="loader"></div>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["_","$scope","Comparator",function(e,n,o){var i=this;if(this.comparator=o,this.loader=!0,n.$watch(function(){return i.data},function(e){angular.isDefined(e)&&(i.loader=!1)}),!this.config)throw new Error('[xosTable] Please provide a configuration via the "config" attribute');if(!this.config.columns)throw new Error("[xosTable] Please provide a columns list in the configuration");this.config.order&&angular.isObject(this.config.order)&&(this.reverse=this.config.order.reverse||!1,this.orderBy=this.config.order.field||"id");var t=e.filter(this.config.columns,{type:"custom"});angular.isArray(t)&&t.length>0&&e.forEach(t,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided a custom field type, a formatter function should provided too.")});var r=e.filter(this.config.columns,{type:"icon"});angular.isArray(r)&&r.length>0&&e.forEach(r,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided an icon field type, a formatter function should provided too.")});var a=e.filter(this.config.columns,function(e){return angular.isDefined(e.link)});angular.isArray(a)&&a.length>0&&e.forEach(a,function(e){if(!angular.isFunction(e.link))throw new Error("[xosTable] The link property should be a function.")}),this.columns=this.config.columns,this.classes=this.config.classes||"table table-striped table-bordered",this.config.actions,this.config.pagination&&(this.currentPage=0,this.goToPage=function(e){i.currentPage=e})}]}}).filter("arrayToList",function(){return function(e){return angular.isArray(e)?e.join(", "):e}}).directive("xosLinkWrapper",function(){return{restrict:"A",transclude:!0,template:'\n <a ng-if="col.link" href="{{col.link(item)}}">\n <div ng-transclude></div>\n </a>\n <div ng-transclude ng-if="!col.link"></div>\n '}})}(),function(){angular.module("xos.uiComponents").directive("xosForm",function(){return{restrict:"E",scope:{config:"=",ngModel:"="},template:'\n <form name="vm.{{vm.config.formName || \'form\'}}" novalidate>\n <div class="form-group" ng-repeat="(name, field) in vm.formField">\n <xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>\n <xos-validation field="vm[vm.config.formName || \'form\'][name]" form = "vm[vm.config.formName || \'form\']"></xos-validation>\n <div class="alert alert-info" ng-show="(field.hint).length >0" role="alert">{{field.hint}}</div>\n </div>\n <div class="form-group" ng-if="vm.config.actions">\n <xos-alert config="vm.config.feedback" show="vm.config.feedback.show">{{vm.config.feedback.message}}</xos-alert>\n\n <button role="button" href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(vm.ngModel, vm[vm.config.formName || \'form\'])"\n class="btn btn-{{action.class}}"\n title="{{action.label}}">\n <i class="glyphicon glyphicon-{{action.icon}}"></i>\n {{action.label}}\n </button>\n </div>\n </form>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope","$log","_","XosFormHelpers",function(e,n,o,i){var t=this;if(!this.config)throw new Error('[xosForm] Please provide a configuration via the "config" attribute');if(!this.config.actions)throw new Error("[xosForm] Please provide an action list in the configuration");this.config.feedback||(this.config.feedback={show:!1,message:"Form submitted successfully !!!",type:"success"}),this.excludedField=["id","validators","created","updated","deleted","backend_status"],this.config&&this.config.exclude&&(this.excludedField=this.excludedField.concat(this.config.exclude)),this.formField=[],e.$watch(function(){return t.config},function(){if(t.ngModel){var e=o.difference(Object.keys(t.ngModel),t.excludedField),n=i.parseModelField(e);t.formField=i.buildFormStructure(n,t.config.fields,t.ngModel)}},!0),e.$watch(function(){return t.ngModel},function(e){if(t.formField={},e){var n=o.difference(Object.keys(e),t.excludedField),r=i.parseModelField(n);t.formField=i.buildFormStructure(r,t.config.fields,e)}})}]}})}(),function(){angular.module("xos.uiComponents").directive("xosPagination",function(){return{restrict:"E",scope:{pageSize:"=",totalElements:"=",change:"="},template:'\n <div class="row" ng-if="vm.pageList.length > 1">\n <div class="col-xs-12 text-center">\n <ul class="pagination">\n <li\n ng-click="vm.goToPage(vm.currentPage - 1)"\n ng-class="{disabled: vm.currentPage == 0}">\n <a href="" aria-label="Previous">\n <span aria-hidden="true">«</span>\n </a>\n </li>\n <li ng-repeat="i in vm.pageList" ng-class="{active: i === vm.currentPage}">\n <a href="" ng-click="vm.goToPage(i)">{{i + 1}}</a>\n </li>\n <li\n ng-click="vm.goToPage(vm.currentPage + 1)"\n ng-class="{disabled: vm.currentPage == vm.pages - 1}">\n <a href="" aria-label="Next">\n <span aria-hidden="true">»</span>\n </a>\n </li>\n </ul>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope",function(e){var n=this;this.currentPage=0,this.goToPage=function(e){0>e||e===n.pages||(n.currentPage=e,n.change(e))},this.createPages=function(e){for(var n=[],o=0;e>o;o++)n.push(o);return n},e.$watch(function(){return n.totalElements},function(){n.totalElements&&(n.pages=Math.ceil(n.totalElements/n.pageSize),n.pageList=n.createPages(n.pages))})}]}}).filter("pagination",function(){return function(e,n){return e&&angular.isArray(e)?(n=parseInt(n,10),e.slice(n)):e}})}(),function(){angular.module("xos.uiComponents").directive("xosAlert",function(){return{restrict:"E",scope:{config:"=",show:"=?"},template:'\n <div ng-cloak class="alert alert-{{vm.config.type}}" ng-hide="!vm.show">\n <button type="button" class="close" ng-if="vm.config.closeBtn" ng-click="vm.dismiss()">\n <span aria-hidden="true">×</span>\n </button>\n <p ng-transclude></p>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:["$timeout",function(e){var n=this;if(!this.config)throw new Error('[xosAlert] Please provide a configuration via the "config" attribute');this.show=this.show!==!1,this.dismiss=function(){n.show=!1},this.config.autoHide&&!function(){var o=e(function(){n.dismiss(),e.cancel(o)},n.config.autoHide)}()}]}})}(),function(){function e(e,n,o){e.interceptors.push("SetCSRFToken"),n.startSymbol("{$"),n.endSymbol("$}"),o.defaults.stripTrailingSlashes=!1}e.$inject=["$httpProvider","$interpolateProvider","$resourceProvider"],angular.module("bugSnag",[]).factory("$exceptionHandler",function(){return function(e,n){window.Bugsnag?Bugsnag.notifyException(e,{diagnostics:{cause:n}}):console.error(e,n,e.stack)}}),angular.module("xos.helpers",["ngCookies","ngResource","ngAnimate","bugSnag","xos.uiComponents"]).config(e).factory("_",["$window",function(e){return e._}])}(),function(){angular.module("xos.helpers").service("vSG-Collection",["$resource",function(e){return e("/api/service/vsg/")}])}(),function(){angular.module("xos.helpers").service("vOLT-Collection",["$resource",function(e){return e("/api/tenant/cord/volt/:volt_id/",{volt_id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Login",["$resource",function(e){return e("/api/utility/login/")}]).service("Logout",["$resource",function(e){return e("/api/utility/logout/")}])}(),function(){angular.module("xos.helpers").service("Users",["$resource",function(e){return e("/api/core/users/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Truckroll",["$resource",function(e){return e("/api/tenant/truckroll/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Tenants",["$resource",function(e){return e("/api/core/tenants/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Subscribers",["$resource",function(e){return e("/api/tenant/cord/subscriber/:id/",{id:"@id"},{update:{method:"PUT"},"View-a-Subscriber-Features-Detail":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/"},"Read-Subscriber-uplink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Update-Subscriber-uplink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Read-Subscriber-downlink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Update-Subscriber-downlink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Read-Subscriber-cdn":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Update-Subscriber-cdn":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Read-Subscriber-uverse":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Update-Subscriber-uverse":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Read-Subscriber-status":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"},"Update-Subscriber-status":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"}})}])}(),function(){angular.module("xos.helpers").service("SlicesPlus",["$http","$q",function(e,n){this.query=function(o){var i=n.defer();return e.get("/api/utility/slicesplus/",{params:o}).then(function(e){i.resolve(e.data)})["catch"](function(e){i.reject(e.data)}),{$promise:i.promise}},this.get=function(o,i){var t=n.defer();return e.get("/api/utility/slicesplus/"+o,{params:i}).then(function(e){t.resolve(e.data)})["catch"](function(e){t.reject(e.data)}),{$promise:t.promise}}}])}(),function(){angular.module("xos.helpers").service("Slices",["$resource",function(e){return e("/api/core/slices/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Sites",["$resource",function(e){return e("/api/core/sites/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Services",["$resource",function(e){return e("/api/core/services/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("ONOS-Services-Collection",["$resource",function(e){return e("/api/service/onos/")}])}(),function(){angular.module("xos.helpers").service("ONOS-App-Collection",["$resource",function(e){return e("/api/tenant/onos/app/")}])}(),function(){angular.module("xos.helpers").service("Nodes",["$resource",function(e){return e("/api/core/nodes/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Networks",["$resource",function(e){return e("/api/core/networks/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Instances",["$resource",function(e){return e("/api/core/instances/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Images",["$resource",function(e){return e("/api/core/images/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Flavors",["$resource",function(e){return e("/api/core/flavors/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Example-Services-Collection",["$resource",function(e){return e("/api/service/exampleservice/")}])}(),function(){angular.module("xos.helpers").service("Deployments",["$resource",function(e){return e("/api/core/deployments/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("XosUserPrefs",["$cookies",function(e){var n=this,o=e.get("xosUserPrefs")?angular.fromJson(e.get("xosUserPrefs")):{};this.getAll=function(){return o=e.get("xosUserPrefs")?angular.fromJson(e.get("xosUserPrefs")):{}},this.setAll=function(n){e.put("xosUserPrefs",angular.toJson(n))},this.getSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0];return e?n.getAll().synchronizers.notification[e]:n.getAll().synchronizers.notification},this.setSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0],o=arguments[1];if(!e)throw new Error("[XosUserPrefs] When updating a synchronizer is mandatory to provide a name.");var i=n.getAll();i.synchronizers||(i.synchronizers={notification:{}}),i.synchronizers.notification[e]=o,n.setAll(i)}}])}(),function(){angular.module("xos.helpers").service("GraphService",["$q","Tenants","Services",function(e,n,o){var i=this;this.loadCoarseData=function(){var i=void 0,t=e.defer();return o.query().$promise.then(function(e){return i=e,n.query({kind:"coarse"}).$promise}).then(function(e){t.resolve({tenants:e,services:i})}),t.promise},this.getCoarseGraph=function(){return i.loadCoarseData().then(function(e){console.log(e)}),"ciao"}}])}(),function(){angular.module("xos.helpers").factory("Notification",function(){return window.Notification}).service("xosNotification",["$q","$log","Notification",function(e,n,o){var i=this;this.checkPermission=function(){var n=e.defer();return o.requestPermission().then(function(e){"granted"===e?n.resolve(e):n.reject(e)}),n.promise},this.sendNotification=function(e,i){var t=new o(e,i);t.onerror=function(e){n.error(e)}},this.notify=function(e,t){"Notification"in window?"granted"!==o.permission?i.checkPermission().then(function(){
-return i.sendNotification(e,t)}):"granted"===o.permission&&i.sendNotification(e,t):n.info("This browser does not support desktop notification")}}])}(),function(){function e(){return{request:function(e){return-1===e.url.indexOf(".html")&&(e.url+="?no_hyperlinks=1"),e}}}angular.module("xos.helpers").factory("NoHyperlinks",e)}(),angular.module("xos.helpers").config(["$provide",function(e){e.decorator("$log",["$delegate",function(e){var n=function(){return window.location.href.indexOf("debug=true")>=0},o=e.log,i=e.info,t=e.warn,r=e.error,a=e.debug,s=function(o){return function(){if(n()){var i=[].slice.call(arguments),t=new Date;i[0]="["+t.getHours()+":"+t.getMinutes()+":"+t.getSeconds()+"] "+i[0],!angular.isFunction(e.reset)||e.debug.logs instanceof Array||e.reset(),o.apply(null,i)}}};return e.info=s(i),e.log=s(o),e.warn=s(t),e.error=s(r),e.debug=s(a),e}])}]),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},i=function(i){return i=e(i),i=n(i),i=o(i).replace(/\s\s+/g," ")+":",i.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:i}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}(),function(){function e(e){return{request:function(n){return"GET"!==n.method&&(n.headers["X-CSRFToken"]=e.get("xoscsrftoken")),n}}}e.$inject=["$cookies"],angular.module("xos.helpers").factory("SetCSRFToken",e)}();
\ No newline at end of file
+"use strict";function _toConsumableArray(e){if(Array.isArray(e)){for(var n=0,o=Array(e.length);n<e.length;n++)o[n]=e[n];return o}return Array.from(e)}!function(){angular.module("xos.uiComponents",["chart.js","RecursionHelper"])}(),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},i=function(i){return i=e(i),i=n(i),i=o(i).replace(/\s\s+/g," ")+":",i.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:i}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").service("XosFormHelpers",["_","LabelFormatter",function(e,n){var o=this;this._isEmail=function(e){var n=/(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;return n.test(e)},this._getFieldFormat=function(n){return angular.isArray(n)?"array":e.isDate(n)||!Number.isNaN(Date.parse(n))&&new Date(n).getTime()>6311808e5?"date":"boolean"==typeof n?"boolean":o._isEmail(n)?"email":angular.isString(n)||null===n?"text":"undefined"==typeof n?"undefined":_typeof(n)},this.buildFormStructure=function(i,t,r){return i=angular.extend(i,t),t=t||{},e.reduce(Object.keys(i),function(e,i){return e[i]={label:t[i]&&t[i].label?t[i].label+":":n.format(i),type:t[i]&&t[i].type?t[i].type:o._getFieldFormat(r[i]),validators:t[i]&&t[i].validators?t[i].validators:{},hint:t[i]&&t[i].hint?t[i].hint:""},t[i]&&t[i].options&&(e[i].options=t[i].options),t[i]&&t[i].properties&&(e[i].properties=t[i].properties),"date"===e[i].type&&(r[i]=new Date(r[i])),"number"===e[i].type&&(r[i]=parseInt(r[i],10)),e},{})},this.parseModelField=function(n){return e.reduce(n,function(e,n){return e[n]={},e},{})}}])}(),function(){function e(){return function(e,n){if(angular.isUndefined(e))return!1;if(null===e||null===n)return e===n;if(angular.isObject(n)||angular.isObject(e))return angular.equals(n,e);if(_.isBoolean(e)||_.isBoolean(n))return 0!==e&&1!==e||(e=!!e),angular.equals(n,e);if(!angular.isString(e)||!angular.isString(n)){if(!angular.isDefined(e.toString)||!angular.isDefined(n.toString))return e===n;e=e.toString(),n=n.toString()}return e=e.toLowerCase()+"",n=n.toLowerCase()+"",-1!==e.indexOf(n)}}angular.module("xos.uiComponents").factory("Comparator",e)}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").directive("xosSmartTable",function(){return{restrict:"E",scope:{config:"="},template:'\n <div class="row" ng-show="vm.data.length > 0">\n <div class="col-xs-12 text-right">\n <a href="" class="btn btn-success" ng-click="vm.createItem()">\n Add\n </a>\n </div>\n </div>\n <div class="row">\n <div class="col-xs-12 table-responsive">\n <xos-table config="vm.tableConfig" data="vm.data"></xos-table>\n </div>\n </div>\n <div class="panel panel-default" ng-show="vm.detailedItem">\n <div class="panel-heading">\n <div class="row">\n <div class="col-xs-11">\n <h3 class="panel-title" ng-show="vm.detailedItem.id">Update {{vm.config.resource}} {{vm.detailedItem.id}}</h3>\n <h3 class="panel-title" ng-show="!vm.detailedItem.id">Create {{vm.config.resource}} item</h3>\n </div>\n <div class="col-xs-1">\n <a href="" ng-click="vm.cleanForm()">\n <i class="glyphicon glyphicon-remove pull-right"></i>\n </a>\n </div>\n </div>\n </div>\n <div class="panel-body">\n <xos-form config="vm.formConfig" ng-model="vm.detailedItem"></xos-form>\n </div>\n </div>\n <xos-alert config="{type: \'success\', closeBtn: true}" show="vm.responseMsg">{{vm.responseMsg}}</xos-alert>\n <xos-alert config="{type: \'danger\', closeBtn: true}" show="vm.responseErr">{{vm.responseErr}}</xos-alert>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","LabelFormatter","_","XosFormHelpers",function(e,n,o,i){var t=this;this.responseMsg=!1,this.responseErr=!1,this.tableConfig={columns:[],actions:[{label:"delete",icon:"remove",cb:function(e){t.Resource["delete"]({id:e.id}).$promise.then(function(){o.remove(t.data,function(n){return n.id===e.id}),t.responseMsg=t.config.resource+" with id "+e.id+" successfully deleted"})["catch"](function(n){t.responseErr=n.data.detail||"Error while deleting "+t.config.resource+" with id "+e.id})},color:"red"},{label:"details",icon:"search",cb:function(e){t.detailedItem=e}}],classes:"table table-striped table-bordered table-responsive",filter:"field",order:!0,pagination:{pageSize:10}},this.formConfig={exclude:this.config.hiddenFields,fields:{},formName:this.config.resource+"Form",actions:[{label:"Save",icon:"ok",cb:function(e){var n=void 0,o=!0;e.id?(n=e.$update(),o=!1):n=e.$save(),n.then(function(n){o&&t.data.push(angular.copy(n)),delete t.detailedItem,t.responseMsg=t.config.resource+" with id "+e.id+" successfully saved"})["catch"](function(n){t.responseErr=n.data.detail||"Error while saving "+t.config.resource+" with id "+e.id})},"class":"success"}]},this.cleanForm=function(){delete t.detailedItem},this.createItem=function(){t.detailedItem=new t.Resource},this.Resource=e.get(this.config.resource);var r=function(){t.Resource.query().$promise.then(function(e){if(!e[0])return void(t.data=e);var r=e[0],a=Object.keys(r);o.remove(a,function(e){return"id"===e||"validators"===e}),angular.isArray(t.config.hiddenFields)&&(a=o.difference(a,t.config.hiddenFields));var s=a.map(function(e){return n.format(e)});a.forEach(function(e,n){var o={label:s[n],prop:e};angular.isString(r[e])&&"undefined"!=typeof r[e]&&(o.type=_typeof(r[e])),t.tableConfig.columns.push(o)}),a.forEach(function(e,o){t.formConfig.fields[e]={label:n.format(s[o]).replace(":",""),type:i._getFieldFormat(r[e])}}),t.data=e})};r()}]}})}(),function(){angular.module("xos.uiComponents").directive("xosSmartPie",function(){return{restrict:"E",scope:{config:"="},template:'\n <canvas\n class="chart chart-pie {{vm.config.classes}}"\n chart-data="vm.data" chart-labels="vm.labels"\n chart-legend="{{vm.config.legend}}">\n </canvas>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","$interval","$scope","$timeout","_",function(e,n,o,i,t){var r=this;if(!this.config.resource&&!this.config.data)throw new Error("[xosSmartPie] Please provide a resource or an array of data in the configuration");var a=function(e){return t.groupBy(e,r.config.groupBy)},s=function(e){return t.reduce(Object.keys(e),function(n,o){return n.concat(e[o].length)},[])},l=function(e){return angular.isFunction(r.config.labelFormatter)?r.config.labelFormatter(Object.keys(e)):Object.keys(e)},c=function(e){var n=a(e);r.data=s(n),r.labels=l(n)};this.config.resource?!function(){r.Resource=e.get(r.config.resource);var o=function(){r.Resource.query().$promise.then(function(e){e[0]&&c(e)})};o(),r.config.poll&&n(function(){o()},1e3*r.config.poll)}():o.$watch(function(){return r.config.data},function(e){e&&c(r.config.data)},!0),o.$on("create",function(e,n){console.log("create: "+n.id)}),o.$on("destroy",function(e,n){console.log("destroy: "+n.id)})}]}})}(),function(){angular.module("xos.uiComponents").directive("xosValidation",function(){return{restrict:"E",scope:{field:"=",form:"="},template:'\n <div ng-cloak>\n <xos-alert config="vm.config" show="vm.field.$error.required !== undefined && vm.field.$error.required !== false && (vm.field.$touched || vm.form.$submitted)">\n Field required\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.email !== undefined && vm.field.$error.email !== false && (vm.field.$touched || vm.form.$submitted)">\n This is not a valid email\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.minlength !== undefined && vm.field.$error.minlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too short\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.maxlength !== undefined && vm.field.$error.maxlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too long\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.custom !== undefined && vm.field.$error.custom !== false && (vm.field.$touched || vm.form.$submitted)">\n Field invalid\n </xos-alert>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:function(){this.config={type:"danger"}}}})}(),function(){angular.module("xos.uiComponents").directive("xosTable",function(){return{restrict:"E",scope:{data:"=",config:"="},template:'\n <div ng-show="vm.data.length > 0 && vm.loader == false">\n <div class="row" ng-if="vm.config.filter == \'fulltext\'">\n <div class="col-xs-12">\n <input\n class="form-control"\n placeholder="Type to search.."\n type="text"\n ng-model="vm.query"/>\n </div>\n </div>\n <table ng-class="vm.classes" ng-hide="vm.data.length == 0">\n <thead>\n <tr>\n <th ng-repeat="col in vm.columns">\n {{col.label}}\n <span ng-if="vm.config.order">\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">\n <i class="glyphicon glyphicon-chevron-up"></i>\n </a>\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">\n <i class="glyphicon glyphicon-chevron-down"></i>\n </a>\n </span>\n </th>\n <th ng-if="vm.config.actions">Actions:</th>\n </tr>\n </thead>\n <tbody ng-if="vm.config.filter == \'field\'">\n <tr>\n <td ng-repeat="col in vm.columns">\n <input\n ng-if="col.type !== \'boolean\' && col.type !== \'array\' && col.type !== \'object\' && col.type !== \'custom\'"\n class="form-control"\n placeholder="Type to search by {{col.label}}"\n type="text"\n ng-model="vm.query[col.prop]"/>\n <select\n ng-if="col.type === \'boolean\'"\n class="form-control"\n ng-model="vm.query[col.prop]">\n <option value="">-</option>\n <option value="true">True</option>\n <option value="false">False</option>\n </select>\n </td>\n <td ng-if="vm.config.actions"></td>\n </tr>\n </tbody>\n <tbody>\n <tr ng-repeat="item in vm.data | filter:vm.query:vm.comparator | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">\n <td ng-repeat="col in vm.columns" xos-link-wrapper>\n <span ng-if="!col.type">{{item[col.prop]}}</span>\n <span ng-if="col.type === \'boolean\'">\n <i class="glyphicon"\n ng-class="{\'glyphicon-ok\': item[col.prop], \'glyphicon-remove\': !item[col.prop]}">\n </i>\n </span>\n <span ng-if="col.type === \'date\'">\n {{item[col.prop] | date:\'H:mm MMM d, yyyy\'}}\n </span>\n <span ng-if="col.type === \'array\'">\n {{item[col.prop] | arrayToList}}\n </span>\n <span ng-if="col.type === \'object\'">\n <dl class="dl-horizontal">\n <span ng-repeat="(k,v) in item[col.prop]">\n <dt>{{k}}</dt>\n <dd>{{v}}</dd>\n </span>\n </dl>\n </span>\n <span ng-if="col.type === \'custom\'">\n {{col.formatter(item)}}\n </span>\n <span ng-if="col.type === \'icon\'">\n <i class="glyphicon glyphicon-{{col.formatter(item)}}">\n </i>\n </span>\n </td>\n <td ng-if="vm.config.actions">\n <a href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(item)"\n title="{{action.label}}">\n <i\n class="glyphicon glyphicon-{{action.icon}}"\n style="color: {{action.color}};"></i>\n </a>\n </td>\n </tr>\n </tbody>\n </table>\n <xos-pagination\n ng-if="vm.config.pagination"\n page-size="vm.config.pagination.pageSize"\n total-elements="vm.data.length"\n change="vm.goToPage">\n </xos-pagination>\n </div>\n <div ng-show="(vm.data.length == 0 || !vm.data) && vm.loader == false">\n <xos-alert config="{type: \'info\'}">\n No data to show.\n </xos-alert>\n </div>\n <div ng-show="vm.loader == true">\n <div class="loader"></div>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["_","$scope","Comparator",function(e,n,o){var i=this;if(this.comparator=o,this.loader=!0,n.$watch(function(){return i.data},function(e){angular.isDefined(e)&&(i.loader=!1)}),!this.config)throw new Error('[xosTable] Please provide a configuration via the "config" attribute');if(!this.config.columns)throw new Error("[xosTable] Please provide a columns list in the configuration");this.config.order&&angular.isObject(this.config.order)&&(this.reverse=this.config.order.reverse||!1,this.orderBy=this.config.order.field||"id");var t=e.filter(this.config.columns,{type:"custom"});angular.isArray(t)&&t.length>0&&e.forEach(t,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided a custom field type, a formatter function should provided too.")});var r=e.filter(this.config.columns,{type:"icon"});angular.isArray(r)&&r.length>0&&e.forEach(r,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided an icon field type, a formatter function should provided too.")});var a=e.filter(this.config.columns,function(e){return angular.isDefined(e.link)});angular.isArray(a)&&a.length>0&&e.forEach(a,function(e){if(!angular.isFunction(e.link))throw new Error("[xosTable] The link property should be a function.")}),this.columns=this.config.columns,this.classes=this.config.classes||"table table-striped table-bordered",this.config.actions,this.config.pagination&&(this.currentPage=0,this.goToPage=function(e){i.currentPage=e})}]}}).filter("arrayToList",function(){return function(e){return angular.isArray(e)?e.join(", "):e}}).directive("xosLinkWrapper",function(){return{restrict:"A",transclude:!0,template:'\n <a ng-if="col.link" href="{{col.link(item)}}">\n <div ng-transclude></div>\n </a>\n <div ng-transclude ng-if="!col.link"></div>\n '}})}(),function(){angular.module("xos.uiComponents").directive("xosPagination",function(){return{restrict:"E",scope:{pageSize:"=",totalElements:"=",change:"="},template:'\n <div class="row" ng-if="vm.pageList.length > 1">\n <div class="col-xs-12 text-center">\n <ul class="pagination">\n <li\n ng-click="vm.goToPage(vm.currentPage - 1)"\n ng-class="{disabled: vm.currentPage == 0}">\n <a href="" aria-label="Previous">\n <span aria-hidden="true">«</span>\n </a>\n </li>\n <li ng-repeat="i in vm.pageList" ng-class="{active: i === vm.currentPage}">\n <a href="" ng-click="vm.goToPage(i)">{{i + 1}}</a>\n </li>\n <li\n ng-click="vm.goToPage(vm.currentPage + 1)"\n ng-class="{disabled: vm.currentPage == vm.pages - 1}">\n <a href="" aria-label="Next">\n <span aria-hidden="true">»</span>\n </a>\n </li>\n </ul>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope",function(e){var n=this;this.currentPage=0,this.goToPage=function(e){0>e||e===n.pages||(n.currentPage=e,n.change(e))},this.createPages=function(e){for(var n=[],o=0;e>o;o++)n.push(o);return n},e.$watch(function(){return n.totalElements},function(){n.totalElements&&(n.pages=Math.ceil(n.totalElements/n.pageSize),n.pageList=n.createPages(n.pages))})}]}}).filter("pagination",function(){return function(e,n){return e&&angular.isArray(e)?(n=parseInt(n,10),e.slice(n)):e}})}(),function(){angular.module("xos.uiComponents").directive("xosForm",function(){return{restrict:"E",scope:{config:"=",ngModel:"="},template:'\n <form name="vm.{{vm.config.formName || \'form\'}}" novalidate>\n <div class="form-group" ng-repeat="(name, field) in vm.formField">\n <xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>\n <xos-validation field="vm[vm.config.formName || \'form\'][name]" form = "vm[vm.config.formName || \'form\']"></xos-validation>\n <div class="alert alert-info" ng-show="(field.hint).length >0" role="alert">{{field.hint}}</div>\n </div>\n <div class="form-group" ng-if="vm.config.actions">\n <xos-alert config="vm.config.feedback" show="vm.config.feedback.show">{{vm.config.feedback.message}}</xos-alert>\n\n <button role="button" href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(vm.ngModel, vm[vm.config.formName || \'form\'])"\n class="btn btn-{{action.class}}"\n title="{{action.label}}">\n <i class="glyphicon glyphicon-{{action.icon}}"></i>\n {{action.label}}\n </button>\n </div>\n </form>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope","$log","_","XosFormHelpers",function(e,n,o,i){var t=this;if(!this.config)throw new Error('[xosForm] Please provide a configuration via the "config" attribute');if(!this.config.actions)throw new Error("[xosForm] Please provide an action list in the configuration");this.config.feedback||(this.config.feedback={show:!1,message:"Form submitted successfully !!!",type:"success"}),this.excludedField=["id","validators","created","updated","deleted","backend_status"],this.config&&this.config.exclude&&(this.excludedField=this.excludedField.concat(this.config.exclude)),this.formField=[],e.$watch(function(){return t.config},function(){if(t.ngModel){var e=o.difference(Object.keys(t.ngModel),t.excludedField),n=i.parseModelField(e);t.formField=i.buildFormStructure(n,t.config.fields,t.ngModel)}},!0),e.$watch(function(){return t.ngModel},function(e){if(t.formField={},e){var n=o.difference(Object.keys(e),t.excludedField),r=i.parseModelField(n);t.formField=i.buildFormStructure(r,t.config.fields,e)}})}]}})}(),function(){angular.module("xos.uiComponents").directive("xosField",["RecursionHelper",function(e){return{restrict:"E",scope:{name:"=",field:"=",ngModel:"="},template:'\n <label ng-if="vm.field.type !== \'object\'">{{vm.field.label}}</label>\n <input\n xos-custom-validator custom-validator="vm.field.validators.custom || null"\n ng-if="vm.field.type !== \'boolean\' && vm.field.type !== \'object\' && vm.field.type !== \'select\'"\n type="{{vm.field.type}}"\n name="{{vm.name}}"\n class="form-control"\n ng-model="vm.ngModel"\n ng-minlength="vm.field.validators.minlength || 0"\n ng-maxlength="vm.field.validators.maxlength || 2000"\n ng-required="vm.field.validators.required || false" />\n <select class="form-control" ng-if ="vm.field.type === \'select\'"\n name = "{{vm.name}}"\n ng-options="item.id as item.label for item in vm.field.options"\n ng-model="vm.ngModel"\n ng-required="vm.field.validators.required || false">\n </select>\n <span class="boolean-field" ng-if="vm.field.type === \'boolean\'">\n <a href="#"\n class="btn btn-success"\n ng-show="vm.ngModel"\n ng-click="vm.ngModel = false">\n <i class="glyphicon glyphicon-ok"></i>\n </a>\n <a href="#"\n class="btn btn-danger"\n ng-show="!vm.ngModel"\n ng-click="vm.ngModel = true">\n <i class="glyphicon glyphicon-remove"></i>\n </a>\n </span>\n <div\n class="panel panel-default object-field"\n ng-if="vm.field.type == \'object\' && (!vm.isEmptyObject(vm.ngModel) || !vm.isEmptyObject(vm.field.properties))"\n >\n <div class="panel-heading">{{vm.field.label}}</div>\n <div class="panel-body">\n <div ng-if="!vm.field.properties" ng-repeat="(k, v) in vm.ngModel">\n <xos-field\n name="k"\n field="{label: vm.formatLabel(k), type: vm.getType(v)}"\n ng-model="v">\n </xos-field>\n </div>\n <div ng-if="vm.field.properties" ng-repeat="(k, v) in vm.field.properties">\n <xos-field\n name="k"\n field="{\n label: v.label || vm.formatLabel(k),\n type: v.type,\n validators: v.validators\n }"\n ng-model="vm.ngModel[k]">\n </xos-field>\n </div>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",compile:function(n){return e.compile(n)},controller:["$attrs","XosFormHelpers","LabelFormatter",function(e,n,o){if(!this.name)throw new Error("[xosField] Please provide a field name");if(!this.field)throw new Error("[xosField] Please provide a field definition");if(!this.field.type)throw new Error("[xosField] Please provide a type in the field definition");if(!e.ngModel)throw new Error("[xosField] Please provide an ng-model");this.getType=n._getFieldFormat,this.formatLabel=o.format,this.isEmptyObject=function(e){return e?0===Object.keys(e).length:!0}}]}}]).directive("xosCustomValidator",function(){return{restrict:"A",scope:{fn:"=customValidator"},require:"ngModel",link:function(e,n,o,i){function t(n){var o=e.fn(n);return angular.isArray(o)?i.$setValidity.apply(i,_toConsumableArray(o)):i.$setValidity("customValidation",o),n}angular.isFunction(e.fn)&&i.$parsers.push(t)}}})}(),function(){angular.module("xos.uiComponents").directive("xosAlert",function(){return{restrict:"E",scope:{config:"=",show:"=?"},template:'\n <div ng-cloak class="alert alert-{{vm.config.type}}" ng-hide="!vm.show">\n <button type="button" class="close" ng-if="vm.config.closeBtn" ng-click="vm.dismiss()">\n <span aria-hidden="true">×</span>\n </button>\n <p ng-transclude></p>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:["$timeout",function(e){var n=this;if(!this.config)throw new Error('[xosAlert] Please provide a configuration via the "config" attribute');this.show=this.show!==!1,this.dismiss=function(){n.show=!1},this.config.autoHide&&!function(){var o=e(function(){n.dismiss(),e.cancel(o)},n.config.autoHide)}()}]}})}(),function(){function e(e,n,o){e.interceptors.push("SetCSRFToken"),n.startSymbol("{$"),n.endSymbol("$}"),o.defaults.stripTrailingSlashes=!1}e.$inject=["$httpProvider","$interpolateProvider","$resourceProvider"],angular.module("bugSnag",[]).factory("$exceptionHandler",function(){return function(e,n){window.Bugsnag?Bugsnag.notifyException(e,{diagnostics:{cause:n}}):console.error(e,n,e.stack)}}),angular.module("xos.helpers",["ngCookies","ngResource","ngAnimate","bugSnag","xos.uiComponents"]).config(e).factory("_",["$window",function(e){return e._}])}(),function(){angular.module("xos.helpers").service("vSG-Collection",["$resource",function(e){return e("/api/service/vsg/")}])}(),function(){angular.module("xos.helpers").service("vOLT-Collection",["$resource",function(e){return e("/api/tenant/cord/volt/:volt_id/",{volt_id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Login",["$resource",function(e){return e("/api/utility/login/")}]).service("Logout",["$resource",function(e){return e("/api/utility/logout/")}])}(),function(){angular.module("xos.helpers").service("Users",["$resource",function(e){return e("/api/core/users/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Truckroll",["$resource",function(e){return e("/api/tenant/truckroll/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Tenants",["$resource",function(e){return e("/api/core/tenants/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Subscribers",["$resource",function(e){return e("/api/tenant/cord/subscriber/:id/",{id:"@id"},{update:{method:"PUT"},"View-a-Subscriber-Features-Detail":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/"},"Read-Subscriber-uplink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Update-Subscriber-uplink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Read-Subscriber-downlink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Update-Subscriber-downlink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Read-Subscriber-cdn":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Update-Subscriber-cdn":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Read-Subscriber-uverse":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Update-Subscriber-uverse":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Read-Subscriber-status":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"},"Update-Subscriber-status":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"}})}])}(),function(){angular.module("xos.helpers").service("SlicesPlus",["$http","$q",function(e,n){this.query=function(o){var i=n.defer();return e.get("/api/utility/slicesplus/",{params:o}).then(function(e){i.resolve(e.data)})["catch"](function(e){i.reject(e.data)}),{$promise:i.promise}},this.get=function(o,i){var t=n.defer();return e.get("/api/utility/slicesplus/"+o,{params:i}).then(function(e){t.resolve(e.data)})["catch"](function(e){t.reject(e.data)}),{$promise:t.promise}}}])}(),function(){angular.module("xos.helpers").service("Slices",["$resource",function(e){return e("/api/core/slices/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Sites",["$resource",function(e){return e("/api/core/sites/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Services",["$resource",function(e){return e("/api/core/services/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("ONOS-Services-Collection",["$resource",function(e){return e("/api/service/onos/")}])}(),function(){angular.module("xos.helpers").service("ONOS-App-Collection",["$resource",function(e){return e("/api/tenant/onos/app/")}])}(),function(){angular.module("xos.helpers").service("Nodes",["$resource",function(e){return e("/api/core/nodes/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Networks",["$resource",function(e){return e("/api/core/networks/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Instances",["$resource",function(e){return e("/api/core/instances/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Images",["$resource",function(e){return e("/api/core/images/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Flavors",["$resource",function(e){return e("/api/core/flavors/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Example-Services-Collection",["$resource",function(e){return e("/api/service/exampleservice/")}])}(),function(){angular.module("xos.helpers").service("Deployments",["$resource",function(e){return e("/api/core/deployments/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("XosUserPrefs",["$cookies",function(e){var n=this,o=e.get("xosUserPrefs")?angular.fromJson(e.get("xosUserPrefs")):{};this.getAll=function(){return o=e.get("xosUserPrefs")?angular.fromJson(e.get("xosUserPrefs")):{}},this.setAll=function(n){e.put("xosUserPrefs",angular.toJson(n))},this.getSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0];return e?n.getAll().synchronizers.notification[e]:n.getAll().synchronizers.notification},this.setSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0],o=arguments[1];if(!e)throw new Error("[XosUserPrefs] When updating a synchronizer is mandatory to provide a name.");var i=n.getAll();i.synchronizers||(i.synchronizers={notification:{}}),i.synchronizers.notification[e]=o,n.setAll(i)}}])}(),function(){angular.module("xos.helpers").service("GraphService",["$q","Tenants","Services",function(e,n,o){var i=this;this.loadCoarseData=function(){var i=void 0,t=e.defer();return o.query().$promise.then(function(e){return i=e,n.query({kind:"coarse"}).$promise}).then(function(e){t.resolve({tenants:e,services:i})}),t.promise},this.getCoarseGraph=function(){return i.loadCoarseData().then(function(e){console.log(e)}),"ciao"}}])}(),function(){angular.module("xos.helpers").factory("Notification",function(){return window.Notification}).service("xosNotification",["$q","$log","Notification",function(e,n,o){var i=this;this.checkPermission=function(){var n=e.defer();return o.requestPermission().then(function(e){"granted"===e?n.resolve(e):n.reject(e)}),n.promise},this.sendNotification=function(e,i){var t=new o(e,i);t.onerror=function(e){n.error(e)}},this.notify=function(e,t){
+"Notification"in window?"granted"!==o.permission?i.checkPermission().then(function(){return i.sendNotification(e,t)}):"granted"===o.permission&&i.sendNotification(e,t):n.info("This browser does not support desktop notification")}}])}(),function(){function e(){return{request:function(e){return-1===e.url.indexOf(".html")&&(e.url+="?no_hyperlinks=1"),e}}}angular.module("xos.helpers").factory("NoHyperlinks",e)}(),angular.module("xos.helpers").config(["$provide",function(e){e.decorator("$log",["$delegate",function(e){var n=function(){return window.location.href.indexOf("debug=true")>=0},o=e.log,i=e.info,t=e.warn,r=e.error,a=e.debug,s=function(o){return function(){if(n()){var i=[].slice.call(arguments),t=new Date;i[0]="["+t.getHours()+":"+t.getMinutes()+":"+t.getSeconds()+"] "+i[0],!angular.isFunction(e.reset)||e.debug.logs instanceof Array||e.reset(),o.apply(null,i)}}};return e.info=s(i),e.log=s(o),e.warn=s(t),e.error=s(r),e.debug=s(a),e}])}]),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},i=function(i){return i=e(i),i=n(i),i=o(i).replace(/\s\s+/g," ")+":",i.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:i}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}(),function(){function e(e){return{request:function(n){return"GET"!==n.method&&(n.headers["X-CSRFToken"]=e.get("xoscsrftoken")),n}}}e.$inject=["$cookies"],angular.module("xos.helpers").factory("SetCSRFToken",e)}();
\ No newline at end of file
diff --git a/xos/core/xoslib/static/js/xosHpc.js b/xos/core/xoslib/static/js/xosHpc.js
index e0b0437..a1eb26e 100644
--- a/xos/core/xoslib/static/js/xosHpc.js
+++ b/xos/core/xoslib/static/js/xosHpc.js
@@ -1,105 +1 @@
-/* eslint-disable */
-SC_RR = 60;
-SC_HPC_PROBE = 600;
-SC_HPC_FETCH = 3600;
-
-function staleCheck(row, time_key, msg_key, seconds) {
- if (parseInt(row[time_key])>seconds) {
- return "stale";
- } else {
- if (! row[msg_key]) {
- return "null";
- } else {
- return row[msg_key];
- }
- }
-}
-
-function updateDnsDemuxTable(dnsdemux) {
- $('#xos-hpc-dns').html( '<table cellpadding="0" cellspacing="0" border="0" class="display" id="dynamic_dnsdemux"></table>' );
- var actualEntries = [];
-
- //console.log(dnsdemux);
-
- for (rowkey in dnsdemux) {
- row = dnsdemux[rowkey];
-
- actualEntries.push( [row.name, row.ip, staleCheck(row, "watcher.DNS.time", "watcher.DNS.msg", SC_RR), row.nameservers.join(","),
- row.dnsredir_config_age + "," + row.dnsdemux_config_age] );
- }
- console.log(actualEntries);
- oTable = $('#dynamic_dnsdemux').dataTable( {
- "bJQueryUI": true,
- "aaData": actualEntries ,
- "bStateSave": true,
- "bFilter": false,
- "bPaginate": false,
- "aoColumns": [
- { "sTitle": "Node" },
- { "sTitle": "IP Address" },
- { "sTitle": "Record Checker" },
- { "sTitle": "Nameservers" },
- { "sTitle": "Config Age" },
- ]
- } );
-}
-
-function updateHpcTable(hpcnodes) {
- $('#xos-hpc-hpc').html( '<table cellpadding="0" cellspacing="0" border="0" class="display" id="dynamic_hpc"></table>' );
- var actualEntries = [];
-
- //console.log(hpcnodes);
-
- for (rowkey in hpcnodes) {
- row = hpcnodes[rowkey];
-
- actualEntries.push( [row.name, staleCheck(row, "watcher.HPC-hb.time", "watcher.HPC-hb.msg", SC_HPC_PROBE),
- staleCheck(row, "watcher.HPC-fetch.time", "watcher.HPC-fetch.msg", SC_HPC_FETCH),
- row.config_age, ] );
- }
- console.log(actualEntries);
- oTable = $('#dynamic_hpc').dataTable( {
- "bJQueryUI": true,
- "aaData": actualEntries ,
- "bStateSave": true,
- "bFilter": false,
- "bPaginate": false,
- "aoColumns": [
- { "sTitle": "Node", },
- { "sTitle": "Prober" },
- { "sTitle": "Fetcher" },
- { "sTitle": "Config Age" },
- ]
- } );
-}
-
-function updateWarnings(data) {
- nameservers = data.attributes.nameservers;
- warnings = [];
- for (rowKey in nameservers) {
- nameserver = nameservers[rowKey]
- if (!nameserver.hit) {
- warnings.push("<B>WARNING:</B> nameserver " + nameserver.name + " does not map to a request router");
- }
- }
-
- console.log(warnings);
-
- $("#warnings").html(warnings.join("<BR>"));
-}
-
-function updateHpcView(data) {
- data = data[0];
- updateDnsDemuxTable( data.attributes.dnsdemux );
- updateHpcTable( data.attributes.hpc );
- updateWarnings( data );
-}
-
-$(document).ready(function(){
- xos.hpcview.on("change", function() { console.log("change"); updateHpcView(xos.hpcview.models); });
- xos.hpcview.on("remove", function() { console.log("sort"); updateHpcView(xos.hpcview.models); });
- xos.hpcview.on("sort", function() { console.log("sort"); updateHpcView(xos.hpcview.models); });
-
- xos.hpcview.startPolling();
-});
-/* eslint-enable */
+"use strict";angular.module("xos.hpc",["ngResource","ngCookies","ui.router","xos.helpers"]).config(["$stateProvider",function(e){e.state("hpc-list",{url:"/",template:"<hpcs-list></hpcs-list>"})}]).config(["$httpProvider",function(e){e.interceptors.push("NoHyperlinks")}]).service("Hpc",["$q","$http",function(e,r){this.query=function(t){var n=e.defer();return r.get("/xoslib/hpcview",{params:t}).then(function(e){n.resolve(e.data)})["catch"](n.reject),{$promise:n.promise}}}]).directive("hpcsList",function(){return{restrict:"E",scope:{},bindToController:!0,controllerAs:"vm",templateUrl:"templates/hpc-list.tpl.html",controller:["Hpc",function(e){var r=this,t=function(e){e=Number(e);var r=Math.floor(e/3600),t=Math.floor(e%3600/60),n=Math.floor(e%3600%60);return(r>0?r+"h "+(10>t?"0":""):"")+t+"m "+(10>n?"0":"")+n+"s"},n=function(e){return function(r){return angular.isNumber(r[e])?t(r[e]):r[e]}};this.routerConfig={filter:"field",order:!0,columns:[{label:"Name",prop:"name"},{label:"Ip Address",prop:"ip"},{label:"Record Checker",prop:"watcher.DNS.msg"},{label:"Name Servers",prop:"nameservers",type:"array"},{label:"Dns Demux Config Age",prop:"dnsdemux_config_age",type:"custom",formatter:n("dnsdemux_config_age")},{label:"Dns Redir Config Age",prop:"dnsredir_config_age",type:"custom",formatter:n("dnsredir_config_age")}]},this.cacheConfig={filter:"field",order:!0,columns:[{label:"Name",prop:"name"},{label:"Prober",prop:"watcher.HPC-hb.msg"},{label:"Fetcher",prop:"watcher.HPC-fetch.msg"},{label:"Config Age",prop:"config_age",type:"custom",formatter:n("config_age")}]},this.fetch=function(){e.query().$promise.then(function(e){r.routers=e[0].dnsdemux,r.caches=e[0].hpc})["catch"](function(e){throw new Error(e)})},this.fetch()}]}}),angular.module("xos.hpc").run(["$templateCache",function(e){e.put("templates/hpc-list.tpl.html",'<div class="container-fluid">\n <div class="row">\n <div class="col-xs-10">\n <h1>Request Routers</h1>\n </div>\n <div class="col-xs-2 text-right">\n <a href="" ng-click="vm.fetch()" class="btn btn-primary btn-reload">\n <i class="glyphicon glyphicon-refresh"></i>\n Refresh\n </a>\n </div>\n </div>\n <div class="row">\n <div class="col-xs-12">\n <xos-table config="vm.routerConfig" data="vm.routers"></xos-table>\n </div>\n </div>\n <div class="row">\n <div class="col-xs-12">\n <h1>HyperCache</h1>\n </div>\n </div>\n <div class="row">\n <div class="col-xs-12">\n <xos-table config="vm.cacheConfig" data="vm.caches"></xos-table>\n </div>\n </div>\n</div>')}]),angular.module("xos.hpc").run(["$location",function(e){e.path("/")}]);
\ No newline at end of file
diff --git a/xos/onboard/onos/onos-onboard.yaml b/xos/onboard/onos/onos-onboard.yaml
index 1338bb6..3d4ac3b 100644
--- a/xos/onboard/onos/onos-onboard.yaml
+++ b/xos/onboard/onos/onos-onboard.yaml
@@ -20,7 +20,7 @@
synchronizer_run: onos-synchronizer.py
#tosca_custom_types: exampleservice.yaml
tosca_resource: tosca/resources/onosservice.py, tosca/resources/onosapp.py
- rest_service: subdirectory:vsg api/service/onos.py
+ rest_service: api/service/onos.py
rest_tenant: subdirectory:onos api/tenant/onos/app.py
private_key: file:///opt/xos/key_import/onos_rsa
public_key: file:///opt/xos/key_import/onos_rsa.pub
diff --git a/xos/services/openvpn/admin.py b/xos/onboard/openvpn/admin.py
similarity index 100%
rename from xos/services/openvpn/admin.py
rename to xos/onboard/openvpn/admin.py
diff --git a/xos/api/tenant/openvpn/openvpn.py b/xos/onboard/openvpn/api/tenant/openvpn/openvpn.py
similarity index 100%
rename from xos/api/tenant/openvpn/openvpn.py
rename to xos/onboard/openvpn/api/tenant/openvpn/openvpn.py
diff --git a/xos/services/openvpn/models.py b/xos/onboard/openvpn/models.py
similarity index 100%
rename from xos/services/openvpn/models.py
rename to xos/onboard/openvpn/models.py
diff --git a/xos/onboard/openvpn/openvpn-onboard.yaml b/xos/onboard/openvpn/openvpn-onboard.yaml
new file mode 100644
index 0000000..980e0b5
--- /dev/null
+++ b/xos/onboard/openvpn/openvpn-onboard.yaml
@@ -0,0 +1,25 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the OpenVPN Service
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ servicecontroller#openvpn:
+ type: tosca.nodes.ServiceController
+ properties:
+ base_url: file:///opt/xos/onboard/vsg/
+ # The following will concatenate with base_url automatically, if
+ # base_url is non-null.
+ models: models.py
+ admin: admin.py
+ admin_template: templates/connect.vpn.j2
+ synchronizer: synchronizer/manifest
+ synchronizer_run: openvpn-synchronizer.py
+ #tosca_custom_types: exampleservice.yaml
+ rest_tenant: subdirectory:openvpn api/tenant/openvpn/openvpn.py
+ private_key: file:///opt/xos/key_import/openvpn_rsa
+ public_key: file:///opt/xos/key_import/openvpn_rsa.pub
+
diff --git a/xos/services/cord_old/migrations/__init__.py b/xos/onboard/openvpn/synchronizer/__init__.py
similarity index 100%
rename from xos/services/cord_old/migrations/__init__.py
rename to xos/onboard/openvpn/synchronizer/__init__.py
diff --git a/xos/onboard/openvpn/synchronizer/manifest b/xos/onboard/openvpn/synchronizer/manifest
new file mode 100644
index 0000000..44d6986
--- /dev/null
+++ b/xos/onboard/openvpn/synchronizer/manifest
@@ -0,0 +1,14 @@
+manifest
+openvpn_config
+__init__.py
+steps/sync_tenantprivilege.py
+steps/sync_openvpntenant.yaml
+steps/__init__.py
+steps/roles/openvpn/templates/server.conf.j2
+steps/roles/openvpn/handlers/main.yml
+steps/roles/openvpn/tasks/main.yml
+steps/sync_openvpntenant.py
+stop.sh
+model-deps
+openvpn-synchronizer.py
+run.sh
diff --git a/xos/synchronizers/openvpn/model-deps b/xos/onboard/openvpn/synchronizer/model-deps
similarity index 100%
rename from xos/synchronizers/openvpn/model-deps
rename to xos/onboard/openvpn/synchronizer/model-deps
diff --git a/xos/synchronizers/openvpn/openvpn-synchronizer.py b/xos/onboard/openvpn/synchronizer/openvpn-synchronizer.py
similarity index 100%
rename from xos/synchronizers/openvpn/openvpn-synchronizer.py
rename to xos/onboard/openvpn/synchronizer/openvpn-synchronizer.py
diff --git a/xos/synchronizers/openvpn/openvpn_config b/xos/onboard/openvpn/synchronizer/openvpn_config
similarity index 100%
rename from xos/synchronizers/openvpn/openvpn_config
rename to xos/onboard/openvpn/synchronizer/openvpn_config
diff --git a/xos/synchronizers/openvpn/run.sh b/xos/onboard/openvpn/synchronizer/run.sh
similarity index 100%
rename from xos/synchronizers/openvpn/run.sh
rename to xos/onboard/openvpn/synchronizer/run.sh
diff --git a/xos/synchronizers/openvpn/steps/__init__.py b/xos/onboard/openvpn/synchronizer/steps/__init__.py
similarity index 100%
rename from xos/synchronizers/openvpn/steps/__init__.py
rename to xos/onboard/openvpn/synchronizer/steps/__init__.py
diff --git a/xos/synchronizers/openvpn/steps/roles/openvpn/handlers/main.yml b/xos/onboard/openvpn/synchronizer/steps/roles/openvpn/handlers/main.yml
similarity index 100%
rename from xos/synchronizers/openvpn/steps/roles/openvpn/handlers/main.yml
rename to xos/onboard/openvpn/synchronizer/steps/roles/openvpn/handlers/main.yml
diff --git a/xos/synchronizers/openvpn/steps/roles/openvpn/tasks/main.yml b/xos/onboard/openvpn/synchronizer/steps/roles/openvpn/tasks/main.yml
similarity index 100%
rename from xos/synchronizers/openvpn/steps/roles/openvpn/tasks/main.yml
rename to xos/onboard/openvpn/synchronizer/steps/roles/openvpn/tasks/main.yml
diff --git a/xos/synchronizers/openvpn/steps/roles/openvpn/templates/server.conf.j2 b/xos/onboard/openvpn/synchronizer/steps/roles/openvpn/templates/server.conf.j2
similarity index 100%
rename from xos/synchronizers/openvpn/steps/roles/openvpn/templates/server.conf.j2
rename to xos/onboard/openvpn/synchronizer/steps/roles/openvpn/templates/server.conf.j2
diff --git a/xos/synchronizers/openvpn/steps/sync_openvpntenant.py b/xos/onboard/openvpn/synchronizer/steps/sync_openvpntenant.py
similarity index 100%
rename from xos/synchronizers/openvpn/steps/sync_openvpntenant.py
rename to xos/onboard/openvpn/synchronizer/steps/sync_openvpntenant.py
diff --git a/xos/synchronizers/openvpn/steps/sync_openvpntenant.yaml b/xos/onboard/openvpn/synchronizer/steps/sync_openvpntenant.yaml
similarity index 100%
rename from xos/synchronizers/openvpn/steps/sync_openvpntenant.yaml
rename to xos/onboard/openvpn/synchronizer/steps/sync_openvpntenant.yaml
diff --git a/xos/synchronizers/openvpn/steps/sync_tenantprivilege.py b/xos/onboard/openvpn/synchronizer/steps/sync_tenantprivilege.py
similarity index 100%
rename from xos/synchronizers/openvpn/steps/sync_tenantprivilege.py
rename to xos/onboard/openvpn/synchronizer/steps/sync_tenantprivilege.py
diff --git a/xos/synchronizers/openvpn/stop.sh b/xos/onboard/openvpn/synchronizer/stop.sh
similarity index 100%
rename from xos/synchronizers/openvpn/stop.sh
rename to xos/onboard/openvpn/synchronizer/stop.sh
diff --git a/xos/services/openvpn/templates/connect.vpn.j2 b/xos/onboard/openvpn/templates/connect.vpn.j2
similarity index 100%
rename from xos/services/openvpn/templates/connect.vpn.j2
rename to xos/onboard/openvpn/templates/connect.vpn.j2
diff --git a/xos/services/cord_old/__init__.py b/xos/services/cord_old/__init__.py
deleted file mode 100644
index 8b13789..0000000
--- a/xos/services/cord_old/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/xos/services/cord_old/admin.py b/xos/services/cord_old/admin.py
deleted file mode 100644
index 5bccd11..0000000
--- a/xos/services/cord_old/admin.py
+++ /dev/null
@@ -1,471 +0,0 @@
-from django.contrib import admin
-
-from services.cord.models import *
-from django import forms
-from django.utils.safestring import mark_safe
-from django.contrib.auth.admin import UserAdmin
-from django.contrib.admin.widgets import FilteredSelectMultiple
-from django.contrib.auth.forms import ReadOnlyPasswordHashField
-from django.contrib.auth.signals import user_logged_in
-from django.utils import timezone
-from suit.widgets import LinkedSelect
-from core.admin import ServiceAppAdmin,SliceInline,ServiceAttrAsTabInline, ReadOnlyAwareAdmin, XOSTabularInline, ServicePrivilegeInline, TenantRootTenantInline, TenantRootPrivilegeInline
-from core.middleware import get_request
-
-from functools import update_wrapper
-from django.contrib.admin.views.main import ChangeList
-from django.core.urlresolvers import reverse
-from django.contrib.admin.utils import quote
-
-#-----------------------------------------------------------------------------
-# vOLT
-#-----------------------------------------------------------------------------
-
-class VOLTServiceAdmin(ReadOnlyAwareAdmin):
- model = VOLTService
- verbose_name = "vOLT Service"
- verbose_name_plural = "vOLT Service"
- list_display = ("backend_status_icon", "name", "enabled")
- list_display_links = ('backend_status_icon', 'name', )
- fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description',"view_url","icon_url" ], 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text', )
- inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
-
- extracontext_registered_admins = True
-
- user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
-
- suit_form_tabs =(('general', 'vOLT Service Details'),
- ('administration', 'Administration'),
- #('tools', 'Tools'),
- ('slices','Slices'),
- ('serviceattrs','Additional Attributes'),
- ('serviceprivileges','Privileges'),
- )
-
- suit_form_includes = (('voltadmin.html', 'top', 'administration'),
- ) #('hpctools.html', 'top', 'tools') )
-
- def get_queryset(self, request):
- return VOLTService.get_service_objects_by_user(request.user)
-
-class VOLTTenantForm(forms.ModelForm):
- s_tag = forms.CharField()
- c_tag = forms.CharField()
- creator = forms.ModelChoiceField(queryset=User.objects.all())
-
- def __init__(self,*args,**kwargs):
- super (VOLTTenantForm,self ).__init__(*args,**kwargs)
- self.fields['kind'].widget.attrs['readonly'] = True
- self.fields['provider_service'].queryset = VOLTService.get_service_objects().all()
- if self.instance:
- # fields for the attributes
- self.fields['c_tag'].initial = self.instance.c_tag
- self.fields['s_tag'].initial = self.instance.s_tag
- self.fields['creator'].initial = self.instance.creator
- if (not self.instance) or (not self.instance.pk):
- # default fields for an 'add' form
- self.fields['kind'].initial = VOLT_KIND
- self.fields['creator'].initial = get_request().user
- if VOLTService.get_service_objects().exists():
- self.fields["provider_service"].initial = VOLTService.get_service_objects().all()[0]
-
- def save(self, commit=True):
- self.instance.s_tag = self.cleaned_data.get("s_tag")
- self.instance.c_tag = self.cleaned_data.get("c_tag")
- self.instance.creator = self.cleaned_data.get("creator")
- return super(VOLTTenantForm, self).save(commit=commit)
-
- class Meta:
- model = VOLTTenant
- fields = '__all__'
-
-class VOLTTenantAdmin(ReadOnlyAwareAdmin):
- list_display = ('backend_status_icon', 'id', 'service_specific_id', 's_tag', 'c_tag', 'subscriber_root' )
- list_display_links = ('backend_status_icon', 'id')
- fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'provider_service', 'subscriber_root', 'service_specific_id', # 'service_specific_attribute',
- 's_tag', 'c_tag', 'creator'],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text', 'service_specific_attribute')
- form = VOLTTenantForm
-
- suit_form_tabs = (('general','Details'),)
-
- def get_queryset(self, request):
- return VOLTTenant.get_tenant_objects_by_user(request.user)
-
-class AccessDeviceInline(XOSTabularInline):
- model = AccessDevice
- fields = ['volt_device','uplink','vlan']
- readonly_fields = []
- extra = 0
-# max_num = 0
- suit_classes = 'suit-tab suit-tab-accessdevices'
-
-# @property
-# def selflink_reverse_path(self):
-# return "admin:cord_volttenant_change"
-
-class VOLTDeviceAdmin(ReadOnlyAwareAdmin):
- list_display = ('backend_status_icon', 'name', 'openflow_id', 'driver' )
- list_display_links = ('backend_status_icon', 'name', 'openflow_id')
- fieldsets = [ (None, {'fields': ['backend_status_text','name','volt_service','openflow_id','driver','access_agent'],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text',)
- inlines = [AccessDeviceInline]
-
- suit_form_tabs = (('general','Details'), ('accessdevices','Access Devices'))
-
-class AccessDeviceAdmin(ReadOnlyAwareAdmin):
- list_display = ('backend_status_icon', 'id', 'volt_device', 'uplink', 'vlan' )
- list_display_links = ('backend_status_icon', 'id')
- fieldsets = [ (None, {'fields': ['backend_status_text','volt_device','uplink','vlan'],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text',)
-
- suit_form_tabs = (('general','Details'),)
-
-class AgentPortMappingInline(XOSTabularInline):
- model = AgentPortMapping
- fields = ['access_agent', 'mac', 'port']
- readonly_fields = []
- extra = 0
-# max_num = 0
- suit_classes = 'suit-tab suit-tab-accessportmaps'
-
-# @property
-# def selflink_reverse_path(self):
-# return "admin:cord_volttenant_change"
-
-class AccessAgentAdmin(ReadOnlyAwareAdmin):
- list_display = ('backend_status_icon', 'name', 'mac' )
- list_display_links = ('backend_status_icon', 'name')
- fieldsets = [ (None, {'fields': ['backend_status_text','name','volt_service','mac'],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text',)
- inlines= [AgentPortMappingInline]
-
- suit_form_tabs = (('general','Details'), ('accessportmaps', 'Port Mappings'))
-
-#-----------------------------------------------------------------------------
-# vCPE
-#-----------------------------------------------------------------------------
-
-class VSGServiceForm(forms.ModelForm):
- bbs_api_hostname = forms.CharField(required=False)
- bbs_api_port = forms.IntegerField(required=False)
- bbs_server = forms.CharField(required=False)
- backend_network_label = forms.CharField(required=False)
- bbs_slice = forms.ModelChoiceField(queryset=Slice.objects.all(), required=False)
- dns_servers = forms.CharField(required=False)
- url_filter_kind = forms.ChoiceField(choices=VSGService.URL_FILTER_KIND_CHOICES, required=False)
- node_label = forms.CharField(required=False)
-
- def __init__(self,*args,**kwargs):
- super (VSGServiceForm,self ).__init__(*args,**kwargs)
- if self.instance:
- self.fields['bbs_api_hostname'].initial = self.instance.bbs_api_hostname
- self.fields['bbs_api_port'].initial = self.instance.bbs_api_port
- self.fields['bbs_server'].initial = self.instance.bbs_server
- self.fields['backend_network_label'].initial = self.instance.backend_network_label
- self.fields['bbs_slice'].initial = self.instance.bbs_slice
- self.fields['dns_servers'].initial = self.instance.dns_servers
- self.fields['url_filter_kind']. initial = self.instance.url_filter_kind
- self.fields['node_label'].initial = self.instance.node_label
-
- def save(self, commit=True):
- self.instance.bbs_api_hostname = self.cleaned_data.get("bbs_api_hostname")
- self.instance.bbs_api_port = self.cleaned_data.get("bbs_api_port")
- self.instance.bbs_server = self.cleaned_data.get("bbs_server")
- self.instance.backend_network_label = self.cleaned_data.get("backend_network_label")
- self.instance.bbs_slice = self.cleaned_data.get("bbs_slice")
- self.instance.dns_servers = self.cleaned_data.get("dns_servers")
- self.instance.url_filter_kind = self.cleaned_data.get("url_filter_kind")
- self.instance.node_label = self.cleaned_data.get("node_label")
- return super(VSGServiceForm, self).save(commit=commit)
-
- class Meta:
- model = VSGService
- fields = '__all__'
-
-class VSGServiceAdmin(ReadOnlyAwareAdmin):
- model = VSGService
- verbose_name = "vSG Service"
- verbose_name_plural = "vSG Service"
- list_display = ("backend_status_icon", "name", "enabled")
- list_display_links = ('backend_status_icon', 'name', )
- fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description', "view_url", "icon_url", "service_specific_attribute", "node_label"],
- 'classes':['suit-tab suit-tab-general']}),
- ("backend config", {'fields': [ "backend_network_label", "url_filter_kind", "bbs_api_hostname", "bbs_api_port", "bbs_server", "bbs_slice"],
- 'classes':['suit-tab suit-tab-backend']}),
- ("vSG config", {'fields': ["dns_servers"],
- 'classes':['suit-tab suit-tab-vsg']}) ]
- readonly_fields = ('backend_status_text', "service_specific_attribute")
- inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
- form = VSGServiceForm
-
- extracontext_registered_admins = True
-
- user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
-
- suit_form_tabs =(('general', 'Service Details'),
- ('backend', 'Backend Config'),
- ('vsg', 'vSG Config'),
- ('administration', 'Administration'),
- #('tools', 'Tools'),
- ('slices','Slices'),
- ('serviceattrs','Additional Attributes'),
- ('serviceprivileges','Privileges') ,
- )
-
- suit_form_includes = (('vcpeadmin.html', 'top', 'administration'),
- ) #('hpctools.html', 'top', 'tools') )
-
- def get_queryset(self, request):
- return VSGService.get_service_objects_by_user(request.user)
-
-class VSGTenantForm(forms.ModelForm):
- bbs_account = forms.CharField(required=False)
- creator = forms.ModelChoiceField(queryset=User.objects.all())
- instance = forms.ModelChoiceField(queryset=Instance.objects.all(),required=False)
- last_ansible_hash = forms.CharField(required=False)
- wan_container_ip = forms.CharField(required=False)
- wan_container_mac = forms.CharField(required=False)
-
- def __init__(self,*args,**kwargs):
- super (VSGTenantForm,self ).__init__(*args,**kwargs)
- self.fields['kind'].widget.attrs['readonly'] = True
- self.fields['provider_service'].queryset = VSGService.get_service_objects().all()
- if self.instance:
- # fields for the attributes
- self.fields['bbs_account'].initial = self.instance.bbs_account
- self.fields['creator'].initial = self.instance.creator
- self.fields['instance'].initial = self.instance.instance
- self.fields['last_ansible_hash'].initial = self.instance.last_ansible_hash
- self.fields['wan_container_ip'].initial = self.instance.wan_container_ip
- self.fields['wan_container_mac'].initial = self.instance.wan_container_mac
- if (not self.instance) or (not self.instance.pk):
- # default fields for an 'add' form
- self.fields['kind'].initial = VCPE_KIND
- self.fields['creator'].initial = get_request().user
- if VSGService.get_service_objects().exists():
- self.fields["provider_service"].initial = VSGService.get_service_objects().all()[0]
-
- def save(self, commit=True):
- self.instance.creator = self.cleaned_data.get("creator")
- self.instance.instance = self.cleaned_data.get("instance")
- self.instance.last_ansible_hash = self.cleaned_data.get("last_ansible_hash")
- return super(VSGTenantForm, self).save(commit=commit)
-
- class Meta:
- model = VSGTenant
- fields = '__all__'
-
-
-class VSGTenantAdmin(ReadOnlyAwareAdmin):
- list_display = ('backend_status_icon', 'id', 'subscriber_tenant' )
- list_display_links = ('backend_status_icon', 'id')
- fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'provider_service', 'subscriber_tenant', 'service_specific_id', # 'service_specific_attribute',
- 'wan_container_ip', 'wan_container_mac', 'bbs_account', 'creator', 'instance', 'last_ansible_hash'],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text', 'service_specific_attribute', 'bbs_account', 'wan_container_ip', 'wan_container_mac')
- form = VSGTenantForm
-
- suit_form_tabs = (('general','Details'),)
-
- def get_queryset(self, request):
- return VSGTenant.get_tenant_objects_by_user(request.user)
-
-#-----------------------------------------------------------------------------
-# vBNG
-#-----------------------------------------------------------------------------
-
-class VBNGServiceForm(forms.ModelForm):
- vbng_url = forms.CharField(required=False)
-
- def __init__(self,*args,**kwargs):
- super (VBNGServiceForm,self ).__init__(*args,**kwargs)
- if self.instance:
- self.fields['vbng_url'].initial = self.instance.vbng_url
-
- def save(self, commit=True):
- self.instance.vbng_url = self.cleaned_data.get("vbng_url")
- return super(VBNGServiceForm, self).save(commit=commit)
-
- class Meta:
- model = VBNGService
- fields = '__all__'
-
-
-class VBNGServiceAdmin(ReadOnlyAwareAdmin):
- model = VBNGService
- verbose_name = "vBNG Service"
- verbose_name_plural = "vBNG Service"
- list_display = ("backend_status_icon", "name", "enabled")
- list_display_links = ('backend_status_icon', 'name', )
- fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description', "view_url", "icon_url",
- 'vbng_url' ],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text', )
- inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
- form = VBNGServiceForm
-
- extracontext_registered_admins = True
-
- user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
-
- suit_form_tabs =(('general', 'vBNG Service Details'),
- ('administration', 'Administration'),
- #('tools', 'Tools'),
- ('slices','Slices'),
- ('serviceattrs','Additional Attributes'),
- ('serviceprivileges','Privileges'),
- )
-
- suit_form_includes = (('vbngadmin.html', 'top', 'administration'),
- ) #('hpctools.html', 'top', 'tools') )
-
- def get_queryset(self, request):
- return VBNGService.get_service_objects_by_user(request.user)
-
-class VBNGTenantForm(forms.ModelForm):
- routeable_subnet = forms.CharField(required=False)
- mapped_hostname = forms.CharField(required=False)
- mapped_ip = forms.CharField(required=False)
- mapped_mac = forms.CharField(required=False)
-
- def __init__(self,*args,**kwargs):
- super (VBNGTenantForm,self ).__init__(*args,**kwargs)
- self.fields['kind'].widget.attrs['readonly'] = True
- self.fields['provider_service'].queryset = VBNGService.get_service_objects().all()
- if self.instance:
- # fields for the attributes
- self.fields['routeable_subnet'].initial = self.instance.routeable_subnet
- self.fields['mapped_hostname'].initial = self.instance.mapped_hostname
- self.fields['mapped_ip'].initial = self.instance.mapped_ip
- self.fields['mapped_mac'].initial = self.instance.mapped_mac
- if (not self.instance) or (not self.instance.pk):
- # default fields for an 'add' form
- self.fields['kind'].initial = VBNG_KIND
- if VBNGService.get_service_objects().exists():
- self.fields["provider_service"].initial = VBNGService.get_service_objects().all()[0]
-
- def save(self, commit=True):
- self.instance.routeable_subnet = self.cleaned_data.get("routeable_subnet")
- self.instance.mapped_hostname = self.cleaned_data.get("mapped_hostname")
- self.instance.mapped_ip = self.cleaned_data.get("mapped_ip")
- self.instance.mapped_mac = self.cleaned_data.get("mapped_mac")
- return super(VBNGTenantForm, self).save(commit=commit)
-
- class Meta:
- model = VBNGTenant
- fields = '__all__'
-
-class VBNGTenantAdmin(ReadOnlyAwareAdmin):
- list_display = ('backend_status_icon', 'id', 'subscriber_tenant' )
- list_display_links = ('backend_status_icon', 'id')
- fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'provider_service', 'subscriber_tenant', 'service_specific_id', # 'service_specific_attribute',
- 'routeable_subnet', 'mapped_hostname', 'mapped_ip', 'mapped_mac'],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text', 'service_specific_attribute')
- form = VBNGTenantForm
-
- suit_form_tabs = (('general','Details'),)
-
- def get_queryset(self, request):
- return VBNGTenant.get_tenant_objects_by_user(request.user)
-
-#-----------------------------------------------------------------------------
-# CordSubscriberRoot
-#-----------------------------------------------------------------------------
-
-class VOLTTenantInline(XOSTabularInline):
- model = VOLTTenant
- fields = ['provider_service', 'subscriber_root', 'service_specific_id']
- readonly_fields = ['provider_service', 'subscriber_root', 'service_specific_id']
- extra = 0
- max_num = 0
- suit_classes = 'suit-tab suit-tab-volttenants'
- fk_name = 'subscriber_root'
- verbose_name = 'subscribed tenant'
- verbose_name_plural = 'subscribed tenants'
-
- @property
- def selflink_reverse_path(self):
- return "admin:cord_volttenant_change"
-
- def get_queryset(self, request):
- qs = super(VOLTTenantInline, self).queryset(request)
- return qs.filter(kind=VOLT_KIND)
-
-class CordSubscriberRootForm(forms.ModelForm):
- url_filter_level = forms.CharField(required = False)
- uplink_speed = forms.CharField(required = False)
- downlink_speed = forms.CharField(required = False)
- status = forms.ChoiceField(choices=CordSubscriberRoot.status_choices, required=True)
- enable_uverse = forms.BooleanField(required=False)
- cdn_enable = forms.BooleanField(required=False)
-
- def __init__(self,*args,**kwargs):
- super (CordSubscriberRootForm,self ).__init__(*args,**kwargs)
- self.fields['kind'].widget.attrs['readonly'] = True
- if self.instance:
- self.fields['url_filter_level'].initial = self.instance.url_filter_level
- self.fields['uplink_speed'].initial = self.instance.uplink_speed
- self.fields['downlink_speed'].initial = self.instance.downlink_speed
- self.fields['status'].initial = self.instance.status
- self.fields['enable_uverse'].initial = self.instance.enable_uverse
- self.fields['cdn_enable'].initial = self.instance.cdn_enable
- if (not self.instance) or (not self.instance.pk):
- # default fields for an 'add' form
- self.fields['kind'].initial = CORD_SUBSCRIBER_KIND
- self.fields['uplink_speed'].initial = CordSubscriberRoot.get_default_attribute("uplink_speed")
- self.fields['downlink_speed'].initial = CordSubscriberRoot.get_default_attribute("downlink_speed")
- self.fields['status'].initial = CordSubscriberRoot.get_default_attribute("status")
- self.fields['enable_uverse'].initial = CordSubscriberRoot.get_default_attribute("enable_uverse")
- self.fields['cdn_enable'].initial = CordSubscriberRoot.get_default_attribute("cdn_enable")
-
- def save(self, commit=True):
- self.instance.url_filter_level = self.cleaned_data.get("url_filter_level")
- self.instance.uplink_speed = self.cleaned_data.get("uplink_speed")
- self.instance.downlink_speed = self.cleaned_data.get("downlink_speed")
- self.instance.status = self.cleaned_data.get("status")
- self.instance.enable_uverse = self.cleaned_data.get("enable_uverse")
- self.instance.cdn_enable = self.cleaned_data.get("cdn_enable")
- return super(CordSubscriberRootForm, self).save(commit=commit)
-
- class Meta:
- model = CordSubscriberRoot
- fields = '__all__'
-
-
-class CordSubscriberRootAdmin(ReadOnlyAwareAdmin):
- list_display = ('backend_status_icon', 'id', 'name', )
- list_display_links = ('backend_status_icon', 'id', 'name', )
- fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'name', 'service_specific_id', # 'service_specific_attribute',
- 'url_filter_level', "uplink_speed", "downlink_speed", "status", "enable_uverse", "cdn_enable"],
- 'classes':['suit-tab suit-tab-general']})]
- readonly_fields = ('backend_status_text', 'service_specific_attribute',)
- form = CordSubscriberRootForm
- inlines = (VOLTTenantInline, TenantRootPrivilegeInline)
-
- suit_form_tabs =(('general', 'Cord Subscriber Root Details'),
- ('volttenants','VOLT Tenancy'),
- ('tenantrootprivileges','Privileges')
- )
-
- def get_queryset(self, request):
- return CordSubscriberRoot.get_tenant_objects_by_user(request.user)
-
-admin.site.register(VOLTService, VOLTServiceAdmin)
-admin.site.register(VOLTTenant, VOLTTenantAdmin)
-admin.site.register(VOLTDevice, VOLTDeviceAdmin)
-admin.site.register(AccessDevice, AccessDeviceAdmin)
-admin.site.register(AccessAgent, AccessAgentAdmin)
-
-admin.site.register(VSGService, VSGServiceAdmin)
-admin.site.register(VSGTenant, VSGTenantAdmin)
-admin.site.register(VBNGService, VBNGServiceAdmin)
-admin.site.register(VBNGTenant, VBNGTenantAdmin)
-admin.site.register(CordSubscriberRoot, CordSubscriberRootAdmin)
-
diff --git a/xos/services/cord_old/migrations/0001_initial.py b/xos/services/cord_old/migrations/0001_initial.py
deleted file mode 100644
index 75e30f5..0000000
--- a/xos/services/cord_old/migrations/0001_initial.py
+++ /dev/null
@@ -1,197 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import models, migrations
-import core.models.plcorebase
-import django.utils.timezone
-from django.conf import settings
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('core', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='AccessAgent',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('created', models.DateTimeField(auto_now_add=True)),
- ('updated', models.DateTimeField(default=django.utils.timezone.now)),
- ('enacted', models.DateTimeField(default=None, null=True, blank=True)),
- ('policed', models.DateTimeField(default=None, null=True, blank=True)),
- ('backend_register', models.CharField(default=b'{}', max_length=1024, null=True)),
- ('backend_status', models.CharField(default=b'0 - Provisioning in progress', max_length=1024)),
- ('deleted', models.BooleanField(default=False)),
- ('write_protect', models.BooleanField(default=False)),
- ('lazy_blocked', models.BooleanField(default=False)),
- ('no_sync', models.BooleanField(default=False)),
- ('no_policy', models.BooleanField(default=False)),
- ('name', models.CharField(help_text=b'name of agent', max_length=254)),
- ('mac', models.CharField(help_text=b'MAC Address or Access Agent', max_length=32, null=True, blank=True)),
- ],
- options={
- },
- bases=(models.Model, core.models.plcorebase.PlModelMixIn),
- ),
- migrations.CreateModel(
- name='AccessDevice',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('created', models.DateTimeField(auto_now_add=True)),
- ('updated', models.DateTimeField(default=django.utils.timezone.now)),
- ('enacted', models.DateTimeField(default=None, null=True, blank=True)),
- ('policed', models.DateTimeField(default=None, null=True, blank=True)),
- ('backend_register', models.CharField(default=b'{}', max_length=1024, null=True)),
- ('backend_status', models.CharField(default=b'0 - Provisioning in progress', max_length=1024)),
- ('deleted', models.BooleanField(default=False)),
- ('write_protect', models.BooleanField(default=False)),
- ('lazy_blocked', models.BooleanField(default=False)),
- ('no_sync', models.BooleanField(default=False)),
- ('no_policy', models.BooleanField(default=False)),
- ('uplink', models.IntegerField(null=True, blank=True)),
- ('vlan', models.IntegerField(null=True, blank=True)),
- ],
- options={
- },
- bases=(models.Model, core.models.plcorebase.PlModelMixIn),
- ),
- migrations.CreateModel(
- name='AgentPortMapping',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('created', models.DateTimeField(auto_now_add=True)),
- ('updated', models.DateTimeField(default=django.utils.timezone.now)),
- ('enacted', models.DateTimeField(default=None, null=True, blank=True)),
- ('policed', models.DateTimeField(default=None, null=True, blank=True)),
- ('backend_register', models.CharField(default=b'{}', max_length=1024, null=True)),
- ('backend_status', models.CharField(default=b'0 - Provisioning in progress', max_length=1024)),
- ('deleted', models.BooleanField(default=False)),
- ('write_protect', models.BooleanField(default=False)),
- ('lazy_blocked', models.BooleanField(default=False)),
- ('no_sync', models.BooleanField(default=False)),
- ('no_policy', models.BooleanField(default=False)),
- ('mac', models.CharField(help_text=b'MAC Address', max_length=32, null=True, blank=True)),
- ('port', models.CharField(help_text=b'Openflow port ID', max_length=32, null=True, blank=True)),
- ('access_agent', models.ForeignKey(related_name=b'port_mappings', to='cord.AccessAgent')),
- ],
- options={
- },
- bases=(models.Model, core.models.plcorebase.PlModelMixIn),
- ),
- migrations.CreateModel(
- name='VOLTDevice',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('created', models.DateTimeField(auto_now_add=True)),
- ('updated', models.DateTimeField(default=django.utils.timezone.now)),
- ('enacted', models.DateTimeField(default=None, null=True, blank=True)),
- ('policed', models.DateTimeField(default=None, null=True, blank=True)),
- ('backend_register', models.CharField(default=b'{}', max_length=1024, null=True)),
- ('backend_status', models.CharField(default=b'0 - Provisioning in progress', max_length=1024)),
- ('deleted', models.BooleanField(default=False)),
- ('write_protect', models.BooleanField(default=False)),
- ('lazy_blocked', models.BooleanField(default=False)),
- ('no_sync', models.BooleanField(default=False)),
- ('no_policy', models.BooleanField(default=False)),
- ('name', models.CharField(help_text=b'name of device', max_length=254)),
- ('openflow_id', models.CharField(help_text=b'OpenFlow ID', max_length=254, null=True, blank=True)),
- ('driver', models.CharField(help_text=b'driver', max_length=254, null=True, blank=True)),
- ('access_agent', models.ForeignKey(related_name=b'volt_devices', blank=True, to='cord.AccessAgent', null=True)),
- ],
- options={
- },
- bases=(models.Model, core.models.plcorebase.PlModelMixIn),
- ),
- migrations.CreateModel(
- name='VOLTService',
- fields=[
- ('service_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='core.Service')),
- ],
- options={
- 'verbose_name': 'vOLT Service',
- },
- bases=('core.service',),
- ),
- migrations.CreateModel(
- name='VOLTTenant',
- fields=[
- ('tenant_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='core.Tenant')),
- ('s_tag', models.IntegerField(help_text=b's-tag', null=True, blank=True)),
- ('c_tag', models.IntegerField(help_text=b'c-tag', null=True, blank=True)),
- ('creator', models.ForeignKey(related_name=b'created_volts', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
- ],
- options={
- 'verbose_name': 'vOLT Tenant',
- },
- bases=('core.tenant',),
- ),
- migrations.AddField(
- model_name='voltdevice',
- name='volt_service',
- field=models.ForeignKey(related_name=b'volt_devices', to='cord.VOLTService'),
- preserve_default=True,
- ),
- migrations.AddField(
- model_name='accessdevice',
- name='volt_device',
- field=models.ForeignKey(related_name=b'access_devices', to='cord.VOLTDevice'),
- preserve_default=True,
- ),
- migrations.AddField(
- model_name='accessagent',
- name='volt_service',
- field=models.ForeignKey(related_name=b'access_agents', to='cord.VOLTService'),
- preserve_default=True,
- ),
- migrations.CreateModel(
- name='CordSubscriberRoot',
- fields=[
- ],
- options={
- 'proxy': True,
- },
- bases=('core.subscriber',),
- ),
- migrations.CreateModel(
- name='VBNGService',
- fields=[
- ],
- options={
- 'verbose_name': 'vBNG Service',
- 'proxy': True,
- },
- bases=('core.service',),
- ),
- migrations.CreateModel(
- name='VBNGTenant',
- fields=[
- ],
- options={
- 'proxy': True,
- },
- bases=('core.tenant',),
- ),
- migrations.CreateModel(
- name='VSGService',
- fields=[
- ],
- options={
- 'verbose_name': 'vSG Service',
- 'proxy': True,
- },
- bases=('core.service',),
- ),
- migrations.CreateModel(
- name='VSGTenant',
- fields=[
- ],
- options={
- 'proxy': True,
- },
- bases=('core.tenantwithcontainer',),
- ),
- ]
diff --git a/xos/services/cord_old/models.py b/xos/services/cord_old/models.py
deleted file mode 100644
index 19b3ba6..0000000
--- a/xos/services/cord_old/models.py
+++ /dev/null
@@ -1,846 +0,0 @@
-from django.db import models
-from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, Port, AddressPool, User
-from core.models.plcorebase import StrippedCharField
-import os
-from django.db import models, transaction
-from django.forms.models import model_to_dict
-from django.db.models import Q
-from operator import itemgetter, attrgetter, methodcaller
-from core.models import Tag
-from core.models.service import LeastLoadedNodeScheduler
-from services.vrouter.models import VRouterService, VRouterTenant
-import traceback
-from xos.exceptions import *
-from xos.config import Config
-
-class ConfigurationError(Exception):
- pass
-
-VOLT_KIND = "vOLT"
-VCPE_KIND = "vCPE"
-VBNG_KIND = "vBNG"
-CORD_SUBSCRIBER_KIND = "CordSubscriberRoot"
-
-CORD_USE_VTN = getattr(Config(), "networking_use_vtn", False)
-
-# -------------------------------------------
-# CordSubscriberRoot
-# -------------------------------------------
-
-class CordSubscriberRoot(Subscriber):
- class Meta:
- proxy = True
-
- KIND = CORD_SUBSCRIBER_KIND
-
- status_choices = (("enabled", "Enabled"),
- ("suspended", "Suspended"),
- ("delinquent", "Delinquent"),
- ("copyrightviolation", "Copyright Violation"))
-
- # 'simple_attributes' will be expanded into properties and setters that
- # store the attribute using self.set_attribute / self.get_attribute.
-
- simple_attributes = ( ("firewall_enable", False),
- ("firewall_rules", "accept all anywhere anywhere"),
- ("url_filter_enable", False),
- ("url_filter_rules", "allow all"),
- ("url_filter_level", "PG"),
- ("cdn_enable", False),
- ("devices", []),
- ("is_demo_user", False),
-
- ("uplink_speed", 1000000000), # 1 gigabit, a reasonable default?
- ("downlink_speed", 1000000000),
- ("enable_uverse", True) )
-
- default_attributes = {"status": "enabled"}
-
- sync_attributes = ("firewall_enable",
- "firewall_rules",
- "url_filter_enable",
- "url_filter_rules",
- "cdn_enable",
- "uplink_speed",
- "downlink_speed",
- "enable_uverse",
- "status")
-
- def __init__(self, *args, **kwargs):
- super(CordSubscriberRoot, self).__init__(*args, **kwargs)
- self.cached_volt = None
- self._initial_url_filter_enable = self.url_filter_enable
-
- @property
- def volt(self):
- volt = self.get_newest_subscribed_tenant(VOLTTenant)
- if not volt:
- return None
-
- # always return the same object when possible
- if (self.cached_volt) and (self.cached_volt.id == volt.id):
- return self.cached_volt
-
- #volt.caller = self.creator
- self.cached_volt = volt
- return volt
-
- @property
- def status(self):
- return self.get_attribute("status", self.default_attributes["status"])
-
- @status.setter
- def status(self, value):
- if not value in [x[0] for x in self.status_choices]:
- raise Exception("invalid status %s" % value)
- self.set_attribute("status", value)
-
- def find_device(self, mac):
- for device in self.devices:
- if device["mac"] == mac:
- return device
- return None
-
- def update_device(self, mac, **kwargs):
- # kwargs may be "level" or "mac"
- # Setting one of these to None will cause None to be stored in the db
- devices = self.devices
- for device in devices:
- if device["mac"] == mac:
- for arg in kwargs.keys():
- device[arg] = kwargs[arg]
- self.devices = devices
- return device
- raise ValueError("Device with mac %s not found" % mac)
-
- def create_device(self, **kwargs):
- if "mac" not in kwargs:
- raise XOSMissingField("The mac field is required")
-
- if self.find_device(kwargs['mac']):
- raise XOSDuplicateKey("Device with mac %s already exists" % kwargs["mac"])
-
- device = kwargs.copy()
-
- devices = self.devices
- devices.append(device)
- self.devices = devices
-
- return device
-
- def delete_device(self, mac):
- devices = self.devices
- for device in devices:
- if device["mac"]==mac:
- devices.remove(device)
- self.devices = devices
- return
-
- raise ValueError("Device with mac %s not found" % mac)
-
- #--------------------------------------------------------------------------
- # Deprecated -- devices used to be called users
-
- def find_user(self, uid):
- return self.find_device(uid)
-
- def update_user(self, uid, **kwargs):
- return self.update_device(uid, **kwargs)
-
- def create_user(self, **kwargs):
- return self.create_device(**kwargs)
-
- def delete_user(self, uid):
- return self.delete_user(uid)
-
- # ------------------------------------------------------------------------
-
- @property
- def services(self):
- return {"cdn": self.cdn_enable,
- "url_filter": self.url_filter_enable,
- "firewall": self.firewall_enable}
-
- @services.setter
- def services(self, value):
- pass
-
- def save(self, *args, **kwargs):
- self.validate_unique_service_specific_id(none_okay=True)
- if (not hasattr(self, 'caller') or not self.caller.is_admin):
- if (self.has_field_changed("service_specific_id")):
- raise XOSPermissionDenied("You do not have permission to change service_specific_id")
- super(CordSubscriberRoot, self).save(*args, **kwargs)
- if (self.volt) and (self.volt.vcpe): # and (self._initial_url_filter_enabled != self.url_filter_enable):
- # 1) trigger manage_bbs_account to run
- # 2) trigger vcpe observer to wake up
- self.volt.vcpe.save()
-
-CordSubscriberRoot.setup_simple_attributes()
-
-# -------------------------------------------
-# VOLT
-# -------------------------------------------
-
-class VOLTService(Service):
- KIND = VOLT_KIND
-
- class Meta:
- app_label = "cord"
- verbose_name = "vOLT Service"
-
-class VOLTTenant(Tenant):
- KIND = VOLT_KIND
-
- class Meta:
- app_label = "cord"
- verbose_name = "vOLT Tenant"
-
- s_tag = models.IntegerField(null=True, blank=True, help_text="s-tag")
- c_tag = models.IntegerField(null=True, blank=True, help_text="c-tag")
-
- # at some point, this should probably end up part of Tenant.
- creator = models.ForeignKey(User, related_name='created_volts', blank=True, null=True)
-
- def __init__(self, *args, **kwargs):
- volt_services = VOLTService.get_service_objects().all()
- if volt_services:
- self._meta.get_field("provider_service").default = volt_services[0].id
- super(VOLTTenant, self).__init__(*args, **kwargs)
- self.cached_vcpe = None
-
- @property
- def vcpe(self):
- vcpe = self.get_newest_subscribed_tenant(VSGTenant)
- if not vcpe:
- return None
-
- # always return the same object when possible
- if (self.cached_vcpe) and (self.cached_vcpe.id == vcpe.id):
- return self.cached_vcpe
-
- vcpe.caller = self.creator
- self.cached_vcpe = vcpe
- return vcpe
-
- @vcpe.setter
- def vcpe(self, value):
- raise XOSConfigurationError("vOLT.vCPE cannot be set this way -- create a new vCPE object and set its subscriber_tenant instead")
-
- @property
- def subscriber(self):
- if not self.subscriber_root:
- return None
- subs = CordSubscriberRoot.objects.filter(id=self.subscriber_root.id)
- if not subs:
- return None
- return subs[0]
-
- def manage_vcpe(self):
- # Each VOLT object owns exactly one VCPE object
-
- if self.deleted:
- return
-
- if self.vcpe is None:
- vsgServices = VSGService.get_service_objects().all()
- if not vsgServices:
- raise XOSConfigurationError("No VSG Services available")
-
- vcpe = VSGTenant(provider_service = vsgServices[0],
- subscriber_tenant = self)
- vcpe.caller = self.creator
- vcpe.save()
-
- def manage_subscriber(self):
- if (self.subscriber_root is None):
- # The vOLT is not connected to a Subscriber, so either find an
- # existing subscriber with the same SSID, or autogenerate a new
- # subscriber.
- #
- # TODO: This probably goes away when we rethink the ONOS-to-XOS
- # vOLT API.
-
- subs = CordSubscriberRoot.get_tenant_objects().filter(service_specific_id = self.service_specific_id)
- if subs:
- sub = subs[0]
- else:
- sub = CordSubscriberRoot(service_specific_id = self.service_specific_id,
- name = "autogenerated-for-vOLT-%s" % self.id)
- sub.save()
- self.subscriber_root = sub
- self.save()
-
- def cleanup_vcpe(self):
- if self.vcpe:
- # print "XXX cleanup vcpe", self.vcpe
- self.vcpe.delete()
-
- def cleanup_orphans(self):
- # ensure vOLT only has one vCPE
- cur_vcpe = self.vcpe
- for vcpe in list(self.get_subscribed_tenants(VSGTenant)):
- if (not cur_vcpe) or (vcpe.id != cur_vcpe.id):
- # print "XXX clean up orphaned vcpe", vcpe
- vcpe.delete()
-
- def save(self, *args, **kwargs):
- # VOLTTenant probably doesn't need a SSID anymore; that will be handled
- # by CORDSubscriberRoot...
- # self.validate_unique_service_specific_id()
-
- if (self.subscriber_root is not None):
- subs = self.subscriber_root.get_subscribed_tenants(VOLTTenant)
- if (subs) and (self not in subs):
- raise XOSDuplicateKey("Subscriber should only be linked to one vOLT")
-
- if not self.creator:
- if not getattr(self, "caller", None):
- # caller must be set when creating a vCPE since it creates a slice
- raise XOSProgrammingError("VOLTTenant's self.caller was not set")
- self.creator = self.caller
- if not self.creator:
- raise XOSProgrammingError("VOLTTenant's self.creator was not set")
-
- super(VOLTTenant, self).save(*args, **kwargs)
- model_policy_volt(self.pk)
-
- def delete(self, *args, **kwargs):
- self.cleanup_vcpe()
- super(VOLTTenant, self).delete(*args, **kwargs)
-
-def model_policy_volt(pk):
- # TODO: this should be made in to a real model_policy
- with transaction.atomic():
- volt = VOLTTenant.objects.select_for_update().filter(pk=pk)
- if not volt:
- return
- volt = volt[0]
- volt.manage_vcpe()
- volt.manage_subscriber()
- volt.cleanup_orphans()
-
-class VOLTDevice(PlCoreBase):
- class Meta:
- app_label = "cord"
-
- name = models.CharField(max_length=254, help_text="name of device", null=False, blank=False)
- volt_service = models.ForeignKey(VOLTService, related_name='volt_devices')
- openflow_id = models.CharField(max_length=254, help_text="OpenFlow ID", null=True, blank=True)
- driver = models.CharField(max_length=254, help_text="driver", null=True, blank=True)
- access_agent = models.ForeignKey("AccessAgent", related_name='volt_devices', blank=True, null=True)
-
- def __unicode__(self): return u'%s' % (self.name)
-
-class AccessDevice(PlCoreBase):
- class Meta:
- app_label = "cord"
-
- volt_device = models.ForeignKey(VOLTDevice, related_name='access_devices')
- uplink = models.IntegerField(null=True, blank=True)
- vlan = models.IntegerField(null=True, blank=True)
-
- def __unicode__(self): return u'%s-%d:%d' % (self.volt_device.name,self.uplink,self.vlan)
-
-class AccessAgent(PlCoreBase):
- class Meta:
- app_label = "cord"
-
- name = models.CharField(max_length=254, help_text="name of agent", null=False, blank=False)
- volt_service = models.ForeignKey(VOLTService, related_name='access_agents')
- mac = models.CharField(max_length=32, help_text="MAC Address or Access Agent", null=True, blank=True)
-
- def __unicode__(self): return u'%s' % (self.name)
-
-class AgentPortMapping(PlCoreBase):
- class Meta:
- app_label = "cord"
-
- access_agent = models.ForeignKey(AccessAgent, related_name='port_mappings')
- mac = models.CharField(max_length=32, help_text="MAC Address", null=True, blank=True)
- port = models.CharField(max_length=32, help_text="Openflow port ID", null=True, blank=True)
-
- def __unicode__(self): return u'%s-%s-%s' % (self.access_agent.name, self.port, self.mac)
-
-
-# -------------------------------------------
-# VCPE
-# -------------------------------------------
-
-class VSGService(Service):
- KIND = VCPE_KIND
-
- URL_FILTER_KIND_CHOICES = ( (None, "None"), ("safebrowsing", "Safe Browsing"), ("answerx", "AnswerX") )
-
- simple_attributes = ( ("bbs_api_hostname", None),
- ("bbs_api_port", None),
- ("bbs_server", None),
- ("backend_network_label", "hpc_client"),
- ("dns_servers", "8.8.8.8"),
- ("url_filter_kind", None),
- ("node_label", None) )
-
- def __init__(self, *args, **kwargs):
- super(VSGService, self).__init__(*args, **kwargs)
-
- class Meta:
- app_label = "cord"
- verbose_name = "vSG Service"
- proxy = True
-
- def allocate_bbs_account(self):
- vcpes = VSGTenant.get_tenant_objects().all()
- bbs_accounts = [vcpe.bbs_account for vcpe in vcpes]
-
- # There's a bit of a race here; some other user could be trying to
- # allocate a bbs_account at the same time we are.
-
- for i in range(2,21):
- account_name = "bbs%02d@onlab.us" % i
- if (account_name not in bbs_accounts):
- return account_name
-
- raise XOSConfigurationError("We've run out of available broadbandshield accounts. Delete some vcpe and try again.")
-
- @property
- def bbs_slice(self):
- bbs_slice_id=self.get_attribute("bbs_slice_id")
- if not bbs_slice_id:
- return None
- bbs_slices=Slice.objects.filter(id=bbs_slice_id)
- if not bbs_slices:
- return None
- return bbs_slices[0]
-
- @bbs_slice.setter
- def bbs_slice(self, value):
- if value:
- value = value.id
- self.set_attribute("bbs_slice_id", value)
-
-VSGService.setup_simple_attributes()
-
-class VSGTenant(TenantWithContainer):
- class Meta:
- proxy = True
-
- KIND = VCPE_KIND
-
- sync_attributes = ("wan_container_ip", "wan_container_mac", "wan_container_netbits",
- "wan_container_gateway_ip", "wan_container_gateway_mac",
- "wan_vm_ip", "wan_vm_mac")
-
- default_attributes = {"instance_id": None,
- "container_id": None,
- "users": [],
- "bbs_account": None,
- "last_ansible_hash": None,
- "wan_container_ip": None}
-
- def __init__(self, *args, **kwargs):
- super(VSGTenant, self).__init__(*args, **kwargs)
- self.cached_vrouter=None
-
- @property
- def vbng(self):
- # not supported
- return None
-
- @vbng.setter
- def vbng(self, value):
- raise XOSConfigurationError("vCPE.vBNG cannot be set this way -- create a new vBNG object and set it's subscriber_tenant instead")
-
- @property
- def vrouter(self):
- vrouter = self.get_newest_subscribed_tenant(VRouterTenant)
- if not vrouter:
- return None
-
- # always return the same object when possible
- if (self.cached_vrouter) and (self.cached_vrouter.id == vrouter.id):
- return self.cached_vrouter
-
- vrouter.caller = self.creator
- self.cached_vrouter = vrouter
- return vrouter
-
- @vrouter.setter
- def vrouter(self, value):
- raise XOSConfigurationError("vCPE.vRouter cannot be set this way -- create a new vRuter object and set its subscriber_tenant instead")
-
- @property
- def volt(self):
- if not self.subscriber_tenant:
- return None
- volts = VOLTTenant.objects.filter(id=self.subscriber_tenant.id)
- if not volts:
- return None
- return volts[0]
-
- @property
- def bbs_account(self):
- return self.get_attribute("bbs_account", self.default_attributes["bbs_account"])
-
- @bbs_account.setter
- def bbs_account(self, value):
- return self.set_attribute("bbs_account", value)
-
- @property
- def last_ansible_hash(self):
- return self.get_attribute("last_ansible_hash", self.default_attributes["last_ansible_hash"])
-
- @last_ansible_hash.setter
- def last_ansible_hash(self, value):
- return self.set_attribute("last_ansible_hash", value)
-
- @property
- def ssh_command(self):
- if self.instance:
- return self.instance.get_ssh_command()
- else:
- return "no-instance"
-
- @ssh_command.setter
- def ssh_command(self, value):
- pass
-
- def get_vrouter_field(self, name, default=None):
- if self.vrouter:
- return getattr(self.vrouter, name, default)
- else:
- return default
-
- @property
- def wan_container_ip(self):
- return self.get_vrouter_field("public_ip", None)
-
- @property
- def wan_container_mac(self):
- return self.get_vrouter_field("public_mac", None)
-
- @property
- def wan_container_netbits(self):
- return self.get_vrouter_field("netbits", None)
-
- @property
- def wan_container_gateway_ip(self):
- return self.get_vrouter_field("gateway_ip", None)
-
- @property
- def wan_container_gateway_mac(self):
- return self.get_vrouter_field("gateway_mac", None)
-
- @property
- def wan_vm_ip(self):
- tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
- if tags:
- tenant = VRouterTenant.objects.get(id=tags[0].value)
- return tenant.public_ip
- else:
- raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
-
- @property
- def wan_vm_mac(self):
- tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
- if tags:
- tenant = VRouterTenant.objects.get(id=tags[0].value)
- return tenant.public_mac
- else:
- raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
-
- @property
- def is_synced(self):
- return (self.enacted is not None) and (self.enacted >= self.updated)
-
- @is_synced.setter
- def is_synced(self, value):
- pass
-
- def get_vrouter_service(self):
- vrouterServices = VRouterService.get_service_objects().all()
- if not vrouterServices:
- raise XOSConfigurationError("No VROUTER Services available")
- return vrouterServices[0]
-
- def manage_vrouter(self):
- # Each vCPE object owns exactly one vRouterTenant object
-
- if self.deleted:
- return
-
- if self.vrouter is None:
- vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_tenant = self)
- vrouter.caller = self.creator
- vrouter.save()
-
- def cleanup_vrouter(self):
- if self.vrouter:
- # print "XXX cleanup vrouter", self.vrouter
- self.vrouter.delete()
-
- def cleanup_orphans(self):
- # ensure vCPE only has one vRouter
- cur_vrouter = self.vrouter
- for vrouter in list(self.get_subscribed_tenants(VRouterTenant)):
- if (not cur_vrouter) or (vrouter.id != cur_vrouter.id):
- # print "XXX clean up orphaned vrouter", vrouter
- vrouter.delete()
-
- if self.orig_instance_id and (self.orig_instance_id != self.get_attribute("instance_id")):
- instances=Instance.objects.filter(id=self.orig_instance_id)
- if instances:
- # print "XXX clean up orphaned instance", instances[0]
- instances[0].delete()
-
- def get_slice(self):
- if not self.provider_service.slices.count():
- print self, "dio porco"
- raise XOSConfigurationError("The service has no slices")
- slice = self.provider_service.slices.all()[0]
- return slice
-
- def get_vsg_service(self):
- return VSGService.get_service_objects().get(id=self.provider_service.id)
-
- def find_instance_for_s_tag(self, s_tag):
- #s_tags = STagBlock.objects.find(s_s_tag)
- #if s_tags:
- # return s_tags[0].instance
-
- tags = Tag.objects.filter(name="s_tag", value=s_tag)
- if tags:
- return tags[0].content_object
-
- return None
-
- def find_or_make_instance_for_s_tag(self, s_tag):
- instance = self.find_instance_for_s_tag(self.volt.s_tag)
- if instance:
- return instance
-
- flavors = Flavor.objects.filter(name="m1.small")
- if not flavors:
- raise XOSConfigurationError("No m1.small flavor")
-
- slice = self.provider_service.slices.all()[0]
-
- if slice.default_isolation == "container_vm":
- (node, parent) = ContainerVmScheduler(slice).pick()
- else:
- (node, parent) = LeastLoadedNodeScheduler(slice, label=self.get_vsg_service().node_label).pick()
-
- instance = Instance(slice = slice,
- node = node,
- image = self.image,
- creator = self.creator,
- deployment = node.site_deployment.deployment,
- flavor = flavors[0],
- isolation = slice.default_isolation,
- parent = parent)
-
- self.save_instance(instance)
-
- return instance
-
- def manage_container(self):
- from core.models import Instance, Flavor
-
- if self.deleted:
- return
-
- # For container or container_vm isolation, use what TenantWithCotnainer
- # provides us
- slice = self.get_slice()
- if slice.default_isolation in ["container_vm", "container"]:
- super(VSGTenant,self).manage_container()
- return
-
- if not self.volt:
- raise XOSConfigurationError("This vCPE container has no volt")
-
- if self.instance:
- # We're good.
- return
-
- instance = self.find_or_make_instance_for_s_tag(self.volt.s_tag)
- self.instance = instance
- super(TenantWithContainer, self).save()
-
- def cleanup_container(self):
- if self.get_slice().default_isolation in ["container_vm", "container"]:
- super(VSGTenant,self).cleanup_container()
-
- # To-do: cleanup unused instances
- pass
-
- def manage_bbs_account(self):
- if self.deleted:
- return
-
- if self.volt and self.volt.subscriber and self.volt.subscriber.url_filter_enable:
- if not self.bbs_account:
- # make sure we use the proxied VSGService object, not the generic Service object
- vcpe_service = VSGService.objects.get(id=self.provider_service.id)
- self.bbs_account = vcpe_service.allocate_bbs_account()
- super(VSGTenant, self).save()
- else:
- if self.bbs_account:
- self.bbs_account = None
- super(VSGTenant, self).save()
-
- def find_or_make_port(self, instance, network, **kwargs):
- port = Port.objects.filter(instance=instance, network=network)
- if port:
- port = port[0]
- else:
- port = Port(instance=instance, network=network, **kwargs)
- port.save()
- return port
-
- def get_lan_network(self, instance):
- slice = self.provider_service.slices.all()[0]
- if CORD_USE_VTN:
- # there should only be one network private network, and its template should not be the management template
- lan_networks = [x for x in slice.networks.all() if x.template.visibility=="private" and (not "management" in x.template.name)]
- if len(lan_networks)>1:
- raise XOSProgrammingError("The vSG slice should only have one non-management private network")
- else:
- lan_networks = [x for x in slice.networks.all() if "lan" in x.name]
- if not lan_networks:
- raise XOSProgrammingError("No lan_network")
- return lan_networks[0]
-
- def save_instance(self, instance):
- with transaction.atomic():
- instance.volumes = "/etc/dnsmasq.d,/etc/ufw"
- super(VSGTenant, self).save_instance(instance)
-
- if instance.isolation in ["container", "container_vm"]:
- lan_network = self.get_lan_network(instance)
- port = self.find_or_make_port(instance, lan_network, ip="192.168.0.1", port_id="unmanaged")
- port.set_parameter("c_tag", self.volt.c_tag)
- port.set_parameter("s_tag", self.volt.s_tag)
- port.set_parameter("device", "eth1")
- port.set_parameter("bridge", "br-lan")
-
- wan_networks = [x for x in instance.slice.networks.all() if "wan" in x.name]
- if not wan_networks:
- raise XOSProgrammingError("No wan_network")
- port = self.find_or_make_port(instance, wan_networks[0])
- port.set_parameter("next_hop", value="10.0.1.253") # FIX ME
- port.set_parameter("device", "eth0")
-
- if instance.isolation in ["vm"]:
- lan_network = self.get_lan_network(instance)
- port = self.find_or_make_port(instance, lan_network)
- port.set_parameter("c_tag", self.volt.c_tag)
- port.set_parameter("s_tag", self.volt.s_tag)
- port.set_parameter("neutron_port_name", "stag-%s" % self.volt.s_tag)
- port.save()
-
- # tag the instance with the s-tag, so we can easily find the
- # instance later
- if self.volt and self.volt.s_tag:
- tags = Tag.objects.filter(name="s_tag", value=self.volt.s_tag)
- if not tags:
- tag = Tag(service=self.provider_service, content_object=instance, name="s_tag", value=self.volt.s_tag)
- tag.save()
-
- # VTN-CORD needs a WAN address for the VM, so that the VM can
- # be configured.
- if CORD_USE_VTN:
- tags = Tag.select_by_content_object(instance).filter(name="vm_vrouter_tenant")
- if not tags:
- vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_service = self.provider_service)
- vrouter.set_attribute("tenant_for_instance_id", instance.id)
- vrouter.save()
- tag = Tag(service=self.provider_service, content_object=instance, name="vm_vrouter_tenant", value="%d" % vrouter.id)
- tag.save()
-
- def save(self, *args, **kwargs):
- if not self.creator:
- if not getattr(self, "caller", None):
- # caller must be set when creating a vCPE since it creates a slice
- raise XOSProgrammingError("VSGTenant's self.caller was not set")
- self.creator = self.caller
- if not self.creator:
- raise XOSProgrammingError("VSGTenant's self.creator was not set")
-
- super(VSGTenant, self).save(*args, **kwargs)
- model_policy_vcpe(self.pk)
-
- def delete(self, *args, **kwargs):
- self.cleanup_vrouter()
- self.cleanup_container()
- super(VSGTenant, self).delete(*args, **kwargs)
-
-def model_policy_vcpe(pk):
- # TODO: this should be made in to a real model_policy
- with transaction.atomic():
- vcpe = VSGTenant.objects.select_for_update().filter(pk=pk)
- if not vcpe:
- return
- vcpe = vcpe[0]
- vcpe.manage_container()
- vcpe.manage_vrouter()
- vcpe.manage_bbs_account()
- vcpe.cleanup_orphans()
-
-#----------------------------------------------------------------------------
-# vBNG
-#----------------------------------------------------------------------------
-
-class VBNGService(Service):
- KIND = VBNG_KIND
-
- simple_attributes = ( ("vbng_url", ""), ) # "http://10.0.3.136:8181/onos/virtualbng/"
-
- class Meta:
- app_label = "cord"
- verbose_name = "vBNG Service"
- proxy = True
-
-VBNGService.setup_simple_attributes()
-
-class VBNGTenant(Tenant):
- class Meta:
- proxy = True
-
- KIND = VBNG_KIND
-
- default_attributes = {"routeable_subnet": "",
- "mapped_ip": "",
- "mapped_mac": "",
- "mapped_hostname": ""}
-
- @property
- def routeable_subnet(self):
- return self.get_attribute("routeable_subnet", self.default_attributes["routeable_subnet"])
-
- @routeable_subnet.setter
- def routeable_subnet(self, value):
- self.set_attribute("routeable_subnet", value)
-
- @property
- def mapped_ip(self):
- return self.get_attribute("mapped_ip", self.default_attributes["mapped_ip"])
-
- @mapped_ip.setter
- def mapped_ip(self, value):
- self.set_attribute("mapped_ip", value)
-
- @property
- def mapped_mac(self):
- return self.get_attribute("mapped_mac", self.default_attributes["mapped_mac"])
-
- @mapped_mac.setter
- def mapped_mac(self, value):
- self.set_attribute("mapped_mac", value)
-
- @property
- def mapped_hostname(self):
- return self.get_attribute("mapped_hostname", self.default_attributes["mapped_hostname"])
-
- @mapped_hostname.setter
- def mapped_hostname(self, value):
- self.set_attribute("mapped_hostname", value)
diff --git a/xos/services/cord_old/templates/vbngadmin.html b/xos/services/cord_old/templates/vbngadmin.html
deleted file mode 100644
index cceaee0..0000000
--- a/xos/services/cord_old/templates/vbngadmin.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<div class = "row text-center">
- <div class="col-xs-12">
- <a class="btn btn-primary" href="/admin/cord/vbngtenant/">vBNG Tenants</a>
- </div>
-</div>
-
diff --git a/xos/services/openvpn/__init__.py b/xos/services/openvpn/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/xos/services/openvpn/__init__.py
+++ /dev/null
diff --git a/xos/synchronizers/onboarding/xosbuilder.py b/xos/synchronizers/onboarding/xosbuilder.py
index 51499bd..6e24a28 100644
--- a/xos/synchronizers/onboarding/xosbuilder.py
+++ b/xos/synchronizers/onboarding/xosbuilder.py
@@ -256,9 +256,9 @@
containers = {}
- containers["xos_db"] = \
- {"image": "xosproject/xos-postgres",
- "expose": [5432]}
+# containers["xos_db"] = \
+# {"image": "xosproject/xos-postgres",
+# "expose": [5432]}
db_container_name = xos.docker_project_name + "_xos_db_1"
@@ -266,14 +266,14 @@
{"image": "xosproject/xos-ui",
"command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.ui_port,
"ports": {"%d"%xos.ui_port : "%d"%xos.ui_port},
- "links": ["xos_db"],
- #"external_links": [db_container_name],
+ #"links": ["xos_db"],
+ "external_links": ["%s:%s" % (db_container_name, "xos_db")],
"volumes": volume_list}
# containers["xos_bootstrap_ui"] = {"image": "xosproject/xos",
# "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.bootstrap_ui_port,
# "ports": {"%d"%xos.bootstrap_ui_port : "%d"%xos.bootstrap_ui_port},
-# #"external_links": [db_container_name],
+# #"external_links": ["%s:%s" % (db_container_name, "xos_db")],
# "links": ["xos_db"],
# "volumes": volume_list}
@@ -292,8 +292,8 @@
containers["xos_synchronizer_%s" % c.name] = \
{"image": "xosproject/xos-synchronizer-%s" % c.name,
"command": command,
- #"external_links": [db_container_name],
- "links": ["xos_db"],
+ "external_links": ["%s:%s" % (db_container_name, "xos_db")],
+ #"links": ["xos_db"],
"volumes": volume_list}
vars = { "containers": containers }
diff --git a/xos/synchronizers/openvpn/__init__.py b/xos/synchronizers/openvpn/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/xos/synchronizers/openvpn/__init__.py
+++ /dev/null
diff --git a/xos/tests/api/dredd.yml b/xos/tests/api/dredd.yml
index a214c7c..9e32eba 100644
--- a/xos/tests/api/dredd.yml
+++ b/xos/tests/api/dredd.yml
@@ -32,4 +32,4 @@
hooks-worker-handler-host: localhost
hooks-worker-handler-port: 61321
blueprint: apiary.apib
-endpoint: 'http://127.0.0.1:8000/'
+endpoint: 'http://127.0.0.1:9999/'
diff --git a/xos/tests/api/hooks.py b/xos/tests/api/hooks.py
index 707b258..e7ba705 100644
--- a/xos/tests/api/hooks.py
+++ b/xos/tests/api/hooks.py
@@ -153,13 +153,6 @@
# print 'vcpe network relation added'
- # vbng service
- vbng_service = VBNGService()
- vbng_service.name = 'service_vbng'
- vbng_service.save()
-
- # print 'vbng_service creater'
-
# volt tenant
vt = VOLTTenant(subscriber=subscriber.id, id=1)
vt.s_tag = "222"
diff --git a/xos/services/cord_old/rest_examples/add_truckroll.sh b/xos/tests/rest_examples/add_truckroll.sh
similarity index 100%
rename from xos/services/cord_old/rest_examples/add_truckroll.sh
rename to xos/tests/rest_examples/add_truckroll.sh
diff --git a/xos/services/cord_old/rest_examples/add_volt_tenant.sh b/xos/tests/rest_examples/add_volt_tenant.sh
similarity index 100%
rename from xos/services/cord_old/rest_examples/add_volt_tenant.sh
rename to xos/tests/rest_examples/add_volt_tenant.sh
diff --git a/xos/services/cord_old/rest_examples/config.sh b/xos/tests/rest_examples/config.sh
similarity index 100%
rename from xos/services/cord_old/rest_examples/config.sh
rename to xos/tests/rest_examples/config.sh
diff --git a/xos/services/cord_old/rest_examples/delete_volt_tenant.sh b/xos/tests/rest_examples/delete_volt_tenant.sh
similarity index 100%
rename from xos/services/cord_old/rest_examples/delete_volt_tenant.sh
rename to xos/tests/rest_examples/delete_volt_tenant.sh
diff --git a/xos/services/cord_old/rest_examples/list_cord_subscribers.sh b/xos/tests/rest_examples/list_cord_subscribers.sh
similarity index 100%
rename from xos/services/cord_old/rest_examples/list_cord_subscribers.sh
rename to xos/tests/rest_examples/list_cord_subscribers.sh
diff --git a/xos/services/cord_old/rest_examples/list_truckrolls.sh b/xos/tests/rest_examples/list_truckrolls.sh
similarity index 100%
rename from xos/services/cord_old/rest_examples/list_truckrolls.sh
rename to xos/tests/rest_examples/list_truckrolls.sh
diff --git a/xos/services/cord_old/rest_examples/list_volt_tenants.sh b/xos/tests/rest_examples/list_volt_tenants.sh
similarity index 100%
rename from xos/services/cord_old/rest_examples/list_volt_tenants.sh
rename to xos/tests/rest_examples/list_volt_tenants.sh
diff --git a/xos/tools/xos-manage b/xos/tools/xos-manage
index 3478da3..e10a47e 100755
--- a/xos/tools/xos-manage
+++ b/xos/tools/xos-manage
@@ -143,14 +143,6 @@
python ./manage.py makemigrations requestrouter
python ./manage.py makemigrations syndicate_storage
python ./manage.py makemigrations mcord
-# python ./manage.py makemigrations ceilometer
-# python ./manage.py makemigrations onos
- python ./manage.py makemigrations openvpn
-# python ./manage.py makemigrations vtr
-# python ./manage.py makemigrations vrouter
-# python ./manage.py makemigrations vtn
-# python ./manage.py makemigrations fabric
- #python ./manage.py makemigrations servcomp
if [[ -e /opt/xos/xos/xosbuilder_migration_list ]]; then
cat /opt/xos/xos/xosbuilder_migration_list | while read line; do
diff --git a/xos/tosca/resources/xosmodel.py b/xos/tosca/resources/xosmodel.py
index 86aa8a8..343fb1f 100644
--- a/xos/tosca/resources/xosmodel.py
+++ b/xos/tosca/resources/xosmodel.py
@@ -17,7 +17,7 @@
class XOSVolume(XOSResource):
provides = "tosca.nodes.XOSVolume"
xos_model = XOSVolume
- copyin_props = ["host_path"]
+ copyin_props = ["host_path", "read_only"]
name_field = "container_path"
def get_xos_args(self, throw_exception=True):
diff --git a/xos/xos/config.py b/xos/xos/config.py
index 8ae7816..154ba20 100644
--- a/xos/xos/config.py
+++ b/xos/xos/config.py
@@ -81,8 +81,12 @@
if filename:
try:
if os.path.isdir(filename):
- config_list = list(reversed(os.listdir(filename)))
- config_list.remove('README.md')
+ config_list = list(sorted(os.listdir(filename)))
+ config_list = [x for x in config_list if not x.endswith(".md")]
+ if "xos_common_config" in config_list:
+ # move xos_common_config to the front of the list
+ config_list.remove("xos_common_config")
+ config_list=["xos_common_config"] + config_list
config_list = [os.path.join(filename, s) for s in config_list]
self.config.read(config_list)
else:
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index 53d9c2c..85b5128 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -183,15 +183,8 @@
'core',
'services.hpc',
'services.mcord',
-# 'services.onos',
-# 'services.ceilometer',
'services.requestrouter',
'services.syndicate_storage',
- 'services.openvpn',
-# 'services.vtr',
-# 'services.vrouter',
-# 'services.vtn',
-# 'services.fabric',
'geoposition',
'rest_framework_swagger',
)