Moved existing version of sample application to jenkins repo
diff --git a/auto-scale/gui/.bowerrc b/auto-scale/gui/.bowerrc
new file mode 100644
index 0000000..e491038
--- /dev/null
+++ b/auto-scale/gui/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "src/vendor/"
+}
\ No newline at end of file
diff --git a/auto-scale/gui/.editorconfig b/auto-scale/gui/.editorconfig
new file mode 100644
index 0000000..e717f5e
--- /dev/null
+++ b/auto-scale/gui/.editorconfig
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/auto-scale/gui/.eslintrc b/auto-scale/gui/.eslintrc
new file mode 100644
index 0000000..868fd4b
--- /dev/null
+++ b/auto-scale/gui/.eslintrc
@@ -0,0 +1,44 @@
+{
+ "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": [0, {"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,
+ "no-undef": 2,
+
+ //"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,
+ "Chart": true
+ }
+}
\ No newline at end of file
diff --git a/auto-scale/gui/.gitignore b/auto-scale/gui/.gitignore
new file mode 100644
index 0000000..429d79a
--- /dev/null
+++ b/auto-scale/gui/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+bower_components/
+coverage/
+.sass-cache/
+.idea/
+.tmp/
+dist/
+src/vendor
+src/.tmp
diff --git a/auto-scale/gui/bower.json b/auto-scale/gui/bower.json
new file mode 100644
index 0000000..3286dd3
--- /dev/null
+++ b/auto-scale/gui/bower.json
@@ -0,0 +1,43 @@
+{
+ "name": "xos-ceilometerDashboard",
+ "version": "0.0.0",
+ "authors": [
+ "Matteo Scandolo <teo@onlab.us>"
+ ],
+ "description": "The ceilometerDashboard view",
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "static/js/vendor/",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "angular-chart.js": "~0.10.2",
+ "angular-animate": "~1.4.8",
+ "ui.bootstrap": "~0.14.3",
+ "jquery": "~2.2.3",
+ "angular": "~1.4.7",
+ "angular-ui-router": "~0.2.15",
+ "ng-lodash": "~0.3.0",
+ "bootstrap-css": "3.3.4"
+ },
+ "overrides": {
+ "ui.bootstrap": {
+ "main": [
+ "src/accordion/accordion.js",
+ "src/collapse/collapse.js"
+ ]
+ }
+ },
+ "devDependencies": {
+ "angular-mocks": "~1.4.8"
+ },
+ "resolutions": {
+ "jquery": "~2.1.4",
+ "angular-chart.js": "~0.8.7",
+ "Chart.js": "~1.0.1"
+ }
+}
diff --git a/auto-scale/gui/env/default.js b/auto-scale/gui/env/default.js
new file mode 100644
index 0000000..2153a31
--- /dev/null
+++ b/auto-scale/gui/env/default.js
@@ -0,0 +1,3 @@
+module.exports = {
+ host: 'http://130.127.133.58:9991/'
+}
diff --git a/auto-scale/gui/env/srikanth.js b/auto-scale/gui/env/srikanth.js
new file mode 100644
index 0000000..0489fb9
--- /dev/null
+++ b/auto-scale/gui/env/srikanth.js
@@ -0,0 +1,3 @@
+module.exports = {
+ host: 'http://128.104.222.16:9991'
+}
\ No newline at end of file
diff --git a/auto-scale/gui/gulp/build.js b/auto-scale/gui/gulp/build.js
new file mode 100644
index 0000000..9f9bfb8
--- /dev/null
+++ b/auto-scale/gui/gulp/build.js
@@ -0,0 +1,149 @@
+'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');
+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');
+
+var TEMPLATE_FOOTER = `}]);
+angular.module('xos.ceilometerDashboard').run(function($location){$location.path('/')});
+angular.bootstrap(angular.element('#xosCeilometerDashboard'), ['xos.ceilometerDashboard']);`;
+
+module.exports = function(options){
+
+ // delete previous builded file
+ gulp.task('clean', function(){
+ return del(
+ [options.dashboards + 'xosCeilometerDashboard.html'],
+ {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/'));
+ });
+
+ gulp.task('copyCss', ['css'], function(){
+ return gulp.src([`${options.tmp}/css/*.css`])
+ .pipe(concat('xosCeilometerDashboard.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('xosCeilometerDashboard.js'))
+ .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.ceilometerDashboard',
+ root: 'templates/',
+ templateFooter: TEMPLATE_FOOTER
+ }))
+ .pipe(gulp.dest(options.tmp));
+ });
+
+ // copy html index to Django Folder
+ gulp.task('copyHtml', ['clean'], function(){
+ return gulp.src(options.src + 'index.html')
+ // remove dev dependencies from html
+ .pipe(replace(/<!-- bower:css -->(\n.*)*\n<!-- endbower --><!-- endcss -->/, ''))
+ .pipe(replace(/<!-- bower:js -->(\n.*)*\n<!-- endbower --><!-- endjs -->/, ''))
+ .pipe(replace(/ng-app=".*"\s/, ''))
+ //rewriting css path
+ // .pipe(replace(/(<link.*">)/, ''))
+ // injecting minified files
+ .pipe(
+ inject(
+ gulp.src([
+ options.static + 'js/vendor/xosCeilometerDashboardVendor.js',
+ options.static + 'js/xosCeilometerDashboard.js',
+ options.static + 'css/xosCeilometerDashboard.css'
+ ])
+ )
+ )
+ .pipe(rename('xosCeilometerDashboard.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('xosCeilometerDashboardVendor.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('build', function() {
+ runSequence(
+ 'lint',
+ 'templates',
+ 'babel',
+ 'scripts',
+ 'wiredep',
+ 'copyHtml',
+ 'copyCss',
+ 'cleanTmp'
+ );
+ });
+};
\ No newline at end of file
diff --git a/auto-scale/gui/gulp/server.js b/auto-scale/gui/gulp/server.js
new file mode 100644
index 0000000..0f69064
--- /dev/null
+++ b/auto-scale/gui/gulp/server.js
@@ -0,0 +1,149 @@
+'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 del = require('del');
+var httpProxy = require('http-proxy');
+
+const environment = process.env.NODE_ENV;
+
+if (environment){
+ var conf = require(`../env/${environment}.js`);
+}
+else{
+ var conf = require('../env/default.js')
+}
+
+console.log(conf);
+
+var proxy = httpProxy.createProxyServer({
+ target: conf.host || 'http://0.0.0.0:9999'
+});
+
+proxy.on('error', function(error, req, res) {
+ res.writeHead(500, {
+ 'Content-Type': 'text/plain'
+ });
+
+ console.error('[Proxy]', error);
+});
+
+module.exports = function(options){
+
+ // open in browser with sync and proxy to 0.0.0.0
+ gulp.task('browser', function() {
+ browserSync.init({
+ // reloadDelay: 500,
+ // logLevel: 'debug',
+ // logConnections: true,
+ // directory: true,
+ startPath: '#/',
+ snippetOptions: {
+ rule: {
+ match: /<!-- browserSync -->/i
+ }
+ },
+ server: {
+ baseDir: options.src,
+ middleware: function(req, res, next){
+ if(
+ req.url.indexOf('autoscaledata') !== -1
+ ){
+ 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.src + '**/*.css', function(){
+ browserSync.reload();
+ });
+ });
+
+ // 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', function(){
+ runSequence(
+ 'cleanTmp',
+ 'babel',
+ function() {
+ return gulp.src(options.src + 'index.html')
+ .pipe(
+ inject(
+ gulp.src([
+ options.tmp + '**/*.js',
+ options.api + '*.js',
+ options.helpers + '**/*.js'
+ ])
+ // .pipe(debug({title: 'unicorn:'}))
+ .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'),
+ {
+ 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(
+ 'bower',
+ 'injectScript',
+ 'injectCss',
+ ['browser']
+ );
+ });
+};
diff --git a/auto-scale/gui/gulpfile.js b/auto-scale/gui/gulpfile.js
new file mode 100644
index 0000000..de911e9
--- /dev/null
+++ b/auto-scale/gui/gulpfile.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var gulp = require('gulp');
+var wrench = require('wrench');
+
+var options = {
+ src: 'src/',
+ css: 'src/css/',
+ scripts: 'src/js/',
+ tmp: 'src/.tmp',
+ dist: 'dist/',
+ api: '../../ngXosLib/api/',
+ helpers: '../../ngXosLib/xosHelpers/src/',
+ static: '../../static/', // this is the django static folder
+ dashboards: '../../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/auto-scale/gui/karma.conf.js b/auto-scale/gui/karma.conf.js
new file mode 100644
index 0000000..b67c208
--- /dev/null
+++ b/auto-scale/gui/karma.conf.js
@@ -0,0 +1,94 @@
+// 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);
+});
+/*eslint-enable*/
+module.exports = function(config) {
+ 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([
+ 'src/css/**/*.css',
+ 'src/js/main.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/**/*.js': ['babel'],
+ 'src/**/*.html': ['ng-html2js']
+ },
+
+ babelPreprocessor: {
+ options: {
+ presets: ['es2015'],
+ sourceMap: 'inline'
+ }
+ },
+
+ 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/auto-scale/gui/package.json b/auto-scale/gui/package.json
new file mode 100644
index 0000000..0a9ccae
--- /dev/null
+++ b/auto-scale/gui/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "xos-ceilometerDashboard",
+ "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",
+ "pretest": "npm install",
+ "test": "karma start",
+ "lint": "eslint src/js/"
+ },
+ "keywords": [
+ "XOS",
+ "Angular",
+ "XOSlib"
+ ],
+ "author": "Matteo Scandolo",
+ "license": "MIT",
+ "dependencies": {},
+ "devDependencies": {
+ "autoprefixer": "^6.1.2",
+ "babel": "^6.3.13",
+ "babel-preset-es2015": "^6.3.13",
+ "browser-sync": "^2.9.11",
+ "css-mqpacker": "^4.0.0",
+ "csswring": "^4.1.1",
+ "del": "^2.0.2",
+ "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-debug": "^2.1.2",
+ "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-uglify": "^1.4.2",
+ "http-proxy": "^1.12.0",
+ "jasmine-core": "^2.4.1",
+ "karma": "^0.13.15",
+ "karma-babel-preprocessor": "^6.0.1",
+ "karma-jasmine": "^0.3.6",
+ "karma-mocha-reporter": "^1.1.3",
+ "karma-ng-html2js-preprocessor": "^0.2.0",
+ "karma-phantomjs-launcher": "^0.2.1",
+ "lodash": "^3.10.1",
+ "mocha": "^2.3.4",
+ "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/auto-scale/gui/spec/.eslintrc b/auto-scale/gui/spec/.eslintrc
new file mode 100644
index 0000000..ad4bc2d
--- /dev/null
+++ b/auto-scale/gui/spec/.eslintrc
@@ -0,0 +1,9 @@
+{
+ "globals" :{
+ "describe": true,
+ "beforeEach": true,
+ "it": true,
+ "inject": true,
+ "expect": true
+ }
+}
diff --git a/auto-scale/gui/spec/autoscaling.test.js b/auto-scale/gui/spec/autoscaling.test.js
new file mode 100644
index 0000000..54caf0e
--- /dev/null
+++ b/auto-scale/gui/spec/autoscaling.test.js
@@ -0,0 +1,48 @@
+'use strict';
+
+describe('In autoscaling app', () => {
+ var scope, element, vm, httpBackend, Service;
+
+
+
+ beforeEach(module('autoscaling'));
+ beforeEach(module('templates'));
+
+ beforeEach(inject(($httpBackend, $rootScope, Autoscaling) => {
+ httpBackend = $httpBackend;
+ scope = $rootScope.$new();
+ Service = Autoscaling;
+ }));
+
+ describe('the serviceContainer', () => {
+ beforeEach(inject(function($httpBackend, $compile){
+
+ httpBackend.whenGET('/autoscaledata').respond(200, autoscalingMock);
+
+ element = angular.element('<service-container></service-container>');
+ $compile(element)(scope);
+
+ scope.$digest();
+ vm = element.isolateScope().vm;
+ httpBackend.flush();
+ }));
+
+ it('should correctly format data', () => {
+ expect(vm.services['service1']).toBeDefined();
+ expect(vm.services['service1']['slice1']).toBeDefined();
+ expect(vm.services['service1']['slice1']['instance1']).toBeDefined();
+
+ expect(vm.services['service1']['slice1']['instance1'][0].counter_volume).toBe(10);
+ expect(vm.services['service1']['slice1']['instance1'][1].counter_volume).toBe(11);
+
+ // triggering the function with 2 resources
+ vm.printData(Service.formatData(autoscalingMock2instances));
+
+ const keys = Object.keys(vm.services['service1']['slice1']);
+
+ expect(vm.services['service1']['slice1'][keys[0]][0].counter_volume).toBe(10);
+ expect(vm.services['service1']['slice1'][keys[0]][1].counter_volume).toBe(11);
+
+ });
+ });
+});
\ No newline at end of file
diff --git a/auto-scale/gui/spec/autoscaling_data.mock.js b/auto-scale/gui/spec/autoscaling_data.mock.js
new file mode 100644
index 0000000..e9f637a
--- /dev/null
+++ b/auto-scale/gui/spec/autoscaling_data.mock.js
@@ -0,0 +1,64 @@
+var autoscalingMock =[
+ {
+ 'slice': 'slice1',
+ 'service': 'service1',
+ 'project_id': 'project1',
+ 'resources': {
+ 'resource1': {
+ 'queue': [
+ {
+ 'timestamp': '2015-12-17T22:55:36Z',
+ 'counter_volume': 10,
+ },
+ {
+ 'timestamp': '2015-12-17T22:55:46Z',
+ 'counter_volume': 11,
+ }
+ ],
+ 'xos_instance_info': {
+ 'instance_name': 'instance1'
+ }
+ }
+ }
+ }
+];
+
+var autoscalingMock2instances =[
+ {
+ 'slice': 'slice1',
+ 'service': 'service1',
+ 'project_id': 'project1',
+ 'resources': {
+ 'resource2': {
+ 'queue': [
+ {
+ 'timestamp': '2015-12-17T22:55:36Z',
+ 'counter_volume': 20,
+ },
+ {
+ 'timestamp': '2015-12-17T22:55:46Z',
+ 'counter_volume': 21,
+ }
+ ],
+ 'xos_instance_info': {
+ 'instance_name': 'instance2'
+ }
+ },
+ 'resource1': {
+ 'queue': [
+ {
+ 'timestamp': '2015-12-17T22:55:36Z',
+ 'counter_volume': 10,
+ },
+ {
+ 'timestamp': '2015-12-17T22:55:46Z',
+ 'counter_volume': 11,
+ }
+ ],
+ 'xos_instance_info': {
+ 'instance_name': 'instance1'
+ }
+ },
+ }
+ }
+];
\ No newline at end of file
diff --git a/auto-scale/gui/src/css/style.css b/auto-scale/gui/src/css/style.css
new file mode 100644
index 0000000..260b6df
--- /dev/null
+++ b/auto-scale/gui/src/css/style.css
@@ -0,0 +1,134 @@
+body {
+ padding-top: 50px;
+}
+
+.list-group-item.active > a {
+ color: white;
+}
+
+/* ANIMATION */
+.animate-repeat{
+ /*background: red;*/
+}
+
+.animate-repeat.ng-move,
+.animate-repeat.ng-enter,
+.animate-repeat.ng-leave {
+ transition:all linear 0.5s;
+}
+
+/* Element Exit */
+.animate-repeat.ng-leave.ng-leave-active,
+.animate-repeat.ng-move,
+.animate-repeat.ng-enter {
+ /*opacity:0;*/
+ /*animation:1.5s bounceOutDown ease;*/
+}
+
+/* Element Enter */
+.animate-repeat.ng-leave,
+.animate-repeat.ng-move.ng-move-active,
+.animate-repeat.ng-enter.ng-enter-active {
+ /*opacity:1;*/
+ /*animation:1.5s bounceOutUp ease;*/
+}
+
+@keyframes bounceInUp {
+ from, 60%, 75%, 90%, to {
+ animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ }
+
+ from {
+ opacity: 0;
+ transform: translate3d(0, 3000px, 0);
+ }
+
+ 60% {
+ opacity: 1;
+ transform: translate3d(0, -20px, 0);
+ }
+
+ 75% {
+ transform: translate3d(0, 10px, 0);
+ }
+
+ 90% {
+ transform: translate3d(0, -5px, 0);
+ }
+
+ to {
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+@keyframes bounceOutUp {
+ 20% {
+ transform: translate3d(0, -10px, 0);
+ }
+
+ 40%, 45% {
+ opacity: 1;
+ transform: translate3d(0, 20px, 0);
+ }
+
+ to {
+ opacity: 0;
+ transform: translate3d(0, -2000px, 0);
+ }
+}
+
+/* LOADER */
+.loader {
+ font-size: 10px;
+ margin: 150px auto;
+ text-indent: -9999em;
+ width: 11em;
+ height: 11em;
+ border-radius: 50%;
+ background: #ffffff;
+ background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ position: relative;
+ -webkit-animation: load3 1.4s infinite linear;
+ animation: load3 1.4s infinite linear;
+ -webkit-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+}
+.loader:before {
+ width: 50%;
+ height: 50%;
+ background: #105E9E;
+ border-radius: 100% 0 0 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ content: '';
+}
+.loader:after {
+ background: #fff;
+ width: 75%;
+ height: 75%;
+ border-radius: 50%;
+ content: '';
+ margin: auto;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
+
+@keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/auto-scale/gui/src/index.html b/auto-scale/gui/src/index.html
new file mode 100644
index 0000000..51bad93
--- /dev/null
+++ b/auto-scale/gui/src/index.html
@@ -0,0 +1,72 @@
+<!-- browserSync -->
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>XOS - Autoscaling Framework</title>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1, max-scale=1">
+ <!-- bower:css -->
+ <link rel="stylesheet" href="vendor/angular-chart.js/dist/angular-chart.css" />
+ <link rel="stylesheet" href="vendor/bootstrap-css/css/bootstrap.min.css" />
+ <!-- endbower --><!-- endcss -->
+ <!-- inject:css -->
+ <link rel="stylesheet" href="/css/style.css">
+ <!-- endinject -->
+</head>
+<body>
+
+</body>
+</html>
+
+<div ng-app="autoscaling">
+
+ <nav class="navbar navbar-inverse navbar-fixed-top">
+ <div class="container">
+ <div class="navbar-header">
+ <!-- <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button> -->
+ <a class="navbar-brand" href="#">XOS - Autoscaling Framework</a>
+ </div>
+ <!-- <div id="navbar" class="collapse navbar-collapse">
+ <ul class="nav navbar-nav">
+ <li class="active"><a href="#">Home</a></li>
+ <li><a href="#about">About</a></li>
+ <li><a href="#contact">Contact</a></li>
+ </ul>
+ </div><!--/.nav-collapse --> -->
+ </div>
+ </nav>
+
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <div ui-view ng-class="stateName"></div>
+ </div>
+ </div>
+ </div>
+ <!-- bower:js -->
+ <script src="vendor/angular/angular.js"></script>
+ <script src="vendor/Chart.js/Chart.js"></script>
+ <script src="vendor/angular-chart.js/dist/angular-chart.js"></script>
+ <script src="vendor/angular-animate/angular-animate.js"></script>
+ <script src="vendor/ui.bootstrap/src/accordion/accordion.js"></script>
+ <script src="vendor/ui.bootstrap/src/collapse/collapse.js"></script>
+ <script src="vendor/jquery/dist/jquery.js"></script>
+ <script src="vendor/angular-ui-router/release/angular-ui-router.js"></script>
+ <script src="vendor/ng-lodash/build/ng-lodash.js"></script>
+ <script src="vendor/bootstrap-css/js/bootstrap.min.js"></script>
+ <script src="vendor/angular-mocks/angular-mocks.js"></script>
+ <!-- endbower --><!-- endjs -->
+
+ <!-- inject:js -->
+ <script src="/.tmp/main.js"></script>
+ <script src="/.tmp/autoscaling_details.directive.js"></script>
+ <script src="/.tmp/autoscaling.service.js"></script>
+ <!-- endinject -->
+</div>
+
diff --git a/auto-scale/gui/src/js/autoscaling.service.js b/auto-scale/gui/src/js/autoscaling.service.js
new file mode 100644
index 0000000..2762fe7
--- /dev/null
+++ b/auto-scale/gui/src/js/autoscaling.service.js
@@ -0,0 +1,71 @@
+'use strict';
+
+angular.module('autoscaling')
+.service('Autoscaling', function($http, $interval, $rootScope, lodash, $q){
+
+ const pollingFrequency = 10;
+ var pollinginterval;
+
+ /**
+ * Convert data to a flat array of resources
+ */
+
+ this.formatData = (data) => {
+ const list = [];
+ // cicle trough all slices
+ lodash.map(data, (item) => {
+ // cicle trough every resource
+ item.resources = lodash.forEach(
+ Object.keys(item.resources),
+ (resource) => {
+ const tmp = item.resources[resource];
+ tmp.service = item.service;
+ tmp.slice = item.slice;
+ tmp.project_id = item.project_id;
+ tmp.instance_name = tmp.xos_instance_info.instance_name;
+ delete tmp.xos_instance_info;
+ list.push(tmp);
+ }
+ )
+ });
+ return list;
+ };
+
+ function requestData(url){
+
+ const deferred = $q.defer();
+
+ $http.get(url)
+ .success((res) => {
+ deferred.resolve(res);
+ })
+ .error((e) => {
+ deferred.reject(e);
+ });
+
+ return deferred.promise;
+ };
+
+
+ // TODO Move to Websocket
+ this.getAutoscalingData = () => {
+
+ requestData('/autoscaledata')
+ .then((res) => {
+ $rootScope.$emit('autoscaling.update', this.formatData(res));
+ })
+ .catch((e) => {
+ $rootScope.$emit('autoscaling.error', this.formatData(e));
+ });
+
+ pollinginterval = $interval(() => {
+ requestData('/autoscaledata')
+ .then((res) => {
+ $rootScope.$emit('autoscaling.update', this.formatData(res));
+ })
+ .catch((e) => {
+ $rootScope.$emit('autoscaling.error', this.formatData(e));
+ });
+ }, pollingFrequency * 1000)
+ };
+});
\ No newline at end of file
diff --git a/auto-scale/gui/src/js/autoscaling_details.directive.js b/auto-scale/gui/src/js/autoscaling_details.directive.js
new file mode 100644
index 0000000..76cce2d
--- /dev/null
+++ b/auto-scale/gui/src/js/autoscaling_details.directive.js
@@ -0,0 +1,197 @@
+angular.module('autoscaling')
+.directive('serviceContainer', function(lodash, Autoscaling){
+ return {
+ restrict: 'E',
+ scope: {},
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/service-container.tpl.html',
+ controller: function($rootScope) {
+
+ this.loader = true;
+
+ // set to true when a service is manually selected
+ this.manualSelect = false;
+
+ // start polling
+ Autoscaling.getAutoscalingData();
+
+ // list to polling events
+ $rootScope.$on('autoscaling.update', (evt, data) => {
+
+ if (data.length > 0) {
+ this.loader = false;
+ };
+ this.printData(data);
+ });
+
+ // handle errors
+ $rootScope.$on('autoscaling.error', (evt, err) => {
+ this.loader = false;
+ this.error = err.data.message;
+ });
+
+ /**
+ * Group resources by service and slice
+ */
+ this.printData = (data) => {
+ this.services = lodash.groupBy(data, 'service');
+ lodash.forEach(Object.keys(this.services), (service) => {
+ this.services[service] = lodash.groupBy(this.services[service], 'slice');
+ lodash.forEach(Object.keys(this.services[service]), (slice) => {
+ // grouping instance by name
+ this.services[service][slice] = lodash.groupBy(this.services[service][slice], 'instance_name');
+ // instance can't have the same name,
+ // so take them out of an array
+ // and keep only the sample data
+ lodash.forEach(Object.keys(this.services[service][slice]), (instance) => {
+ // TODO maintain the instance order
+ this.services[service][slice][instance] = this.services[service][slice][instance][0].queue;
+ });
+
+ })
+ });
+ // arbitrary set the first service in the list as the selected one
+ if(!this.manualSelect){
+ this.serviceName = Object.keys(this.services)[0];
+ this.selectedService = this.services[Object.keys(this.services)[0]];
+ }
+ else{
+ this.selectedService = this.services[this.serviceName]
+ }
+ };
+
+ /**
+ * Change the current selected service
+ */
+
+ this.selectService = (serviceName) => {
+ this.serviceName = serviceName;
+ this.selectedService = this.services[serviceName];
+ this.manualSelect = true;
+ };
+ }
+ };
+})
+.directive('serviceDetail', function(lodash){
+ return {
+ restrict: 'E',
+ scope: {
+ service: '=service'
+ },
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/service-detail.tpl.html',
+ controller: function($scope) {
+
+ }
+ };
+})
+.directive('sliceDetail', function(lodash){
+ return {
+ restrict: 'E',
+ scope: {
+ instances: '=instances'
+ },
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/slice-detail.tpl.html',
+ controller: function($scope, $timeout) {
+
+ this.chart = {
+ options: {
+ datasetFill: false,
+ animation: true,
+ // animationEasing: 'easeInBack'
+ }
+ };
+
+ this.chartColors = [
+ '#286090',
+ '#F7464A',
+ '#46BFBD',
+ '#FDB45C',
+ '#97BBCD',
+ '#4D5360',
+ '#8c4f9f'
+ ];
+
+ Chart.defaults.global.colours = this.chartColors;
+
+ /**
+ * Goes trough the array and format date to be used as labels
+ *
+ * @param Array data
+ * @returns Array a list of labels
+ */
+
+ this.getLabels = (data) => {
+ // we should compare the labels and get the last available
+ return this.prependValues(
+ data.reduce((list, item) => {
+ let date = new Date(item.timestamp);
+ list.push(`${date.getHours()}:${(date.getMinutes()<10?'0':'') + date.getMinutes()}:${date.getSeconds()}`);
+ return list;
+ }, [])
+ , '');
+ };
+
+ /**
+ * Prepend value if the array is less than 10 element
+ */
+ this.prependValues = (list, value) => {
+ if(list.length < 10){
+ list.unshift(value);
+ // call itself to check again
+ return this.prependValues(list, value);
+ }
+ return list;
+ }
+
+ /**
+ * Convert an object of array,
+ * in an array of arrays of values
+ */
+ this.getData = (data, instanceNames) => {
+ return lodash.map(instanceNames, (item) => {
+ return this.prependValues(lodash.reduce(data[item], (list, sample) => {
+ // console.log(data[item], sample);
+ list.push(sample.counter_volume);
+ return list;
+ }, []), null);
+ });
+ };
+
+ this.getMostRecentSeries = (instances) => {
+ // console.log(instances);
+ const newestValues = [];
+ instances = lodash.toArray(instances)
+ lodash.forEach(instances, (values) => {
+ newestValues.push(lodash.max(values, item => new Date(item.timestamp)));
+ });
+
+ var highestValue = 0;
+ var newestInstanceIndex = lodash.findIndex(newestValues, (val) => {
+ return new Date(val.timestamp) > highestValue;
+ });
+
+ return instances[newestInstanceIndex]
+ }
+
+ this.drawChart = (data) => {
+
+ const instanceNames = Object.keys(data);
+
+ this.chart.labels = this.getLabels(this.getMostRecentSeries(data));
+ this.chart.series = instanceNames;
+ this.chart.data = this.getData(data, instanceNames);
+ }
+
+ $scope.$watch(() => this.instances, (val) => {
+ $timeout(()=>{this.chart.options.animation = false}, 1000);
+ this.drawChart(val)
+ });
+
+ }
+ };
+});
\ No newline at end of file
diff --git a/auto-scale/gui/src/js/main.js b/auto-scale/gui/src/js/main.js
new file mode 100644
index 0000000..468f355
--- /dev/null
+++ b/auto-scale/gui/src/js/main.js
@@ -0,0 +1,17 @@
+'use strict';
+
+angular.module('autoscaling', [
+ 'ngLodash',
+ 'ui.router',
+ 'ngAnimate',
+ 'chart.js'
+])
+.config(($stateProvider, $urlRouterProvider) => {
+ $stateProvider
+ .state('ceilometerDashboard', {
+ url: '/',
+ template: '<service-container></service-container>'
+ });
+
+ $urlRouterProvider.otherwise('/');
+});
diff --git a/auto-scale/gui/src/mocks/mock.json b/auto-scale/gui/src/mocks/mock.json
new file mode 100644
index 0000000..aa91dd2
--- /dev/null
+++ b/auto-scale/gui/src/mocks/mock.json
@@ -0,0 +1 @@
+[{"slice": "mysite_onos_volt", "service": "service_ONOS_vOLT", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "bfef8341327245d682c7bada50aceecb", "resources": {"71863bc9-1840-4014-9a7c-fee8d94d3f77": {"queue": [{"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 10.6, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 11.1, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 10.799999999999999, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 9.9, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 11.2, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 11.1, "project_id": "bfef8341327245d682c7bada50aceecb"}], "xos_instance_info": {"instance_name": "mysite_onos_volt-4"}}}}, {"slice": "mysite_onos_vbng", "service": "service_ONOS_vBNG", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "7c918e6765c24ee281f3ee8692fd102c", "resources": {"4065c7d0-62bc-486a-ad3e-87603d2ab33b": {"queue": [{"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 9.7, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 11.5, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 12.399999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 11.299999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 11.799999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 12.299999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}], "xos_instance_info": {"instance_name": "mysite_onos_vbng-2"}}}}, {"slice": "mysite_vcpe", "service": "service_vcpe", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "61a5d4b37b5d43718854916079760c0d", "resources": {"41625531-8243-4471-b435-574618df0484": {"queue": [{"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}], "xos_instance_info": {"instance_name": "mysite_vcpe-7"}}}}, {"slice": "mysite_volt", "service": null, "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "f506380a49a24389ae0d8469274e3279", "resources": {"9452c9e2-e358-4302-ba79-0d19b7b9be84": {"queue": [{"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "f506380a49a24389ae0d8469274e3279"}], "xos_instance_info": {"instance_name": "mysite_volt-5"}}}}, {"slice": "mysite_vbng", "service": null, "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "c92cf450df4640d6952c0276730e5048", "resources": {"40aec721-b7bc-49b2-9e1f-1f2a9005cb41": {"queue": [{"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "c92cf450df4640d6952c0276730e5048"}], "xos_instance_info": {"instance_name": "mysite_vbng-6"}}}}, {"slice": "mysite_clients", "service": null, "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "f884c440474b40808a8adcdece5f45ef", "resources": {"036742d1-ff67-4c5b-8bde-58d0fc752b1d": {"queue": [{"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f884c440474b40808a8adcdece5f45ef"}], "xos_instance_info": {"instance_name": "mysite_clients-3"}}}}, {"slice": "mysite_ceilometer", "service": "service_ceilometer", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "920d70bc63574552bbb3c3fb262ee1bc", "resources": {"627f7de4-634a-4205-8eb4-93c40a34e862": {"queue": [{"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}], "xos_instance_info": {"instance_name": "mysite_ceilometer-8"}}}}]
\ No newline at end of file
diff --git a/auto-scale/gui/src/templates/service-container.tpl.html b/auto-scale/gui/src/templates/service-container.tpl.html
new file mode 100644
index 0000000..34cdcca
--- /dev/null
+++ b/auto-scale/gui/src/templates/service-container.tpl.html
@@ -0,0 +1,26 @@
+<div class="row">
+ <div class="col-xs-12">
+ <h1>Service detail</h1>
+ </div>
+</div>
+<div class="row">
+ <div class="col-sm-12" ng-show="vm.loader">
+ <div class="loader"></div>
+ </div>
+ <div class="col-sm-3">
+ <ul class="list-group">
+ <li class="list-group-item" ng-class="{active: vm.serviceName == service}" ng-repeat="(service, slices) in vm.services">
+ <a href="" ng-click="vm.selectService(service)">{{service}}</a>
+ </li>
+ </ul>
+ </div>
+
+ <div class="col-sm-9 repeat-container" ng-show="!vm.error">
+ <service-detail service="vm.selectedService"></service-detail>
+ </div>
+ <div class="col-sm-9" ng-show="vm.error">
+ <div class="alert alert-danger">
+ {{vm.error}}
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/auto-scale/gui/src/templates/service-detail.tpl.html b/auto-scale/gui/src/templates/service-detail.tpl.html
new file mode 100644
index 0000000..f0e3636
--- /dev/null
+++ b/auto-scale/gui/src/templates/service-detail.tpl.html
@@ -0,0 +1,8 @@
+<div class="animate-repeat panel panel-default" ng-repeat="(slice, instances) in vm.service">
+ <div class="panel-heading">
+ <h3 class="panel-title">{{slice}}</h3>
+ </div>
+ <div class="panel-body">
+ <slice-detail instances="instances"></slice-detail>
+ </div>
+</div>
\ No newline at end of file
diff --git a/auto-scale/gui/src/templates/slice-detail.tpl.html b/auto-scale/gui/src/templates/slice-detail.tpl.html
new file mode 100644
index 0000000..6e63d5b
--- /dev/null
+++ b/auto-scale/gui/src/templates/slice-detail.tpl.html
@@ -0,0 +1,4 @@
+<canvas id="line" class="chart chart-line" chart-data="vm.chart.data"
+ chart-labels="vm.chart.labels" chart-legend="true" chart-series="vm.chart.series"
+ chart-options="vm.chart.options">
+</canvas>
\ No newline at end of file