From edbe3568a052da8afd24b6877c4c6fdcc7627ba3 Mon Sep 17 00:00:00 2001 From: chenjiankun Date: Mon, 24 Jul 2017 04:13:56 +0000 Subject: [PATCH] Yardstick GUI & GUI deployment JIRA: YARDSTICK-758 As E release plan, we have the need of yardstick GUI. This patch is GUI front end code and deployment. The backend code is yardstick API. Change-Id: Ib15f78bcc50168c7828beff97256e9939c6da809 Signed-off-by: chenjiankun --- api/database/v2/models.py | 2 +- api/resources/v2/images.py | 2 + api/resources/v2/tasks.py | 3 +- api/resources/v2/testcases.py | 2 +- api/urls.py | 3 + docker/Dockerfile | 1 + docker/nginx.sh | 31 + docker/supervisor.sh | 26 + api/api-prepare.sh => docker/uwsgi.sh | 54 +- gui/Gruntfile.js | 492 ++++++++++++++ gui/app/404.html | 152 +++++ gui/app/favicon.ico | Bin 0 -> 4286 bytes gui/app/images/back.png | Bin 0 -> 1976 bytes gui/app/images/checkno.png | Bin 0 -> 5849 bytes gui/app/images/checkyes.png | Bin 0 -> 6423 bytes gui/app/images/close.png | Bin 0 -> 1211 bytes gui/app/images/loading.gif | Bin 0 -> 130310 bytes gui/app/images/loading2.gif | Bin 0 -> 25405 bytes gui/app/images/statusno.png | Bin 0 -> 3861 bytes gui/app/images/statusyes.png | Bin 0 -> 2577 bytes gui/app/images/url.json | 1 + gui/app/images/yeoman.png | Bin 0 -> 13501 bytes gui/app/index.html | 111 ++++ gui/app/robots.txt | 4 + gui/app/scripts/app.js | 30 + .../scripts/controllers/container.controller.js | 182 ++++++ gui/app/scripts/controllers/content.controller.js | 136 ++++ gui/app/scripts/controllers/detail.controller.js | 384 +++++++++++ gui/app/scripts/controllers/image.controller.js | 166 +++++ gui/app/scripts/controllers/main.js | 725 +++++++++++++++++++++ gui/app/scripts/controllers/pod.controller.js | 179 +++++ gui/app/scripts/controllers/project.controller.js | 160 +++++ .../controllers/projectDetail.controller.js | 690 ++++++++++++++++++++ gui/app/scripts/controllers/report.controller.js | 115 ++++ .../scripts/controllers/suitecreate.controller.js | 104 +++ .../scripts/controllers/suitedetail.controller.js | 48 ++ gui/app/scripts/controllers/task.controller.js | 175 +++++ .../scripts/controllers/taskModify.controller.js | 533 +++++++++++++++ gui/app/scripts/controllers/testcase.controller.js | 154 +++++ .../controllers/testcasedetail.controller.js | 50 ++ gui/app/scripts/controllers/testsuit.controller.js | 119 ++++ gui/app/scripts/factory/main.factory.js | 247 +++++++ gui/app/scripts/router.config.js | 184 ++++++ gui/app/styles/main.css | 208 ++++++ gui/app/views/container.html | 134 ++++ gui/app/views/content.html | 0 gui/app/views/environmentDetail.html | 143 ++++ gui/app/views/environmentList.html | 155 +++++ gui/app/views/layout/footer.html | 5 + gui/app/views/layout/header.html | 43 ++ gui/app/views/layout/sideNav.html | 141 ++++ gui/app/views/layout/sideNav2.html | 108 +++ gui/app/views/main.html | 174 +++++ gui/app/views/main2.html | 174 +++++ gui/app/views/modal/chooseContainer.html | 15 + gui/app/views/modal/deleteConfirm.html | 19 + gui/app/views/modal/environmentDialog.html | 330 ++++++++++ gui/app/views/modal/projectCreate.html | 21 + gui/app/views/modal/suiteName.html | 18 + gui/app/views/modal/taskCreate.html | 134 ++++ gui/app/views/podupload.html | 136 ++++ gui/app/views/projectList.html | 57 ++ gui/app/views/projectdetail.html | 97 +++ gui/app/views/report.html | 56 ++ gui/app/views/suite.html | 149 +++++ gui/app/views/suitedetail.html | 110 ++++ gui/app/views/taskList.html | 62 ++ gui/app/views/taskmodify.html | 162 +++++ gui/app/views/testcasechoose.html | 48 ++ gui/app/views/testcasedetail.html | 110 ++++ gui/app/views/testcaselist.html | 150 +++++ gui/app/views/uploadImage.html | 145 +++++ gui/bower.json | 45 ++ gui/gui.sh | 8 + gui/package.json | 43 ++ gui/test/.jshintrc | 18 + gui/test/karma.conf.js | 93 +++ gui/test/spec/controllers/main.js | 23 + install.sh | 5 +- 79 files changed, 8554 insertions(+), 50 deletions(-) create mode 100755 docker/nginx.sh create mode 100755 docker/supervisor.sh rename api/api-prepare.sh => docker/uwsgi.sh (59%) create mode 100644 gui/Gruntfile.js create mode 100644 gui/app/404.html create mode 100644 gui/app/favicon.ico create mode 100644 gui/app/images/back.png create mode 100644 gui/app/images/checkno.png create mode 100644 gui/app/images/checkyes.png create mode 100644 gui/app/images/close.png create mode 100644 gui/app/images/loading.gif create mode 100644 gui/app/images/loading2.gif create mode 100644 gui/app/images/statusno.png create mode 100644 gui/app/images/statusyes.png create mode 100644 gui/app/images/url.json create mode 100644 gui/app/images/yeoman.png create mode 100644 gui/app/index.html create mode 100644 gui/app/robots.txt create mode 100644 gui/app/scripts/app.js create mode 100644 gui/app/scripts/controllers/container.controller.js create mode 100644 gui/app/scripts/controllers/content.controller.js create mode 100644 gui/app/scripts/controllers/detail.controller.js create mode 100644 gui/app/scripts/controllers/image.controller.js create mode 100644 gui/app/scripts/controllers/main.js create mode 100644 gui/app/scripts/controllers/pod.controller.js create mode 100644 gui/app/scripts/controllers/project.controller.js create mode 100644 gui/app/scripts/controllers/projectDetail.controller.js create mode 100644 gui/app/scripts/controllers/report.controller.js create mode 100644 gui/app/scripts/controllers/suitecreate.controller.js create mode 100644 gui/app/scripts/controllers/suitedetail.controller.js create mode 100644 gui/app/scripts/controllers/task.controller.js create mode 100644 gui/app/scripts/controllers/taskModify.controller.js create mode 100644 gui/app/scripts/controllers/testcase.controller.js create mode 100644 gui/app/scripts/controllers/testcasedetail.controller.js create mode 100644 gui/app/scripts/controllers/testsuit.controller.js create mode 100644 gui/app/scripts/factory/main.factory.js create mode 100644 gui/app/scripts/router.config.js create mode 100644 gui/app/styles/main.css create mode 100644 gui/app/views/container.html create mode 100644 gui/app/views/content.html create mode 100644 gui/app/views/environmentDetail.html create mode 100644 gui/app/views/environmentList.html create mode 100644 gui/app/views/layout/footer.html create mode 100644 gui/app/views/layout/header.html create mode 100644 gui/app/views/layout/sideNav.html create mode 100644 gui/app/views/layout/sideNav2.html create mode 100644 gui/app/views/main.html create mode 100644 gui/app/views/main2.html create mode 100644 gui/app/views/modal/chooseContainer.html create mode 100644 gui/app/views/modal/deleteConfirm.html create mode 100644 gui/app/views/modal/environmentDialog.html create mode 100644 gui/app/views/modal/projectCreate.html create mode 100644 gui/app/views/modal/suiteName.html create mode 100644 gui/app/views/modal/taskCreate.html create mode 100644 gui/app/views/podupload.html create mode 100644 gui/app/views/projectList.html create mode 100644 gui/app/views/projectdetail.html create mode 100644 gui/app/views/report.html create mode 100644 gui/app/views/suite.html create mode 100644 gui/app/views/suitedetail.html create mode 100644 gui/app/views/taskList.html create mode 100644 gui/app/views/taskmodify.html create mode 100644 gui/app/views/testcasechoose.html create mode 100644 gui/app/views/testcasedetail.html create mode 100644 gui/app/views/testcaselist.html create mode 100644 gui/app/views/uploadImage.html create mode 100644 gui/bower.json create mode 100755 gui/gui.sh create mode 100644 gui/package.json create mode 100644 gui/test/.jshintrc create mode 100644 gui/test/karma.conf.js create mode 100644 gui/test/spec/controllers/main.js diff --git a/api/database/v2/models.py b/api/database/v2/models.py index 64d49cc9d..1e85559cb 100644 --- a/api/database/v2/models.py +++ b/api/database/v2/models.py @@ -31,7 +31,7 @@ class V2Environment(Base): class V2Openrc(Base): - __tablename__ = 'V2_openrc' + __tablename__ = 'v2_openrc' id = Column(Integer, primary_key=True) uuid = Column(String(30)) name = Column(String(30)) diff --git a/api/resources/v2/images.py b/api/resources/v2/images.py index 701818493..a1577b5d3 100644 --- a/api/resources/v2/images.py +++ b/api/resources/v2/images.py @@ -29,6 +29,8 @@ class V2Images(ApiResource): else: images = [self.get_info(change_obj_to_dict(i)) for i in images_list] status = 1 if all(i['status'] == 'ACTIVE' for i in images) else 0 + if not images: + status = 0 return result_handler(consts.API_SUCCESS, {'status': status, 'images': images}) diff --git a/api/resources/v2/tasks.py b/api/resources/v2/tasks.py index 9790d7640..e95ae0550 100644 --- a/api/resources/v2/tasks.py +++ b/api/resources/v2/tasks.py @@ -106,7 +106,8 @@ class V2Task(ApiResource): if project.tasks: LOG.info('update tasks in project') - new_task_list = project.tasks.split(',').remove(task_id) + new_task_list = project.tasks.split(',') + new_task_list.remove(task_id) if new_task_list: new_tasks = ','.join(new_task_list) else: diff --git a/api/resources/v2/testcases.py b/api/resources/v2/testcases.py index 81b4aa88c..8d5b5e398 100644 --- a/api/resources/v2/testcases.py +++ b/api/resources/v2/testcases.py @@ -17,7 +17,7 @@ class V2Testcases(ApiResource): def get(self): param = Param({}) testcase_list = Testcase().list_all(param) - return result_handler(consts.API_SUCCESS, testcase_list) + return result_handler(consts.API_SUCCESS, {'testcases': testcase_list}) def post(self): return self._dispatch_post() diff --git a/api/urls.py b/api/urls.py index 2211348f3..3fef91af8 100644 --- a/api/urls.py +++ b/api/urls.py @@ -26,15 +26,18 @@ urlpatterns = [ Url('/api/v2/yardstick/environments/action', 'v2_environments'), Url('/api/v2/yardstick/environments/', 'v2_environment'), + Url('/api/v2/yardstick/openrcs', 'v2_openrcs'), Url('/api/v2/yardstick/openrcs/action', 'v2_openrcs'), Url('/api/v2/yardstick/openrcs/', 'v2_openrc'), + Url('/api/v2/yardstick/pods', 'v2_pods'), Url('/api/v2/yardstick/pods/action', 'v2_pods'), Url('/api/v2/yardstick/pods/', 'v2_pod'), Url('/api/v2/yardstick/images', 'v2_images'), Url('/api/v2/yardstick/images/action', 'v2_images'), + Url('/api/v2/yardstick/containers', 'v2_containers'), Url('/api/v2/yardstick/containers/action', 'v2_containers'), Url('/api/v2/yardstick/containers/', 'v2_container'), diff --git a/docker/Dockerfile b/docker/Dockerfile index 2c4270a09..b48a550bf 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -37,6 +37,7 @@ RUN git clone --depth 1 -b $BRANCH https://gerrit.opnfv.org/gerrit/storperf ${ST WORKDIR ${YARDSTICK_REPO_DIR} RUN ${YARDSTICK_REPO_DIR}/install.sh +RUN ${YARDSTICK_REPO_DIR}/docker/supervisor.sh RUN echo "daemon off;" >> /etc/nginx/nginx.conf diff --git a/docker/nginx.sh b/docker/nginx.sh new file mode 100755 index 000000000..26937d134 --- /dev/null +++ b/docker/nginx.sh @@ -0,0 +1,31 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# nginx config +nginx_config='/etc/nginx/conf.d/yardstick.conf' + +if [[ ! -e "${nginx_config}" ]];then + + cat << EOF > "${nginx_config}" +server { + listen 5000; + server_name localhost; + index index.htm index.html; + location / { + include uwsgi_params; + uwsgi_pass unix:///var/run/yardstick.sock; + } + + location /gui/ { + alias /etc/nginx/yardstick/gui/; + } +} +EOF +fi diff --git a/docker/supervisor.sh b/docker/supervisor.sh new file mode 100755 index 000000000..b67de2212 --- /dev/null +++ b/docker/supervisor.sh @@ -0,0 +1,26 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# nginx service start when boot +supervisor_config='/etc/supervisor/conf.d/yardstick.conf' + +if [[ ! -e "${supervisor_config}" ]];then + cat << EOF > "${supervisor_config}" +[supervisord] +nodaemon = true + +[program:nginx] +command = service nginx restart + +[program:yardstick_uwsgi] +directory = /etc/yardstick +command = uwsgi -i yardstick.ini +EOF +fi diff --git a/api/api-prepare.sh b/docker/uwsgi.sh similarity index 59% rename from api/api-prepare.sh rename to docker/uwsgi.sh index 7632d9da9..cf4612332 100755 --- a/api/api-prepare.sh +++ b/docker/uwsgi.sh @@ -1,6 +1,6 @@ #!/bin/bash ############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 @@ -12,6 +12,13 @@ # generate uwsgi config file mkdir -p /etc/yardstick + +# create api log directory +mkdir -p /var/log/yardstick + +# create yardstick.sock for communicating +touch /var/run/yardstick.sock + uwsgi_config='/etc/yardstick/yardstick.ini' if [[ ! -e "${uwsgi_config}" ]];then @@ -37,48 +44,3 @@ EOF echo "virtualenv = ${YARDSTICK_VENV}" >> "${uwsgi_config}" fi fi - -# nginx config -nginx_config='/etc/nginx/conf.d/yardstick.conf' - -if [[ ! -e "${nginx_config}" ]];then - - cat << EOF > "${nginx_config}" -server { - listen 5000; - server_name localhost; - index index.htm index.html; - location / { - include uwsgi_params; - uwsgi_pass unix:///var/run/yardstick.sock; - } -} -EOF -fi - -# nginx service start when boot -supervisor_config='/etc/supervisor/conf.d/yardstick.conf' - -if [[ ! -e "${supervisor_config}" ]];then - cat << EOF > "${supervisor_config}" -[supervisord] -nodaemon = true - -[program:yardstick_nginx] -user = root -command = service nginx restart -autorestart = true - -[program:yardstick_uwsgi] -user = root -directory = /etc/yardstick -command = uwsgi -i yardstick.ini -autorestart = true -EOF -fi - -# create api log directory -mkdir -p /var/log/yardstick - -# create yardstick.sock for communicating -touch /var/run/yardstick.sock diff --git a/gui/Gruntfile.js b/gui/Gruntfile.js new file mode 100644 index 000000000..171d65add --- /dev/null +++ b/gui/Gruntfile.js @@ -0,0 +1,492 @@ +// Generated on 2017-05-31 using generator-angular 0.15.1 +'use strict'; + +// # Globbing +// for performance reasons we're only matching one level down: +// 'test/spec/{,*/}*.js' +// use this if you want to recursively match all subfolders: +// 'test/spec/**/*.js' + +module.exports = function(grunt) { + + // Time how long tasks take. Can help when optimizing build times + require('time-grunt')(grunt); + + // Automatically load required Grunt tasks + require('jit-grunt')(grunt, { + useminPrepare: 'grunt-usemin', + ngtemplates: 'grunt-angular-templates', + cdnify: 'grunt-google-cdn' + }); + + // Configurable paths for the application + var appConfig = { + app: require('./bower.json').appPath || 'app', + dist: 'dist' + }; + + // Define the configuration for all the tasks + grunt.initConfig({ + + // Project settings + yeoman: appConfig, + + // Watches files for changes and runs tasks based on the changed files + watch: { + bower: { + files: ['bower.json'], + tasks: ['wiredep'] + }, + js: { + files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], + tasks: ['newer:jshint:all', 'newer:jscs:all'], + options: { + livereload: '<%= connect.options.livereload %>' + } + }, + jsTest: { + files: ['test/spec/{,*/}*.js'], + tasks: ['newer:jshint:test', 'newer:jscs:test', 'karma'] + }, + styles: { + files: ['<%= yeoman.app %>/styles/{,*/}*.css'], + tasks: ['newer:copy:styles', 'postcss'] + }, + gruntfile: { + files: ['Gruntfile.js'] + }, + livereload: { + options: { + livereload: '<%= connect.options.livereload %>' + }, + files: [ + '<%= yeoman.app %>/{,*/}*.html', + '.tmp/styles/{,*/}*.css', + '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' + ] + } + }, + + // The actual grunt server settings + connect: { + options: { + port: 9099, + // Change this to '0.0.0.0' to access the server from outside. + hostname: 'localhost', + livereload: 35745 + }, + livereload: { + options: { + open: true, + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect().use( + '/bower_components', + connect.static('./bower_components') + ), + connect().use( + '/app/styles', + connect.static('./app/styles') + ), + connect.static(appConfig.app) + ]; + } + } + }, + test: { + options: { + port: 9001, + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect.static('test'), + connect().use( + '/bower_components', + connect.static('./bower_components') + ), + connect.static(appConfig.app) + ]; + } + } + }, + dist: { + options: { + open: true, + base: '<%= yeoman.dist %>' + } + } + }, + + // Make sure there are no obvious mistakes + jshint: { + options: { + jshintrc: '.jshintrc', + reporter: require('jshint-stylish') + }, + all: { + src: [ + 'Gruntfile.js', + '<%= yeoman.app %>/scripts/{,*/}*.js' + ] + }, + test: { + options: { + jshintrc: 'test/.jshintrc' + }, + src: ['test/spec/{,*/}*.js'] + } + }, + + // Make sure code styles are up to par + jscs: { + options: { + config: '.jscsrc', + verbose: true + }, + all: { + src: [ + 'Gruntfile.js', + '<%= yeoman.app %>/scripts/{,*/}*.js' + ] + }, + test: { + src: ['test/spec/{,*/}*.js'] + } + }, + + // Empties folders to start fresh + clean: { + dist: { + files: [{ + dot: true, + src: [ + '.tmp', + '<%= yeoman.dist %>/{,*/}*', + '!<%= yeoman.dist %>/.git{,*/}*' + ] + }] + }, + server: '.tmp' + }, + + // Add vendor prefixed styles + postcss: { + options: { + processors: [ + require('autoprefixer-core')({ browsers: ['last 1 version'] }) + ] + }, + server: { + options: { + map: true + }, + files: [{ + expand: true, + cwd: '.tmp/styles/', + src: '{,*/}*.css', + dest: '.tmp/styles/' + }] + }, + dist: { + files: [{ + expand: true, + cwd: '.tmp/styles/', + src: '{,*/}*.css', + dest: '.tmp/styles/' + }] + } + }, + + // Automatically inject Bower components into the app + wiredep: { + app: { + src: ['<%= yeoman.app %>/index.html'], + ignorePath: /\.\.\// + }, + test: { + devDependencies: true, + src: '<%= karma.unit.configFile %>', + ignorePath: /\.\.\//, + fileTypes: { + js: { + block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi, + detect: { + js: /'(.*\.js)'/gi + }, + replace: { + js: '\'{{filePath}}\',' + } + } + } + } + }, + + // Renames files for browser caching purposes + filerev: { + dist: { + src: [ + '<%= yeoman.dist %>/scripts/{,*/}*.js', + '<%= yeoman.dist %>/styles/{,*/}*.css', + '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', + '<%= yeoman.dist %>/styles/fonts/*' + ] + } + }, + + // Reads HTML for usemin blocks to enable smart builds that automatically + // concat, minify and revision files. Creates configurations in memory so + // additional tasks can operate on them + useminPrepare: { + html: '<%= yeoman.app %>/index.html', + options: { + dest: '<%= yeoman.dist %>', + flow: { + html: { + steps: { + js: ['concat', 'uglifyjs'], + css: ['cssmin'] + }, + post: {} + } + } + } + }, + + // Performs rewrites based on filerev and the useminPrepare configuration + usemin: { + html: ['<%= yeoman.dist %>/{,*/}*.html'], + css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], + js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'], + options: { + assetsDirs: [ + '<%= yeoman.dist %>', + '<%= yeoman.dist %>/images', + '<%= yeoman.dist %>/styles' + ], + patterns: { + js: [ + [/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images'] + ] + } + } + }, + + // The following *-min tasks will produce minified files in the dist folder + // By default, your `index.html`'s will take care of + // minification. These next options are pre-configured if you do not wish + // to use the Usemin blocks. + // cssmin: { + // dist: { + // files: { + // '<%= yeoman.dist %>/styles/main.css': [ + // '.tmp/styles/{,*/}*.css' + // ] + // } + // } + // }, + // uglify: { + // dist: { + // files: { + // '<%= yeoman.dist %>/scripts/scripts.js': [ + // '<%= yeoman.dist %>/scripts/scripts.js' + // ] + // } + // } + // }, + // concat: { + // dist: {} + // }, + + imagemin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '{,*/}*.{png,jpg,jpeg,gif}', + dest: '<%= yeoman.dist %>/images' + }] + } + }, + + svgmin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '{,*/}*.svg', + dest: '<%= yeoman.dist %>/images' + }] + } + }, + + htmlmin: { + dist: { + options: { + collapseWhitespace: true, + conservativeCollapse: true, + collapseBooleanAttributes: true, + removeCommentsFromCDATA: true + }, + files: [{ + expand: true, + cwd: '<%= yeoman.dist %>', + src: ['*.html'], + dest: '<%= yeoman.dist %>' + }] + } + }, + + ngtemplates: { + dist: { + options: { + module: 'yardStickGui2App', + htmlmin: '<%= htmlmin.dist.options %>', + usemin: 'scripts/scripts.js' + }, + cwd: '<%= yeoman.app %>', + src: 'views/{,*/}*.html', + dest: '.tmp/templateCache.js' + } + }, + + // ng-annotate tries to make the code safe for minification automatically + // by using the Angular long form for dependency injection. + ngAnnotate: { + dist: { + files: [{ + expand: true, + cwd: '.tmp/concat/scripts', + src: '*.js', + dest: '.tmp/concat/scripts' + }] + } + }, + + // Replace Google CDN references + cdnify: { + dist: { + html: ['<%= yeoman.dist %>/*.html'] + } + }, + + // Copies remaining files to places other tasks can use + copy: { + dist: { + files: [{ + expand: true, + dot: true, + cwd: '<%= yeoman.app %>', + dest: '<%= yeoman.dist %>', + src: [ + '*.{ico,png,txt}', + '*.html', + 'images/{,*/}*.{webp}', + 'styles/fonts/{,*/}*.*' + ] + }, { + expand: true, + cwd: '.tmp/images', + dest: '<%= yeoman.dist %>/images', + src: ['generated/*'] + }, { + expand: true, + cwd: 'bower_components/bootstrap/dist', + src: 'fonts/*', + dest: '<%= yeoman.dist %>' + }, + { + expand: true, + cwd: 'bower_components/components-font-awesome', + src: 'fonts/*', + dest: '<%=yeoman.dist%>' + } + ] + }, + styles: { + expand: true, + cwd: '<%= yeoman.app %>/styles', + dest: '.tmp/styles/', + src: '{,*/}*.css' + } + }, + + // Run some tasks in parallel to speed up the build process + concurrent: { + server: [ + 'copy:styles' + ], + test: [ + 'copy:styles' + ], + dist: [ + 'copy:styles', + 'imagemin', + 'svgmin' + ] + }, + + // Test settings + karma: { + unit: { + configFile: 'test/karma.conf.js', + singleRun: true + } + } + }); + + + grunt.registerTask('serve', 'Compile then start a connect web server', function(target) { + if (target === 'dist') { + return grunt.task.run(['build', 'connect:dist:keepalive']); + } + + grunt.task.run([ + 'clean:server', + 'wiredep', + 'concurrent:server', + 'postcss:server', + 'connect:livereload', + 'watch' + ]); + }); + + grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function(target) { + grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); + grunt.task.run(['serve:' + target]); + }); + + grunt.registerTask('test', [ + 'clean:server', + 'wiredep', + 'concurrent:test', + 'postcss', + 'connect:test', + 'karma' + ]); + + grunt.registerTask('build', [ + 'clean:dist', + 'wiredep', + 'useminPrepare', + 'concurrent:dist', + 'postcss', + 'ngtemplates', + 'concat', + 'ngAnnotate', + 'copy:dist', + // 'cdnify', + 'cssmin', + 'uglify', + 'filerev', + 'usemin', + 'htmlmin' + ]); + + grunt.registerTask('default', [ + 'newer:jshint', + 'newer:jscs', + 'test', + 'build' + ]); +}; diff --git a/gui/app/404.html b/gui/app/404.html new file mode 100644 index 000000000..899828a3c --- /dev/null +++ b/gui/app/404.html @@ -0,0 +1,152 @@ + + + + + Page Not Found :( + + + +
+

Not found :(

+

Sorry, but the page you were trying to view does not exist.

+

It looks like this was the result of either:

+
    +
  • a mistyped address
  • +
  • an out-of-date link
  • +
+ + +
+ + diff --git a/gui/app/favicon.ico b/gui/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6527905307f19ba00762f9241f7eb535fa84a2f9 GIT binary patch literal 4286 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 JVs!ZUKLH;yW7z-z literal 0 HcmV?d00001 diff --git a/gui/app/images/close.png b/gui/app/images/close.png new file mode 100644 index 0000000000000000000000000000000000000000..0d2c142526bdd8bd9333a613b89f849f11762ddf GIT binary patch literal 1211 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 B9wGn$ literal 0 HcmV?d00001 diff --git a/gui/app/images/loading2.gif b/gui/app/images/loading2.gif new file mode 100644 index 0000000000000000000000000000000000000000..9d1534468d63212cdbae327b4522a84893543466 GIT binary patch literal 25405 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 XU1R-|`WJ^_00000NkvXXu0mjffVMu> literal 0 HcmV?d00001 diff --git a/gui/app/images/statusyes.png b/gui/app/images/statusyes.png new file mode 100644 index 0000000000000000000000000000000000000000..d88a99ee8dd09210a8e00091f96151490b223613 GIT binary patch literal 2577 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 literal 0 HcmV?d00001 diff --git a/gui/app/images/url.json b/gui/app/images/url.json new file mode 100644 index 000000000..f16c4e0ac --- /dev/null +++ b/gui/app/images/url.json @@ -0,0 +1 @@ +{"url": "192.168.23.2:1948"} \ No newline at end of file diff --git a/gui/app/images/yeoman.png b/gui/app/images/yeoman.png new file mode 100644 index 0000000000000000000000000000000000000000..92497addf96c5c009ac24007f55eb989f3bf74c6 GIT binary patch literal 13501 zcmV;uG(yXXP)X+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@+Z=rn|HO+DhmlDBoIatm|!fhjg2uGWbhq^bHc!#?=WDTFl)@& zoXIx9K5)SQ!vZ-`+^PLI8p92OQ1A>zw`vN^+*z2hG=X5&#Fe>&rmC1NA>41U*ojHR< zgOq#|l>0v1#9Yb4KhU2>MZ(pU7gz>5?onTP-Bldj7cMnQbGdKxy(WEeFN*b^@*Q2=Rr-b!~{4F zVKSc5dO*Ow%(@ZlaF!{Hl#aKT-5Tr+q|2-P@lI`aU5b(fP>VlQ*%fH?RyY#TJ7OIe zipa&CK)Xk$QmIwQ9#p48i;iy0ovEYJs%?u#>Cm)A=D^CE4~?w6`8Uy^wJxL?n_{W( zroLCMT>t`scjvk4?Lwzxh3qGpo`MTtV%+Vt$BDCFCa%-I@+jh?0%Q!=Xsu4O23Fqm zEimEsqOy=?#uAb=H3=0-QO7$QFGXkGXy_>?Gfb3e1Hne4f37N1(mWaszMnPlOz}@_PB&u?~vqutC(U`D1(sF{{XPz1Jo&4l@VtSs7kX7QbG$%xPBPg ziFKLC>f389OYTf2xc)-D>4w;St(xBUmbR;{^%wV?|72;^nGe3kx@(f9%2I>mtRt5Y zG2m<_=5y3VKDbgar5hzWV06(ZgZyyJ&sQ%e&0srd&t;A~Igq;l;C;`?eeL3|r@uKS z-PbiRanZwZ){U&Z>6W6hqHjlgV`+iJX#K5P|F-23Pq6BW@Z6I(nTfz`ZnU(7NTSWq z50yk>o2ulP^1Bxwy_CK5dEYyMnTKTs>&lv6UYqLN@#AFkTQ83NmuDB+>+h_LBobSE z9=$AWX_mx#^~jc&V&0nB_XWqzI6N5C7m5xchNwk*BR>m&?CS5&ojLC8cQ&=%GGNFQ z(20|LFH+87c;qIlN@{Gc#KA{D*wLkBZ3}c%i)TRsS`Abi!XQ%52U<32 zDH#K%OtSqr<;+M4Xx#&4Mn+v_RY-@HmMOLm_Odb=hma9CG)ShjX=&eoOJ@Z#yXRp~ z)sWp=4JkX?kr87~ef_%ZjMI%zb9IP--kPSXqZY{1iIfV#I%9V465AWLi)yD&6*Dc- zflS6qB|OZvy?zSLYhu9PBVeQU`wX1+DK8(K{PIVFgZmNfIw0 zixwj#)-8_U=)#bh_|G)GaN(iZL0FnHhc$mF9Sj7*Eb#gPqLR$440Yr{t^g__by1t5h-kF>`YqMJeceL5Q<kN}_*h6Jf+eoV zhBy*g9zYkM^lUjw!jFI*$D+t$;JF8fp*JGT_9#6>J?gNhKJRl!9`0D1C7e%N67R=6 z)^wj;I>GYT@hOrRsLI$laker@1!FXw`Oxl-_R~vV((bSC)B>K2p4i!->AO~nC*9L{ z5@_H?Xb)*BH;%}y9Ny6xtxmVYtEuY51ZNj&6RHtym|BDaEX6WOe)iv z@)^E(+hz%c7QiO*h#n|{IDp(>%<7dZvE5Ti~$RZAVoNM0F%=42|FxtNKYS_m5;h$ z4y4TNKF7O#959WW1eBc~&+ih6#2BEy0u1WGsr63~<{IRxIs^+3BgUNEuG*$t9_x$E z3zdWpvd7Lyr#c$-R9BPXEguKrNlARiYVoGJZQUCHz0B{R?yv4x2c4@ zMf{2BV?#gOx+P*)mucEUyU`ATETB0xRTfZ_$8MlIHk`K1Lso;qLLze^V{g-Zh7mH) z>X!y|sGO{Uu+lEc!J+dBQp%9&0bN@Yj$2kUw6Bp)8-N9-X+}``dW6>^{3ya)Pq~X8 z6<}9K6x_!F(RH_t!-?jfL#3f(#k6Ihr`NVqG0jRRMf3Q?C@Qn{o*g(Lc)!1GXa&F;YLk@++MS{BLaLJ;4r8a2#<@dyyYuc zY%cQ!i{@ouBp5nQcCth_Llb6rI%Bb`Mc#fn^5)I|8SRhWGI`2GY!lWEgn9bzY> z)E!1WOs;JY4&6xHR;m zXn%}@awQf(^lFLiuYn1EE!o)xYdk1c$z-v^<6u|W66@=e<}KT# zc>EM8D=!wmPZtnRUU=kha>#;tGH&t&NkJ-nz`1Kjvut>Mg`9Bq=^#AzL`f42jYwb9 zHmRL9S;FPz%EZtkyF_Bmc9GYs4aC!JM!4k2>N6f}S&Tzl7&_F0e2l&X?pWZdL3{wt z)td+w23#T)1Uoc|y>w}IhG8Tdpd-jJXf=0Ez`nMiyi!T<$kxq7)Uvy_@JWKiyZfbU zL!+(Xf!w6yXG&m9wb*7xJecv76DLAF;k+4m(&>yeuHU4jqjG$$6jxP3Wu>9|yplGt zKT7SA_Lf~TZO%-o0Nz@xIf)q)C6!1@u((K_jwj4M+0n2~^JL8Au_1rT0u=e1D z8@_?{qecRyC9>-IS6~>JI5~y2ZDE0jkJTRgE595(Z;mAUqgbn`?yw=nW7u@sbnG@D zL?f;Z$99;)PV*<1`e4q_Aw6e6jh=_K=YC`tRC)@^91IT6-0HE+i?RKlcTVm7IF2Hx zhs9e_tQCR6ej_dIosouA;Dm;WU%m~`eO5J*C{6bqzdWobj_usppDvwt81}6^xxo<) zEVM{8Ax+Jl($?N5{R43^v7q~*!eZ?mVy#{$6=P~3IZ%I4VcqR*q7_$3+f#2y5~|Md z7!ocDNKH+tjIAz{a2Un~t+kLY>!_(!EJ{7 zIvkn9^nyV_nS)+aZJHY@<{Vx0d}?;@^GVAyJ?crH73)sVFI@tjto?>~F`f{9ZHur_$Xn|M4aOJCb4&po|DRzA5JC(zm5ASp)R zDotHeOE*cRWrqlD`6yf)9D$O+U1TNPk)N9*C!czhe0;$S@!>gW&GhkFNx&cIjwGAP zYD<4AvK*xr!fdXGufUa(-$8vdSr%dJlAbeU6-n>Qv-A?B(v+0vnQm@%soEMKDMzvQ~rmic4>cp@=ZAy zl}aat#elTnk%Wsv@FW$}7-x2{+>D*QVok!M8LT}9=mv9qupG>GtTh?w-##E)kb34R zhs#A@7;jIl@${rj?RB3|zqI_U#~Rh+bsR|zR?Mm}#%IGQSOfKTv7_2J!6{uM4uxcn zx?76|?2R((nF{fI28WS}vY-*@E3@vK*1GoVn}2bNe|q=PY0Y7bJ35y^jNg9Ip;x~6 z^-_?V-&BPoU(Uq(LzP%zYB>ATVd9gLRN`ySMVeQ{L4J9~?*Cnz*H* zaL*I77J(W*B8J}Ld9>gpn5sW7oR^)6nKLSa6O;Xk#Z{AohNi>djV1TU7_0%9zA&Ln zMRqjpkb7^xUAFJsA!%H{F>o@<{Cq%0E4+|7v?GZ~>cKO|2Bx5F#_7lKYHBirF)NWV z6)x-)Q0c=9|FdzL zxzcpfN<%C0@k?H?vGWgx!?Ja2k9-Ox&RezuHq{C_1L42mVZt*5Ju>Z}a*0QxPy7hC+KUy>( zr=cG#o=sP#=ghf88u)C)ot?-N>#Uma7)#G=!17)W5dsU9{#UCw7cs76JWclC>?{MT zYt6PkIsLS`^6jh6m5|SrzP^6(1p_#qNl0l)iNw=}+;RJ3^3bCV^08^9>hLs!vp1e@ ztIa2AoTW5FIK~hk0=>HBJ>)NKk#?l1e!(ZWi~rFu3SNc#4E9t@kiMriN>%!xKLwK< zo_}E0$xQJC{Wu*~*E>Mktq5(Bg0qf;H|y|2XWLjQG-*O&IjjXAC%Q7s zz!Xx?#PLeJJC2(**}cuRb?3^dpPMH|;h@AKcsw4BOJ7SXP7TV$F0GV`iX!>WHRs8k zxzpq~*FCKEN8A$yD{x>M#A$(=8a#hhTF*y;z{L|>YT$|`?REH}8wSe408iaDXIS>g zMA@}ML3TFzRmO2<@x@2Wru*H!kkbiwmTISu^PPoj8RvokAJ;tk z_*7HeN6{;u5|)WGr^vjI&y=H&I#?!8tj41N=s!@@z_ARaD!WYZ*^kNRF*q#%BVcN8 z-`OFrzP3qLzP>@4);CIjV?>F9e|#`yn^rS8v;unXp~qTNkFY;=m^g6RHINDKO3pk; z%?h~BL2{l(dUmX;$jJQ^j5$lu;S(_lf1!{2NvY=fp~u$K<7}_D+V*Df)Wx&Rh&*|L zx%~@y%xN{3-67UpwlG|GcYSEpqgUTMc1-E%IBCw{7{^l-%5E%EIOrr`9Z|@kK}inI1t=EL zXv&^W#HRx<+NYxD&6dBh=?C?kqobF8%^{Tmxq*Kw7N z%gZ7=tlewXnxC+W4Vcw8fbOSVhQZYjZQWL;EPfjP_Y!4T#1qMQu@4X9psK)uAq-Jf zu~@hi%B8HLrXy7@!}us0vtIl#DB|%{jP)BDF7ec5kLz;P7_)W*NasOLB9}gb+jKTI zLcaxL&SG>Jrm{SmoV>YG^O_a5$G~QV_s+Q2F<#LJ%me zqX{>49!DZ|I!TvZNDx>EM*tbIN!gtSB)XYSNynCrunWTyg!!yvF~QqelIZW3bUY%J zQw~y>KZdMFL<)3}x9mEzkO}VzZoiHKF22}HaMz)1Yy^J`#vD-C=DIOe8WUJiUPYg$ zff8VtuJbK`3LLET!895`g4fdjCSLtge6ZnH&e*^>szI?EipOQ=Ic`_wV-5-K!PC)j z^;l_sdxb<=R|9JrQsa}Nv4_fpxeJi*N7_(rg=*j`9%I`Yx&WwGzkB;(e~5%nC8lFi z{Muo*>fhcfe$c#vF^3((lA$_2UxJT!*yfkGT5wkh5r9rmPaJum!U1#O_u!iYr((cA z0+ZfETv2oW{7ck{ucd>CkO*vgAVfpyLBfYCE9H=rJ}b%SacXnw!TRJcDgqjq)V#Zp z13V>w*^DcSxP)t3YR$>X-cwL92gbms$P}zuOPqqci}Ri^>|;6`EF>tZtVqS5X%-vL zC2gYzbC`)a>0uh(ZW1KS|5zhuG`>~HRXe>*193-GCVKGe)n zl7T^WK~H}LgCV^JX@>A1kTBqmT_Ku@XPFkldRWuL(O7Ey)XJ*bPxj-w(_*GvD`mPq=QHJV*j zZ9ieek65{_|B~5$f8XV%`9?k3=c&d0(wIwhPB6UKrGF3pagd&i;p0i30#1>FKwcsh zpe43rfREbfGcSl>& za*Z@*9+x<-lIn7r7W(Vo%=!O{Jz4}034M!XEtg1iL!m4Kb7(p*26N7b&;D~91%rYb z1UbFtKuG%=p$<+0gLaz~oPJdx8yRyB&J#avmB_b^cC7@Ork@KiFlbRrtSEM~)#Xa| zGS9b2=J`!{YmGD_GjSIUnr4H~l9{P3s!2^<{Xh`cKeTcf?tx;lI`Jw&3%Y||^x}#& zNNs<% zQSJ28(lhf_>E~j9)`_^B0zZx2@uBdqg1ZZD9ULchL8S)x6_MzV1}JI4De+a9eYm6c z?qW(cM5)2ufB7Y@B1bTl@6&o@DxR~of?>?#d!auyTBQ+htp%Y`u(U&MVc~DX<-lEV z-N1tX+V~=M9jfFT7fD&&_a!j~C;z*^cD##CO9O_6(oywz(du$f6I!;U7I(BM%u})D z`i=Bwx+GotzcT(&WZnRW_jimx!0Si9uVx&;E*Bhj)B6Q%k(!GF(g{NJ!hIf&vrp%4 zq#aB+949#cW>(3s@WSn8WMMg1;>C?fHDYgqF~7w@!NHj1RbWpMn8fGTSo$&P!5BCz zo{=q?b~&gQuZJ|eA^vApq}uxI}A4%&Vgk}so@w3E;a0kn#~N161eU0YZ8yZKsXyyFf|@K zj~!53cUt3R0v^quX>7wR_Q`fooU@0Oc<2V@r6h^5Sx`*pTcvWC9aP^SD8XALO68%> zy5fu+IzX9=far(t8w0Z5j>)rnM4naOWVPiiyF^Y!{Uu;FZo$dXc9A@Yqq65rP6b;Y zlPPu=wzFT=n*7r>&n;W54AmOd*${P%c)`FWR>2y`gP7|L)j|lWeuWqNA3qp`!%*MS z%rtpM?~|YD9r%ubPf3$opB*JtC}ovkQlmoQPGCchNBJ?hLV|C%;jL{`GO*tILO%IN zTPwKBniLB|roiDG2-9De8_gPE$%biETn&l!U?bawZSwb|GIfGh>N`c=c?Ly3i^>Fc zBP(AjTG*fc!5of=0zt}79HSe{+6{ZcMRrO0o8}nrg`Vwrc5ULLLKb?$hEg9%qNA#&DN&izK~x?@XyL5-J}s zofZW@3_5vc9e(>U zY5a!n+hJJ^JM84AQ7j5K>Wo2~ia_-D2arbI`y}23K{;x% zR~DE^j?yuc&V!S zo#zv?ZTp>Fpu%sTM?wJ7fv@LefxapQAMb#uMF}5w!MR+@ z#x*X>y2dHE-x$O8-QGHV*>B#vL@?y5cu(LqYz&Ko=nnQgn6)O=3i@i`gLek5TNzS< z>sIBZh9tWTIq&NU`Qj;YJWxX^iUr86Mh1Sc_9M>*&Yc;;hDcee=(&ccy$2A3Sg zoQTu6Bske^nJpR1x&$?!fFtY|RhDf!`%9u62tc(#vVvUl8(e7mN0}`*^lJ9SmuqF7 zW2|CWXa-*Bur&i$%_Nk{?Qu>WrTV|)7zH^=>bq(3WOwxpFBHez41BE#x0_M2_ z_NlqoGXD+!ogI4W%B^*DMX2MC}FB(#E0ipI*@9yZ^@t zpB0@So@d!V`MPEA)cWl6@rk0zI^G2~bYm>kW|id`z!RW9ZZcE1qQWLHhQPM>%le{P zANF$~F~{wiD@9bf4U4VFm!RCP1HCUm{B#Hpt|j1Q1?a75>WL`sA$Y+IT%s+deGNEf z2Hs2Cj5yC~F#n^mtoGLMWw)(XreUjQ{~1%^Arx&^6|yRf96ewd!+srz%wfM=DKgGO zfNSlCHK1-YM;i=^tc(h{4_I8_AR^smscF(qU zZRg$3$Qnq~a$~?;1}1M&f`tOOA!ICq57_T2EZdC_=Pz|t-idVK$!b4338JH(w9X!n z?-JpP_MBSv*FtDh`8LmnaG((U_5sg?k#_KokHWDr9_limoTeGda(qff z6P9@_!+e^=Za#tvScmz<%M*OpjAfc@LL(^ExB;pz6ygsK^&sBHLmw*$_Cd!Bh9*c^lta2;@MPMBrC?9^qNjdHE<``}nN zljBT;6L<>R90&xau?~O)An3@u0&W8wapt;hz*&bf&Q_Df1%K+m+X+YfQQ&+;rDlmo zLJbEJgWtvZ6v}ryj#svLAB~{$T_5>>gkv9iobl{Jn5H_xowN)qy1}nNKKrBUz<&j4 zxGJQ;9V6+x!YM<1q$A7!$tmaJ8q$E1hgJLd*K@3mchLSw*)tEV({m~-3ff0?VMfwyPq#0 z?JEdxhkq~p2jKpS@~l?{cM~^&IXZZxM?W=Ak*Mq!-pnAi3FSVNQ0ys8!r<2dd=!n=I&^Y9?3}1>O zRKVe4newT+=D|_o#=zkxRAF7ZU7Q{ zF#I&!9Zng=h#Y(_(KWQ!gmNp~JcKWUBjG=jr>;wjN|u4UnPuQ#%s#;L91jv*)j%CU1^V_Qw0#xQeh>Ey9M?1&0pA9G z9+WP^A8g%%G9<=seP>w!^T@n!pw4*U-HQ4@!ur9#h)4~;Cqm9w&TP} zOu|?vetR}LM5VY)4+0ORhW(-iO6iZn(E{y)L##lwgHuSD%$xA@@yCik@sWNh`vw0wIIcO5I%Rfa z&Ta`g$k+*rTp5s^0&%9<%*JOT{za&vF6X;1?x0u}S*q+rgb9Zuem3eBBW%IZ&g46o z^N|KmUF4+CyW|%GCw2Q9==84;e+u!Ph~r%-Ydna2B7CYV!i8{D8O6Z=8Q`ixnEf~m zajrFMv2nc`c+Lc#ClDumGaipOBcC7eR-0R-%|(0>;+vpq-a{Ir9EGg3ISQU1X_ z)j_*Yz|C~((4+cv+o9yBzM;&G%mn_E99&ABIrvpMr015uf#q|oMz_x?jum*WV?F_l zn&IFa93#sh?cb4233lt|;#d7aKG$JISC)6mK7cgBZi3>@`fPUo-vAn)fh=5);AifY z1mMLnqYr<^WZSSR2Ff0ca_j^d#yioRB|`xDY}bQX#MwwmA`rJ)K;c({AhR&*sfO^U z46Ig2DOE@t9KkJ%KVRTG1o${Gwx^^Mc$*P!hodOZffThMNW%b8-;6xvnNQrzXa9+x zeN^-Tz9xh_;0SIRwKidV_&anq$Fd!H-N?k1x83^V;#c?-7SIOFnF~4&fR67Vjd@C) zIQa*Jm)l_yn1tCAFmG@9x%G&5IPTp1;o;n}!}Az_ul#&4?d}VWS<%U^5-)v#W**CX;EVD1|<>F<3hR2aK1okDL74wbJ#j&@%A&okC r_B5{HWj~64M1hYe@DT+*>=gL_d-r`<6cl&$00000NkvXXu0mjfZzoN6 literal 0 HcmV?d00001 diff --git a/gui/app/index.html b/gui/app/index.html new file mode 100644 index 000000000..5592656cc --- /dev/null +++ b/gui/app/index.html @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/app/robots.txt b/gui/app/robots.txt new file mode 100644 index 000000000..4d521f952 --- /dev/null +++ b/gui/app/robots.txt @@ -0,0 +1,4 @@ +# robotstxt.org + +User-agent: * +Disallow: diff --git a/gui/app/scripts/app.js b/gui/app/scripts/app.js new file mode 100644 index 000000000..ecb642c95 --- /dev/null +++ b/gui/app/scripts/app.js @@ -0,0 +1,30 @@ +'use strict'; + +/** + * @ngdoc overview + * @name yardStickGui2App + * @description + * # yardStickGui2App + * + * Main module of the application. + */ +angular + .module('yardStickGui2App', [ + 'ui.router', + 'ngAnimate', + 'ngSanitize', + 'mgcrea.ngStrap', + 'ncy-angular-breadcrumb', + 'mgo-angular-wizard', + 'ngResource', + 'ngFileUpload', + 'toaster', + 'ngDialog', + 'angularUtils.directives.dirPagination', + 'ngStorage', + 'vAccordion', + 'darthwade.dwLoading', + 'ui.bootstrap' + + + ]); diff --git a/gui/app/scripts/controllers/container.controller.js b/gui/app/scripts/controllers/container.controller.js new file mode 100644 index 000000000..6c2ccd8ff --- /dev/null +++ b/gui/app/scripts/controllers/container.controller.js @@ -0,0 +1,182 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ContainerController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + $scope.showloading = false; + + $scope.displayContainerInfo = []; + $scope.containerList = [{ value: 'create_influxdb', name: "InfluxDB" }, { value: 'create_grafana', name: "Grafana" }] + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.createContainer = createContainer; + $scope.openChooseContainnerDialog = openChooseContainnerDialog; + + + getItemIdDetail(); + + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + $scope.containerId = response.result.environment.container_id; + if ($scope.containerId != null) { + + var keysArray = Object.keys($scope.containerId); + for (var k in $scope.containerId) { + getConDetail($scope.containerId[k]); + } + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + response.result.container['id'] = id; + $scope.displayContainerInfo.push(response.result.container); + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + function createContainer() { + + $scope.showloading = true; + mainFactory.runAcontainer().post({ + 'action': $scope.selectContainer.value, + 'args': { + 'environment_id': $scope.uuid, + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create container success', + body: 'you can go next step', + timeout: 3000 + }); + setTimeout(function() { + getItemIdDetail(); + }, 10000); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function openChooseContainnerDialog() { + ngDialog.open({ + template: 'views/modal/chooseContainer.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + } + + function chooseResult(name) { + $scope.selectContainer = name; + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteContainer = function deleteContainer() { + mainFactory.deleteContainer().delete({ 'containerId': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete container success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getItemIdDetail(); + + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + } + ]); diff --git a/gui/app/scripts/controllers/content.controller.js b/gui/app/scripts/controllers/content.controller.js new file mode 100644 index 000000000..d2bc19eea --- /dev/null +++ b/gui/app/scripts/controllers/content.controller.js @@ -0,0 +1,136 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ContentController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$localStorage', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $localStorage) { + + + + + init(); + $scope.showEnvironment = false; + $scope.counldGoDetail = false; + $scope.activeStatus = 0; + + $scope.$watch(function() { + return location.hash + }, function(newvalue, oldvalue) { + if (location.hash.indexOf('project') > -1) { + $scope.projectShow = true; + $scope.taskShow = false; + $scope.reportShow = false; + } else if (location.hash.indexOf('task') > -1) { + $scope.taskShow = true; + $scope.projectShow = true; + } else if (location.hash.indexOf('report') > -1) { + $scope.reportShow = true; + $scope.taskShow = true; + $scope.projectShow = true; + } + + }) + + + function init() { + + + $scope.showEnvironments = showEnvironments; + $scope.showSteps = $location.path().indexOf('project'); + $scope.test = test; + $scope.gotoUploadPage = gotoUploadPage; + $scope.gotoOpenrcPage = gotoOpenrcPage; + $scope.gotoPodPage = gotoPodPage; + $scope.gotoContainerPage = gotoContainerPage; + $scope.gotoTestcase = gotoTestcase; + $scope.gotoEnviron = gotoEnviron; + $scope.gotoSuite = gotoSuite; + $scope.gotoProject = gotoProject; + $scope.gotoTask = gotoTask; + $scope.gotoReport = gotoReport; + $scope.stepsStatus = $localStorage.stepsStatus; + $scope.goBack = goBack; + + + } + + + + function showEnvironments() { + $scope.showEnvironment = true; + } + + function test() { + alert('test'); + } + + function gotoOpenrcPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.environmentDetail', { uuid: $scope.uuid }) + } + + function gotoUploadPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.uploadImage', { uuid: $scope.uuid }); + } + + function gotoPodPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.podUpload', { uuid: $scope.uuid }); + } + + function gotoContainerPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.container', { uuid: $scope.uuid }); + } + + function gotoTestcase() { + $state.go('app2.testcase'); + } + + function gotoEnviron() { + if ($location.path().indexOf('env') > -1 || $location.path().indexOf('environment') > -1) { + $scope.counldGoDetail = true; + } + $state.go('app2.environment'); + } + + function gotoSuite() { + $state.go('app2.testsuite'); + } + + function gotoProject() { + $state.go('app2.projectList'); + } + + function gotoTask() { + $state.go('app2.tasklist'); + } + + function gotoReport() { + $state.go('app2.report'); + } + + function goBack() { + if ($location.path().indexOf('main/environment')) { + return; + } else if ($location.path().indexOf('main/envDetail/') || $location.path().indexOf('main/imageDetail/') || + $location.path().indexOf('main/podupload/') || $location.path().indexOf('main/container/')) { + $state.go('app2.environment'); + return; + } else { + window.history.back(); + } + + } + + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/controllers/detail.controller.js b/gui/app/scripts/controllers/detail.controller.js new file mode 100644 index 000000000..3e2eaa100 --- /dev/null +++ b/gui/app/scripts/controllers/detail.controller.js @@ -0,0 +1,384 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('DetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, ngDialog) { + + + + + init(); + $scope.showEnvironment = false; + $scope.envInfo = []; + + function init() { + $scope.showEnvironments = showEnvironments; + // $scope.openrcID = $stateParams.uuid; + $scope.deleteEnvItem = deleteEnvItem; + $scope.addInfo = addInfo; + $scope.submitOpenRcFile = submitOpenRcFile; + $scope.uploadFiles = uploadFiles; + $scope.addEnvironment = addEnvironment; + + $scope.uuid = $stateParams.uuid; + $scope.openrcID = $stateParams.opercId; + $scope.imageID = $stateParams.imageId; + $scope.podID = $stateParams.podId; + $scope.containerId = $stateParams.containerId; + $scope.ifNew = $stateParams.ifNew; + + + getItemIdDetail(); + } + + + + function showEnvironments() { + $scope.showEnvironment = true; + } + + + function deleteEnvItem(index) { + $scope.envInfo.splice(index, 1); + } + + function addInfo() { + var tempKey = null; + var tempValue = null; + var temp = { + name: tempKey, + value: tempValue + } + $scope.envInfo.push(temp); + + } + + function submitOpenRcFile() { + $scope.showloading = true; + + var postData = {}; + postData['action'] = 'update_openrc'; + rebuildEnvInfo(); + postData['args'] = {}; + postData['args']['openrc'] = $scope.postEnvInfo; + postData['args']['environment_id'] = $scope.uuid; + + + mainFactory.postEnvironmentVariable().post(postData).$promise.then(function(response) { + $scope.showloading = false; + + if (response.status == 1) { + + $scope.openrcInfo = response.result; + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.showEnvrionment = true; + getItemIdDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'faile', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //reconstruc EnvInfo + function rebuildEnvInfo() { + $scope.postEnvInfo = {}; + for (var i = 0; i < $scope.envInfo.length; i++) { + $scope.postEnvInfo[$scope.envInfo[i].name] = $scope.envInfo[i].value; + } + + } + + //buildtoEnvInfo + function buildToEnvInfo(object) { + var tempKeyArray = Object.keys(object); + + for (var i = 0; i < tempKeyArray.length; i++) { + var tempkey = tempKeyArray[i]; + var tempValue = object[tempKeyArray[i]]; + var temp = { + name: tempkey, + value: tempValue + }; + $scope.envInfo.push(temp); + } + } + + function uploadFiles($file, $invalidFiles) { + $scope.openrcInfo = {}; + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/openrcs', + data: { file: $file, 'environment_id': $scope.uuid, 'action': 'upload_openrc' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.openrcInfo = response.data.result; + getItemIdDetail(); + + } else { + toaster.pop({ + type: 'error', + title: 'faile', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + + function addEnvironment() { + mainFactory.addEnvName().post({ + 'action': 'create_environment', + args: { + 'name': $scope.baseElementInfo.name + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create name success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.uuid = response.result.uuid; + var path = $location.path(); + path = path + $scope.uuid; + $location.url(path); + getItemIdDetail(); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getItemIdDetail() { + + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + if ($scope.ifNew != 'true') { + $scope.baseElementInfo = response.result.environment; + if ($scope.baseElementInfo.openrc_id != null) { + getOpenrcDetail($scope.baseElementInfo.openrc_id); + } + } + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail() { + mainFactory.ImageDetail().get({ + 'image_id': $scope.baseElementInfo.image_id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail() { + mainFactory.podDeatil().get({ + 'podId': $scope.baseElementInfo.pod_id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getPodDetail(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.uploadImage', { uuid: $scope.uuid }); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteOpenRc = function deleteOpenRc() { + mainFactory.deleteOpenrc().delete({ 'openrc': $scope.baseElementInfo.openrc_id }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete openrc success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getItemIdDetail(); + $scope.openrcInfo = null; + $scope.envInfo = []; + $scope.displayOpenrcFile = null; + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + } + + + ]); diff --git a/gui/app/scripts/controllers/image.controller.js b/gui/app/scripts/controllers/image.controller.js new file mode 100644 index 000000000..53acff405 --- /dev/null +++ b/gui/app/scripts/controllers/image.controller.js @@ -0,0 +1,166 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ImageController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $interval) { + + + init(); + $scope.showloading = false; + $scope.ifshowStatus = 0; + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.uploadImage = uploadImage; + getItemIdDetail(); + getImageListSimple(); + } + + function getItemIdDetail() { + mainFactory.ItemDetail().get({ + 'envId': $stateParams.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getImageListSimple() { + + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + // $scope.imageStatus = response.result.status; + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + + function getImageList() { + if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + $scope.imageStatus = response.result.status; + + if ($scope.imageStatus == 0) { + $scope.intervalImgae = $interval(function() { + getImageList(); + }, 5000); + } else if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + function uploadImage() { + $scope.imageStatus = 0; + $interval.cancel($scope.intervalImgae); + $scope.ifshowStatus = 1; + $scope.showloading = true; + mainFactory.uploadImage().post({ + 'action': 'load_image', + 'args': { + 'environment_id': $scope.uuid + + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + setTimeout(function() { + getImageList(); + }, 10000); + + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.podUpload', { uuid: $scope.uuid }); + } + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/main.js b/gui/app/scripts/controllers/main.js new file mode 100644 index 000000000..e3e880e62 --- /dev/null +++ b/gui/app/scripts/controllers/main.js @@ -0,0 +1,725 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('MainCtrl', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$localStorage', '$loading', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $localStorage, $loading, $interval) { + + + init(); + $scope.project = 0; + $scope.showloading = false; + $scope.showEnvrionment = false; + $scope.loadingOPENrc = false; + $scope.uuidEnv = null; + $scope.showPod = null; + $scope.showImage = null; + $scope.showContainer = null; + $scope.showNextOpenRc = null; + $scope.showNextPod = null; + $scope.displayContainerInfo = []; + $scope.containerList = [{ value: 'create_influxdb', name: "InfluxDB" }, { value: 'create_grafana', name: "Grafana" }] + $scope.items = [ + 'The first choice!', + 'And another choice for you.', + 'but wait! A third!' + ]; + $scope.$on('$destroy', function() { + $interval.cancel($scope.intervalImgae) + }); + $scope.showImageStatus = 0; + + + + + + + function init() { + + + $scope.gotoProject = gotoProject; + $scope.gotoEnvironment = gotoEnvironment; + $scope.gotoTask = gotoTask; + $scope.gotoExcute = gotoExcute; + $scope.gotoReport = gotoReport; + $scope.deleteEnvItem = deleteEnvItem; + $scope.addInfo = addInfo; + $scope.submitOpenRcFile = submitOpenRcFile; + $scope.uploadFilesPod = uploadFilesPod; + $scope.uploadFiles = uploadFiles; + $scope.showEnvriomentStatus = showEnvriomentStatus; + $scope.openEnvironmentDialog = openEnvironmentDialog; + $scope.getEnvironmentList = getEnvironmentList; + $scope.gotoDetail = gotoDetail; + $scope.addEnvironment = addEnvironment; + $scope.createContainer = createContainer; + $scope.chooseResult = chooseResult; + + getEnvironmentList(); + // getImageList(); + + } + + function gotoProject() { + $scope.project = 1; + } + + function gotoEnvironment() { + $scope.project = 0; + } + + function gotoTask() { + $scope.project = 2; + } + + function gotoExcute() { + $scope.project = 3; + + } + + function gotoReport() { + $scope.project = 4; + } + $scope.skipPod = function skipPod() { + $scope.showContainer = 1; + + } + $scope.skipContainer = function skipContainer() { + getEnvironmentList(); + ngDialog.close(); + } + + $scope.goToImage = function goToImage() { + getImageListSimple(); + $scope.showImage = 1; + } + $scope.goToPod = function goToPod() { + $scope.showPod = 1; + } + $scope.goToPodPrev = function goToPodPrev() { + $scope.showImage = null; + + } + $scope.skipPodPrev = function skipPodPrev() { + $scope.showImage = 1; + $scope.showPod = null; + + } + $scope.skipContainerPrev = function skipContainerPrev() { + $scope.showPod = 1; + $scope.showContainer = null; + } + + $scope.envInfo = [ + { name: 'OS_USERNAME', value: '' }, + { name: 'OS_PASSWORD', value: '' }, + { name: 'OS_TENANT_NAME', value: '' }, + { name: 'EXTERNAL_NETWORK', value: '' } + ]; + + + function deleteEnvItem(index) { + $scope.envInfo.splice(index, 1); + } + + function addInfo() { + var tempKey = null; + var tempValue = null; + var temp = { + name: tempKey, + value: tempValue + } + $scope.envInfo.push(temp); + + } + + function submitOpenRcFile() { + $scope.showloading = true; + + var postData = {}; + postData['action'] = 'update_openrc'; + rebuildEnvInfo(); + postData['args'] = {}; + postData.args["openrc"] = $scope.postEnvInfo; + postData.args['environment_id'] = $scope.uuidEnv; + mainFactory.postEnvironmentVariable().post(postData).$promise.then(function(response) { + $scope.showloading = false; + + if (response.status == 1) { + + $scope.openrcInfo = response.result; + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.showEnvrionment = true; + // $scope.showImage = response.status; + $scope.showNextOpenRc = 1; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function uploadFiles($file, $invalidFiles) { + $scope.openrcInfo = {}; + $scope.loadingOPENrc = true; + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/openrcs', + data: { file: $file, 'environment_id': $scope.uuidEnv, 'action': 'upload_openrc' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.openrcInfo = response.data.result; + + getItemIdDetailforOpenrc(); + $scope.showNextOpenRc = 1; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //reconstruc EnvInfo + function rebuildEnvInfo() { + $scope.postEnvInfo = {}; + for (var i = 0; i < $scope.envInfo.length; i++) { + $scope.postEnvInfo[$scope.envInfo[i].name] = $scope.envInfo[i].value; + } + + } + function uploadFilesPod($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayPodFile = $file; + timeConstruct($scope.displayPodFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/pods', + data: { file: $file, 'environment_id': $scope.uuidEnv, 'action': 'upload_pod_file' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + $scope.podData = response.data.result; + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + + //display environment + function showEnvriomentStatus() { + $scope.showEnvironment = true; + } + + //open Environment dialog + function openEnvironmentDialog() { + $scope.showEnvrionment = false; + $scope.loadingOPENrc = false; + $scope.uuidEnv = null; + $scope.showPod = null; + $scope.showImage = null; + $scope.showContainer = null; + $scope.showNextOpenRc = null; + $scope.showNextPod = null; + $scope.displayContainerInfo = []; + + $scope.displayPodFile = null; + $scope.name = null; + $scope.openrcInfo = null; + $scope.envInfo = [ + { name: 'OS_USERNAME', value: '' }, + { name: 'OS_PASSWORD', value: '' }, + { name: 'OS_TENANT_NAME', value: '' }, + { name: 'EXTERNAL_NETWORK', value: '' } + ]; + $scope.displayOpenrcFile = null; + $scope.podData = null; + $scope.displayContainerInfo = null; + ngDialog.open({ + preCloseCallback: function(value) { + getEnvironmentList(); + // getImageList(); + }, + template: 'views/modal/environmentDialog.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 950, + showClose: true, + closeByDocument: false + }) + } + + function getEnvironmentList() { + $loading.start('key'); + + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + $loading.finish('key'); + + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + //go to detail page + function gotoDetail(ifNew, uuid) { + + $state.go('app.environmentDetail', { uuid: uuid, ifNew: ifNew }); + } + + + function addEnvironment(name) { + mainFactory.addEnvName().post({ + 'action': 'create_environment', + args: { + 'name': name + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create name success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.uuidEnv = response.result.uuid; + $scope.name = name; + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + $scope.displayContainerInfo = []; + + function createContainer(selectContainer) { + + $scope.showloading = true; + mainFactory.runAcontainer().post({ + 'action': selectContainer.value, + 'args': { + 'environment_id': $scope.uuidEnv, + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create container success', + body: 'you can go next step', + timeout: 3000 + }); + + setTimeout(function() { + getItemIdDetail(); + }, 10000); + $scope.ifskipOrClose = 1; + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + $scope.displayContainerInfo.push(response.result.container); + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + function chooseResult(name) { + $scope.selectContainer = name; + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.uuidEnv + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + $scope.containerId = response.result.environment.container_id; + if ($scope.containerId != null) { + + var keysArray = Object.keys($scope.containerId); + for (var k in $scope.containerId) { + getConDetail($scope.containerId[k]); + + } + + + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.uploadImage = function uploadImage() { + $scope.imageStatus = 0; + $scope.showImageStatus = 1; + $scope.showloading = true; + mainFactory.uploadImage().post({ + 'action': 'load_image', + 'args': { + 'environment_id': $scope.uuid + + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + setTimeout(function() { + getImageList(); + }, 10000); + $scope.showNextPod = 1; + + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + function getImageList() { + if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + $scope.imageStatus = response.result.status; + + if ($scope.imageStatus == 0) { + $scope.intervalImgae = $interval(function() { + getImageList(); + }, 5000); + } else if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + function getImageListSimple() { + + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + $scope.imageStatus = response.result.status; + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteEnv = function deleteEnv() { + mainFactory.deleteEnv().delete({ 'env_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete environment success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getEnvironmentList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + function getItemIdDetailforOpenrc() { + + mainFactory.ItemDetail().get({ + 'envId': $scope.uuidEnv + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + if ($scope.ifNew != 'true') { + $scope.baseElementInfo = response.result.environment; + if ($scope.baseElementInfo.openrc_id != null) { + getOpenrcDetailForOpenrc($scope.baseElementInfo.openrc_id); + } + } + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + + } + }, function(error) { + + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + //getopenRcid + function getOpenrcDetailForOpenrc(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + buildToEnvInfoOpenrc($scope.openrcInfo.openrc) + }, function(response) { + toaster.pop({ + type: 'error', + title: 'error', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //buildtoEnvInfo + function buildToEnvInfoOpenrc(object) { + var tempKeyArray = Object.keys(object); + $scope.envInfo = []; + + + for (var i = 0; i < tempKeyArray.length; i++) { + var tempkey = tempKeyArray[i]; + var tempValue = object[tempKeyArray[i]]; + var temp = { + name: tempkey, + value: tempValue + }; + $scope.envInfo.push(temp); + } + } + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/pod.controller.js b/gui/app/scripts/controllers/pod.controller.js new file mode 100644 index 000000000..3ef236854 --- /dev/null +++ b/gui/app/scripts/controllers/pod.controller.js @@ -0,0 +1,179 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('PodController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, ngDialog) { + + + init(); + $scope.showloading = false; + $scope.loadingOPENrc = false; + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.uploadFiles = uploadFiles; + getItemIdDetail(); + + } + + function getItemIdDetail() { + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.name = response.result.environment.name; + $scope.podId = response.result.environment.pod_id; + if ($scope.podId != null) { + getPodDetail($scope.podId); + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getPodDetail(id) { + mainFactory.getPodDetail().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podData = response.result; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + //upload pod file + function uploadFiles($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/pods', + data: { file: $file, 'environment_id': $scope.uuid, 'action': 'upload_pod_file' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + $scope.podData = response.data.result; + + getItemIdDetail(); + + + } else { + + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.container', { uuid: $scope.uuid }); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deletePod = function deletePod() { + mainFactory.deletePod().delete({ 'podId': $scope.podId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete pod success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + $scope.uuid = $stateParams.uuid; + $scope.uploadFiles = uploadFiles; + $scope.displayOpenrcFile = null; + getItemIdDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/controllers/project.controller.js b/gui/app/scripts/controllers/project.controller.js new file mode 100644 index 000000000..0a7b8b932 --- /dev/null +++ b/gui/app/scripts/controllers/project.controller.js @@ -0,0 +1,160 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ProjectController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + + + function init() { + + + getProjectList(); + $scope.openCreateProject = openCreateProject; + $scope.createName = createName; + $scope.gotoDetail = gotoDetail; + + + } + + function getProjectList() { + $loading.start('key'); + mainFactory.projectList().get({}).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.projectListData = response.result.projects; + + + } else { + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function openCreateProject() { + + ngDialog.open({ + template: 'views/modal/projectCreate.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 400, + showClose: true, + closeByDocument: false + }) + } + + function createName(name) { + + mainFactory.createProjectName().post({ + 'action': 'create_project', + 'args': { + 'name': name, + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create project success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectList(); + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'create project failed', + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'Something Wrong', + timeout: 3000 + }); + }) + } + + function gotoDetail(id) { + $state.go('app2.projectdetail', { projectId: id }) + } + + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteProject = function deleteProject() { + mainFactory.deleteProject().delete({ 'project_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Project success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/projectDetail.controller.js b/gui/app/scripts/controllers/projectDetail.controller.js new file mode 100644 index 000000000..4ab4a055a --- /dev/null +++ b/gui/app/scripts/controllers/projectDetail.controller.js @@ -0,0 +1,690 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ProjectDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$localStorage', '$loading', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $localStorage, $loading, $interval) { + + + init(); + // $scope.taskListDisplay = []; + $scope.blisterPackTemplates = [{ id: 1, name: "Test Case" }, { id: 2, name: "Test Suite" }] + $scope.selectType = null; + $scope.ifHasEnv = false; + $scope.ifHasCase = false; + $scope.ifHasSuite = false; + $scope.$on('$destroy', function() { + $interval.cancel($scope.intervalCount) + }); + $scope.finalTaskListDisplay = []; + + + function init() { + + + getProjectDetail(); + + $scope.openCreate = openCreate; + $scope.createTask = createTask; + $scope.constructTestSuit = constructTestSuit; + $scope.addEnvToTask = addEnvToTask; + $scope.triggerContent = triggerContent; + $scope.constructTestCase = constructTestCase; + $scope.getTestDeatil = getTestDeatil; + $scope.confirmAddCaseOrSuite = confirmAddCaseOrSuite; + $scope.runAtask = runAtask; + $scope.gotoDetail = gotoDetail; + $scope.gotoReport = gotoReport; + $scope.gotoModify = gotoModify; + $scope.goBack = goBack; + $scope.goToExternal = goToExternal; + + + } + + function getProjectDetail() { + if ($scope.intervalCount != undefined) { + $interval.cancel($scope.intervalCount); + } + $loading.start('key'); + $scope.taskListDisplay = []; + $scope.finalTaskListDisplay = []; + mainFactory.getProjectDetail().get({ + project_id: $stateParams.projectId + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + + $scope.projectData = response.result.project; + if ($scope.projectData.tasks.length != 0) { + + + for (var i = 0; i < $scope.projectData.tasks.length; i++) { + getDetailTaskForList($scope.projectData.tasks[i]); + } + $scope.intervalCount = $interval(function() { + getDetailForEachTask(); + }, 10000); + } else { + + if ($scope.intervalCount != undefined) { + $interval.cancel($scope.intervalCount); + } + } + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + // function getProjectDetailSimple() { + // getDetailForEachTask(); + // } + + function openCreate() { + $scope.newUUID = null; + $scope.displayEnvName = null; + $scope.selectEnv = null; + $scope.selectCase = null; + $scope.selectType = null; + $scope.contentInfo = null; + $scope.ifHasEnv = false; + $scope.ifHasCase = false; + $scope.ifHasSuite = false; + + // getEnvironmentList(); + $scope.selectEnv = null; + ngDialog.open({ + template: 'views/modal/taskCreate.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 800, + showClose: true, + closeByDocument: false, + preCloseCallback: function(value) { + getProjectDetail(); + }, + }) + } + + function createTask(name) { + mainFactory.createTask().post({ + 'action': 'create_task', + 'args': { + 'name': name, + 'project_id': $stateParams.projectId + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create task success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.newUUID = response.result.uuid; + getEnvironmentList(); + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function getDetailTaskForList(id) { + + mainFactory.getTaskDetail().get({ + 'taskId': id + }).$promise.then(function(response) { + + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + $scope.taskListDisplay.push(response.result.task); + console.log($scope.taskListDisplay); + + $scope.finalTaskListDisplay = $scope.taskListDisplay; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function getDetailTaskForListSimple(id, index) { + + mainFactory.getTaskDetail().get({ + 'taskId': id + }).$promise.then(function(response) { + + if (response.status == 1) { + if (response.result.task.status == -1) { + + $scope.finalTaskListDisplay[index].stausWidth = '5%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 0) { + + $scope.finalTaskListDisplay[index].stausWidth = '50%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 1) { + + $scope.finalTaskListDisplay[index].stausWidth = '100%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 2) { + + $scope.finalTaskListDisplay[index].stausWidth = 'red'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function getDetailForEachTask() { + for (var i = 0; i < $scope.finalTaskListDisplay.length; i++) { + if ($scope.finalTaskListDisplay[i].status != 1 && $scope.finalTaskListDisplay[i].status != -1) { + getDetailTaskForListSimple($scope.finalTaskListDisplay[i].uuid, i); + } + } + } + + function getEnvironmentList() { + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function constructTestSuit(id, name) { + $scope.displayEnvName = name; + $scope.selectEnv = id; + + } + + function constructTestCase(name) { + + $scope.selectCase = name; + if ($scope.selectType.name == 'Test Case') { + getCaseInfo(); + } else { + getSuiteInfo(); + } + + } + + + + + function addEnvToTask() { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_environment', + 'args': { + 'task_id': $scope.newUUID, + 'environment_id': $scope.selectEnv + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add environment success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasEnv = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function triggerContent(name) { + $scope.selectCase = null; + $scope.displayTable = true; + + $scope.selectType = name; + if (name.name == 'Test Case') { + getTestcaseList(); + } else if (name.name == 'Test Suite') { + getsuiteList(); + } + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getsuiteList() { + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getTestDeatil() { + + + if ($scope.selectType.name == 'Test Case') { + getTestcaseDetail(); + } else { + getSuiteDetail(); + } + + } + + function getCaseInfo() { + + + + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getSuiteInfo() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayTable = false; + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.displayTable = false; + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function addCasetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_case', + 'args': { + 'task_id': $scope.newUUID, + 'case_name': $scope.selectCase, + 'case_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test case success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasCase = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function addSuitetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_suite', + 'args': { + 'task_id': $scope.newUUID, + 'suite_name': $scope.selectCase.split('.')[0], + 'suite_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test suite success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasSuite = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'wrong', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + function confirmAddCaseOrSuite(content) { + if ($scope.selectType.name == "Test Case") { + addCasetoTask(content); + } else { + addSuitetoTask(content); + } + } + + function runAtask(id) { + mainFactory.taskAddEnv().put({ + 'taskId': id, + 'action': 'run', + 'args': { + 'task_id': id + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'run a task success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + // getProjectDetail(); + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + $scope.runAtaskForTable = function runAtaskForTable(id) { + mainFactory.taskAddEnv().put({ + 'taskId': id, + 'action': 'run', + 'args': { + 'task_id': id + } + }).$promise.then(function(response) { + if (response.status == 1) { + // toaster.pop({ + // type: 'success', + // title: 'run a task success', + // body: 'you can go next step', + // timeout: 3000 + // }); + // ngDialog.close(); + getProjectDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.result, + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function gotoDetail(id) { + + + $state.go('app2.tasklist', { taskId: id }); + + } + + function gotoReport(id) { + $state.go('app2.report', { taskId: id }); + } + + function gotoModify(id) { + $state.go('app2.taskModify', { taskId: id }); + } + + function goBack() { + window.history.back(); + } + + function goToExternal() { + window.open(External_URL, '_blank'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteTask = function deleteTask() { + mainFactory.deleteTask().delete({ 'task_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Task success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/controllers/report.controller.js b/gui/app/scripts/controllers/report.controller.js new file mode 100644 index 000000000..9b6b5958b --- /dev/null +++ b/gui/app/scripts/controllers/report.controller.js @@ -0,0 +1,115 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ReportController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + getDetailTaskForList(); + + + + } + $scope.goBack = function goBack() { + window.history.back(); + } + + function getDetailTaskForList(id) { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + $scope.result = response.result.task; + $scope.testcaseinfo = response.result.task.result.testcases; + var key = Object.keys($scope.testcaseinfo); + $scope.testcaseResult = $scope.testcaseinfo[key]; + + $scope.envIdForTask = response.result.task.environment_id; + getItemIdDetail(); + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + $scope.goToExternal = function goToExternal(id) { + var url = External_URL + ':' + $scope.jumpPort + '/dashboard/db' + '/' + id; + + window.open(url, '_blank'); + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.envIdForTask + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.environment.container_id.grafana != null) { + getConDetail(response.result.environment.container_id.grafana); + + } else { + $scope.jumpPort = 3000; + } + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + $scope.jumpPort = response.result.container.port; + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/suitecreate.controller.js b/gui/app/scripts/controllers/suitecreate.controller.js new file mode 100644 index 000000000..4a7b6fe85 --- /dev/null +++ b/gui/app/scripts/controllers/suitecreate.controller.js @@ -0,0 +1,104 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('suitcreateController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + + getTestcaseList(); + $scope.constructTestSuit = constructTestSuit; + $scope.openDialog = openDialog; + $scope.createSuite = createSuite; + + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.testsuiteList = []; + $scope.suitReconstructList = []; + + function constructTestSuit(name) { + + var index = $scope.testsuiteList.indexOf(name); + if (index > -1) { + $scope.testsuiteList.splice(index, 1); + } else { + $scope.testsuiteList.push(name); + } + + + $scope.suitReconstructList = $scope.testsuiteList; + + } + + function createSuite(name) { + mainFactory.suiteCreate().post({ + 'action': 'create_suite', + 'args': { + 'name': name, + 'testcases': $scope.testsuiteList + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create suite success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function openDialog() { + ngDialog.open({ + template: 'views/modal/suiteName.html', + className: 'ngdialog-theme-default', + scope: $scope, + width: 314, + showClose: true, + closeByDocument: false + }) + } + + + + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/controllers/suitedetail.controller.js b/gui/app/scripts/controllers/suitedetail.controller.js new file mode 100644 index 000000000..0dd39c389 --- /dev/null +++ b/gui/app/scripts/controllers/suitedetail.controller.js @@ -0,0 +1,48 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('suiteDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + + + function init() { + + getSuiteDetail(); + + } + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $stateParams.name + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.suiteinfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/task.controller.js b/gui/app/scripts/controllers/task.controller.js new file mode 100644 index 000000000..05546f9bf --- /dev/null +++ b/gui/app/scripts/controllers/task.controller.js @@ -0,0 +1,175 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TaskController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + getDetailTaskForList(); + + } + + function getDetailTaskForList() { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + + $scope.taskDetailData = response.result.task; + if ($scope.taskDetailData.environment_id != null) { + getItemIdDetail($scope.taskDetailData.environment_id); + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getItemIdDetail(id) { + mainFactory.ItemDetail().get({ + 'envId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayEnv = response.result.environment; + + if (response.result.environment.pod_id != null) { + getPodDetail(response.result.environment.pod_id); + } else if (response.result.environment.image_id != null) { + getImageDetail(response.result.environment.image_id); + } else if (response.result.environment.openrc_id != null) { + getOpenrcDetail(response.result.environment.openrc_id != null); + } else if (response.result.environment.container_id.length != 0) { + $scope.displayContainerDetail = []; + var containerArray = response.result.environment.container_id; + for (var i = 0; i < containerArray.length; i++) { + getContainerId(containerArray[i]); + } + + } + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + //openrc数据 + $scope.openrcInfo = response.result; + // buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail(id) { + mainFactory.ImageDetail().get({ + 'image_id': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail(id) { + mainFactory.podDeatil().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getContainerId(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.container = response.result.container; + $scope.displayContainerDetail.push($scope.container); + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/controllers/taskModify.controller.js b/gui/app/scripts/controllers/taskModify.controller.js new file mode 100644 index 000000000..757d65866 --- /dev/null +++ b/gui/app/scripts/controllers/taskModify.controller.js @@ -0,0 +1,533 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TaskModifyController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + $scope.blisterPackTemplates = [{ id: 1, name: "Test Case" }, { id: 2, name: "Test Suite" }] + $scope.selectType = null; + + $scope.sourceShow = null; + + + + function init() { + getDetailTaskForList(); + getEnvironmentList(); + $scope.triggerContent = triggerContent; + $scope.constructTestSuit = constructTestSuit; + $scope.constructTestCase = constructTestCase; + $scope.getTestDeatil = getTestDeatil; + $scope.confirmToServer = confirmToServer; + $scope.addEnvToTask = addEnvToTask; + } + + function getDetailTaskForList() { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + + $scope.taskDetailData = response.result.task; + $scope.selectEnv = $scope.taskDetailData.environment_id; + + if ($scope.taskDetailData.environment_id != null) { + getItemIdDetail($scope.taskDetailData.environment_id); + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getItemIdDetail(id) { + mainFactory.ItemDetail().get({ + 'envId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + // $scope.selectEnv = $scope.envName; + } else { + alert('Something Wrong!'); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + // buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail(id) { + mainFactory.ImageDetail().get({ + 'image_id': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail(id) { + mainFactory.podDeatil().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getContainerId(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.container = response.result.container; + $scope.displayContainerDetail.push($scope.container); + + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getEnvironmentList() { + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function triggerContent(name) { + $scope.selectCase = null; + $scope.displayTable = true; + + $scope.selectType = name; + if (name.name == 'Test Case') { + $scope.taskDetailData.suite = false; + getTestcaseList(); + } else if (name.name == 'Test Suite') { + $scope.taskDetailData.suite = true; + getsuiteList(); + } + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getsuiteList() { + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function constructTestSuit(id, name) { + + $scope.envName = name; + $scope.selectEnv = id; + + } + + function constructTestCase(name) { + + $scope.selectCase = name; + if ($scope.selectType.name == 'Test Case') { + getCaseInfo(); + } else { + getSuiteInfo(); + } + + } + + function getCaseInfo() { + + + + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getSuiteInfo() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestDeatil() { + + + if ($scope.selectType.name == 'Test Case') { + getTestcaseDetail(); + } else { + getSuiteDetail(); + } + + } + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayTable = false; + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.displayTable = false; + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + function addCasetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_case', + 'args': { + 'task_id': $stateParams.taskId, + 'case_name': $scope.selectCase, + 'case_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test case success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasCase = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: '', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: '', + timeout: 3000 + }); + }) + } + + function addSuitetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_suite', + 'args': { + 'task_id': $stateParams.taskId, + 'suite_name': $scope.selectCase.split('.')[0], + 'suite_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test suite success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasSuite = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'wrong', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'something wrong', + timeout: 3000 + }); + }) + } + $scope.changeStatussourceTrue = function changeStatussourceTrue() { + $scope.selectCase = null; + $scope.sourceShow = true; + } + + $scope.changeStatussourceFalse = function changeStatussourceFalse() { + $scope.sourceShow = false; + } + + function confirmToServer(content1, content2) { + + var content; + if ($scope.sourceShow == false) { + content = content2; + $scope.selectCase = $scope.taskDetailData.case_name; + } else if ($scope.sourceShow == true) { + content = content1; + } + if ($scope.selectCase == 'Test Case' || $scope.taskDetailData.suite == false) { + + addCasetoTask(content); + } else { + addSuitetoTask(content); + } + } + + + function addEnvToTask() { + + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_environment', + 'args': { + 'task_id': $stateParams.taskId, + 'environment_id': $scope.selectEnv + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add environment success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasEnv = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + window.history.back(); + } + + $scope.runAtask = function runAtask() { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'run', + 'args': { + 'task_id': $stateParams.taskId + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'run a task success', + body: 'go to task list page...', + timeout: 3000 + }); + setTimeout(function() { + window.history.back(); + }, 2000); + + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/testcase.controller.js b/gui/app/scripts/controllers/testcase.controller.js new file mode 100644 index 000000000..616ceb4a8 --- /dev/null +++ b/gui/app/scripts/controllers/testcase.controller.js @@ -0,0 +1,154 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TestcaseController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + $scope.loadingOPENrc = false; + + + function init() { + $scope.testcaselist = []; + getTestcaseList(); + $scope.gotoDetail = gotoDetail; + $scope.uploadFiles = uploadFiles; + + + } + + function getTestcaseList() { + $loading.start('key'); + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function gotoDetail(name) { + $state.go('app2.testcasedetail', { name: name }); + } + + + function uploadFiles($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/testcases', + data: { file: $file, 'action': 'upload_case' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + + + } else { + + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteTestCase = function deleteTestCase() { + mainFactory.deleteTestCase().delete({ 'caseName': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Test Case success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getTestcaseList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/controllers/testcasedetail.controller.js b/gui/app/scripts/controllers/testcasedetail.controller.js new file mode 100644 index 000000000..4e824ca85 --- /dev/null +++ b/gui/app/scripts/controllers/testcasedetail.controller.js @@ -0,0 +1,50 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('testcaseDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + + + function init() { + + getTestcaseDetail(); + + + } + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $stateParams.name + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaseInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/controllers/testsuit.controller.js b/gui/app/scripts/controllers/testsuit.controller.js new file mode 100644 index 000000000..abc9095c7 --- /dev/null +++ b/gui/app/scripts/controllers/testsuit.controller.js @@ -0,0 +1,119 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('SuiteListController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + + + function init() { + $scope.testsuitlist = []; + getsuiteList(); + $scope.gotoDetail = gotoDetail; + $scope.gotoCreateSuite = gotoCreateSuite; + + + } + + function getsuiteList() { + $loading.start('key'); + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function gotoDetail(name) { + var temp = name.split('.')[0]; + + $state.go('app2.suitedetail', { name: temp }) + + } + + function gotoCreateSuite() { + $state.go('app2.suitcreate'); + } + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id.split('.')[0]; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteSuite = function deleteSuite() { + mainFactory.deleteTestSuite().delete({ 'suite_name': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Test Suite success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getTestcaseList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + + + + + + + + + + } + ]); \ No newline at end of file diff --git a/gui/app/scripts/factory/main.factory.js b/gui/app/scripts/factory/main.factory.js new file mode 100644 index 000000000..f8e9df9a1 --- /dev/null +++ b/gui/app/scripts/factory/main.factory.js @@ -0,0 +1,247 @@ +'use strict'; + +/** + * get data factory + */ + + +var Base_URL; +var Grafana_URL; + +angular.module('yardStickGui2App') + .factory('mainFactory', ['$resource','$rootScope','$http', '$location',function($resource, $rootScope,$http,$location) { + + Base_URL = 'http://' + $location.host() + ':' + $location.port(); + Grafana_URL = 'http://' + $location.host(); + + return { + + postEnvironmentVariable: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs', {}, { + 'post': { + method: 'POST' + } + }) + }, + uploadOpenrc: function() { + return $resource(Base_URL + '/ap/v2/yardstick/openrcs', {}, { + 'post': { + method: 'POST' + } + }) + }, + getEnvironmentList: function() { + return $resource(Base_URL+ '/api/v2/yardstick/environments', {}, { + 'get': { + method: 'GET' + } + }) + }, + getEnvironmentDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs/:openrc_id', { openrc_id: "@openrc_id" }, { + 'get': { + method: 'GET' + } + }) + }, + addEnvName: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments', {}, { + 'post': { + method: 'POST' + } + }) + }, + ItemDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments/:envId', { envId: "@envId" }, { + 'get': { + method: 'GET' + } + }) + }, + ImageDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/images/:image_id', { image_id: "@image_id" }, { + 'get': { + method: 'GET' + } + }) + }, + podDeatil: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: "@podId" }, { + 'get': { + method: 'GET' + } + }) + }, + containerDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers/:containerId', { containerId: "@containerId" }, { + 'get': { + method: 'GET' + } + }) + }, + ImageList: function() { + return $resource(Base_URL + '/api/v2/yardstick/images', {}, { + 'get': { + method: 'GET' + } + }) + }, + uploadImage: function() { + return $resource(Base_URL + '/api/v2/yardstick/images', {}, { + 'post': { + method: 'POST' + } + }) + }, + getPodDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: "@podId" }, { + 'get': { + method: 'GET' + } + }) + }, + runAcontainer: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers', { podId: "@podId" }, { + 'post': { + method: 'POST' + } + }) + }, + getTestcaselist: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases', {}, { + 'get': { + method: 'GET' + } + }) + }, + getTestcaseDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases/:testcasename', { testcasename: "@testcasename" }, { + 'get': { + method: 'GET' + } + }) + }, + suiteList: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites', {}, { + 'get': { + method: 'GET' + } + }) + }, + suiteDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites/:suiteName', { suiteName: "@suiteName" }, { + 'get': { + method: 'GET' + } + }) + }, + suiteCreate: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites', {}, { + 'post': { + method: 'POST' + } + }) + }, + projectList: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects', {}, { + 'get': { + method: 'GET' + } + }) + }, + createProjectName: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects', {}, { + 'post': { + method: 'POST' + } + }) + }, + getProjectDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects/:project_id', { project_id: "@project_id" }, { + 'post': { + method: 'POST' + } + }) + }, + createTask: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks', {}, { + 'post': { + method: 'POST' + } + }) + }, + getTaskDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:taskId', { taskId: "@taskId" }, { + 'get': { + method: 'GET' + } + }) + }, + + taskAddEnv: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:taskId', { taskId: "@taskId" }, { + 'put': { + method: 'PUT' + } + }) + }, + //delete operate + deleteEnv: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments/:env_id', { env_id: '@env_id' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteOpenrc: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs/:openrc', { openrc: '@openrc' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deletePod: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: '@podId' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteContainer: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers/:containerId', { containerId: '@containerId' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTestCase: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases/:caseName', { caseName: '@caseName' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTestSuite: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites/:suite_name', { suite_name: '@suite_name' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteProject: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects/:project_id', { project_id: '@project_id' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTask: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:task_id', { task_id: '@task_id' }, { + 'delete': { + method: 'DELETE' + } + }) + } + + }; + }]); diff --git a/gui/app/scripts/router.config.js b/gui/app/scripts/router.config.js new file mode 100644 index 000000000..b42954272 --- /dev/null +++ b/gui/app/scripts/router.config.js @@ -0,0 +1,184 @@ +'use strict'; + +angular.module('yardStickGui2App') + .run( + ['$rootScope', '$state', '$stateParams', + function($rootScope, $state, $stateParams) { + $rootScope.$state = $state; + $rootScope.$stateParams = $stateParams; + + } + ] + ) + .config(['$stateProvider', '$urlRouterProvider', '$locationProvider', + function($stateProvider, $urlRouterProvider, $locationProvider) { + $urlRouterProvider + .otherwise('main/environment'); + + + + + $stateProvider + + .state('app2', { + url: "/main", + controller: 'ContentController', + templateUrl: "views/main2.html", + ncyBreadcrumb: { + label: 'Main' + } + }) + .state('app', { + url: "/main", + controller: 'ContentController', + templateUrl: "views/main.html", + ncyBreadcrumb: { + label: 'Main' + } + }) + + .state('app2.environment', { + url: '/environment', + templateUrl: 'views/environmentList.html', + controller: 'MainCtrl', + ncyBreadcrumb: { + label: 'Environment' + } + }) + .state('app2.testcase', { + url: '/testcase', + templateUrl: 'views/testcaselist.html', + controller: 'TestcaseController', + ncyBreadcrumb: { + label: 'Test Case' + } + }) + .state('app2.testsuite', { + url: '/suite', + templateUrl: 'views/suite.html', + controller: 'SuiteListController', + ncyBreadcrumb: { + label: 'Test Suite' + } + }) + .state('app2.suitcreate', { + url: '/suitcreate', + templateUrl: 'views/testcasechoose.html', + controller: 'suitcreateController', + ncyBreadcrumb: { + label: 'Suite Create' + } + }) + .state('app2.testcasedetail', { + url: '/testdetail/:name', + templateUrl: 'views/testcasedetail.html', + controller: 'testcaseDetailController', + ncyBreadcrumb: { + label: 'Test Case Detail' + }, + params: { name: null } + }) + .state('app2.suitedetail', { + url: '/suitedetail/:name', + templateUrl: 'views/suitedetail.html', + controller: 'suiteDetailController', + ncyBreadcrumb: { + label: 'Suite Detail' + }, + params: { name: null } + }) + .state('app.environmentDetail', { + url: '/envDetail/:uuid', + templateUrl: 'views/environmentDetail.html', + controller: 'DetailController', + params: { uuid: null, ifNew: null }, + ncyBreadcrumb: { + label: 'Environment Detail' + } + }) + .state('app.uploadImage', { + url: '/envimageDetail/:uuid', + templateUrl: 'views/uploadImage.html', + controller: 'ImageController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Upload Image' + } + + }) + .state('app.podUpload', { + url: '/envpodupload/:uuid', + templateUrl: 'views/podupload.html', + controller: 'PodController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Pod Upload' + } + }) + .state('app.container', { + url: '/envcontainer/:uuid', + templateUrl: 'views/container.html', + controller: 'ContainerController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Container Manage' + } + }) + .state('app2.projectList', { + url: '/project', + templateUrl: 'views/projectList.html', + controller: 'ProjectController', + ncyBreadcrumb: { + label: 'Project' + } + + }) + .state('app2.tasklist', { + url: '/task/:taskId', + templateUrl: 'views/taskList.html', + controller: 'TaskController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Task' + } + + }) + .state('app2.report', { + url: '/report/:taskId', + templateUrl: 'views/report.html', + controller: 'ReportController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Report' + } + + }) + .state('app2.projectdetail', { + url: '/projectdetail/:projectId', + templateUrl: 'views/projectdetail.html', + controller: 'ProjectDetailController', + params: { projectId: null }, + ncyBreadcrumb: { + label: 'Project Detail' + } + + }) + .state('app2.taskModify', { + url: '/taskModify/:taskId', + templateUrl: 'views/taskmodify.html', + controller: 'TaskModifyController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Modify Task' + } + + + }) + + + + + + } + ]) + .run(); diff --git a/gui/app/styles/main.css b/gui/app/styles/main.css new file mode 100644 index 000000000..e13a66bce --- /dev/null +++ b/gui/app/styles/main.css @@ -0,0 +1,208 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +body { + padding: 0; +} + + +/* Everything but the jumbotron gets side spacing for mobile first views */ + +.header, +.marketing, +.footer { + /*padding-left: 15px; + padding-right: 15px;*/ +} + + +/* Custom page header */ + +.header { + border-bottom: 1px solid #e5e5e5; + margin-bottom: 10px; +} + + +/* Make the masthead heading the same height as the navigation */ + +.header h3 { + margin-top: 0; + margin-bottom: 0; + line-height: 40px; + padding-bottom: 19px; +} + + +/* Custom page footer */ + +.footer { + padding-top: 19px; + color: #777; + border-top: 1px solid #e5e5e5; +} + +.container-narrow>hr { + margin: 30px 0; +} + + +/* Main marketing message and sign up button */ + +.jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; +} + +.jumbotron .btn { + font-size: 21px; + padding: 14px 24px; +} + + +/* Supporting marketing content */ + +.marketing { + margin: 40px 0; +} + +.marketing p+h4 { + margin-top: 28px; +} + + +/* Responsive: Portrait tablets and up */ + +@media screen and (min-width: 768px) { + .container { + max-width: 730px; + } + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-left: 0; + padding-right: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } +} + +.jumbotron.ng-scope { + margin-top: 100px; +} + +.content { + /*margin-left: 300px;*/ + /*margin-left: 100px;*/ + position: relative; + margin-left: 25%; + width: 70%; + border: 1px solid #dfe3e4; + border-radius: 5px; + padding: 20px 20px 5px 20px; + height: 100%; + margin-right: 10px; + /*border-bottom: none;*/ + margin-bottom: 10px; + padding-bottom: 20px; + /* overflow: hidden; */ +} + +.ngdialog.ngdialog-theme-default .ngdialog-content { + background-color: #fff; +} + +.progree-parent { + width: 50%; + background-color: #dfe3e4; + height: 10px; + border-radius: 10px; +} + +.progree-child { + width: 50%; + background-color: #2ecc71; + /* background-color: white; */ + height: 10px; + border-radius: 5px; +} + +textarea { + width: 100%; + height: 400px; + border-radius: 5px; + border: 1px solid #e8e8e8; +} + +.naviSide { + position: fixed; + left: 0px; + top: 0px; + height: 150%; + background-color: #f8f8f8; + width: 210px; + padding: 30px 0 0 0; + border-radius: 10px; + border-right: 1px solid #e7e7e7; + z-index: 2; +} + +.panel-body { + border: none; +} + +.panel-group { + width: 210px; +} + +.panel-group { + margin-bottom: 0px; +} + +* { + border-radius: 0px ! important; +} + +.naviSide.ng-scope { + box-shadow: 1px 1px 2px #888888; +} + +.pagination>li>a, +.pagination>li>span { + color: #333; +} + +.pagination>.active>a, +.pagination>.active>span, +.pagination>.active>a:hover, +.pagination>.active>span:hover, +.pagination>.active>a:focus, +.pagination>.active>span:focus { + background-color: #f9f9f9; + color: #333; + border-color: #ddd; +} + +.ngdialog.ngdialog-theme-default .ngdialog-close{ + border: none; + background-color: #fff; +} + +button:focus {outline:0;} +input:focus{outline: 0} + +.ngdialog-content { + overflow: hidden; +} + diff --git a/gui/app/views/container.html b/gui/app/views/container.html new file mode 100644 index 000000000..b3d78bfb1 --- /dev/null +++ b/gui/app/views/container.html @@ -0,0 +1,134 @@ + + +
+
+
+ +

{{envName}} -- Container + + +

+ + +
+ + + + + +
+ +
+

No Container Data

+
+

Current Container

+ + + + + + + + + + + + + + + + + + + + +
namestatustimedelete
{{con.name}}{{con.status}}{{con.time}} + +
+
+
+ + + + + + + + + + +
+ + +
+ +
+ + + diff --git a/gui/app/views/content.html b/gui/app/views/content.html new file mode 100644 index 000000000..e69de29bb diff --git a/gui/app/views/environmentDetail.html b/gui/app/views/environmentDetail.html new file mode 100644 index 000000000..4d5f21c68 --- /dev/null +++ b/gui/app/views/environmentDetail.html @@ -0,0 +1,143 @@ + + +
+
+
+ + +

{{baseElementInfo.name}} -- Openrc + + +

+ + + + +
+ + +
+ + + +
+
+
+ +

+ You have already set up the openrc parameters +

+
+
+ + {{key}} : + {{value}} + +
+ +
+
+ +
+ +
+
+ + + + +
+ + + +
+ + + +
+
+ + +
+ +
+
+
+ + + +
+
+
+ + + +
+ + +
+ +
+ + + diff --git a/gui/app/views/environmentList.html b/gui/app/views/environmentList.html new file mode 100644 index 000000000..29273a724 --- /dev/null +++ b/gui/app/views/environmentList.html @@ -0,0 +1,155 @@ +
+ + + + +
+ +

Environments + +

+
+ + +
+
+
Name
+
Action
+ +
+ +
+
+ +
+ + + +
+ + +
+
+ + + +
+ +
+
+ +
+
+ + + + + + + +
+ + + + +
+ + + + diff --git a/gui/app/views/layout/footer.html b/gui/app/views/layout/footer.html new file mode 100644 index 000000000..cfdf74af3 --- /dev/null +++ b/gui/app/views/layout/footer.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/gui/app/views/layout/header.html b/gui/app/views/layout/header.html new file mode 100644 index 000000000..033322a62 --- /dev/null +++ b/gui/app/views/layout/header.html @@ -0,0 +1,43 @@ +
+ +
+ + + diff --git a/gui/app/views/layout/sideNav.html b/gui/app/views/layout/sideNav.html new file mode 100644 index 000000000..42dcbbc6e --- /dev/null +++ b/gui/app/views/layout/sideNav.html @@ -0,0 +1,141 @@ + + + + diff --git a/gui/app/views/layout/sideNav2.html b/gui/app/views/layout/sideNav2.html new file mode 100644 index 000000000..104a9c6cf --- /dev/null +++ b/gui/app/views/layout/sideNav2.html @@ -0,0 +1,108 @@ + + + diff --git a/gui/app/views/main.html b/gui/app/views/main.html new file mode 100644 index 000000000..d5f7a3af3 --- /dev/null +++ b/gui/app/views/main.html @@ -0,0 +1,174 @@ +
+
+
+
+ + +
+ +
+
    +
  1. + Project +
  2. +
  3. + Task +
  4. + +
  5. + Reporting +
  6. + +
+
+ + +
+ + + + + + + + + +
+ + + + diff --git a/gui/app/views/main2.html b/gui/app/views/main2.html new file mode 100644 index 000000000..661d604c6 --- /dev/null +++ b/gui/app/views/main2.html @@ -0,0 +1,174 @@ +
+
+
+
+ + +
+ +
+
    +
  1. + Project +
  2. +
  3. + Task +
  4. + +
  5. + Reporting +
  6. + +
+
+ + +
+ + + + + + + + + +
+ + + + diff --git a/gui/app/views/modal/chooseContainer.html b/gui/app/views/modal/chooseContainer.html new file mode 100644 index 000000000..4b857b22f --- /dev/null +++ b/gui/app/views/modal/chooseContainer.html @@ -0,0 +1,15 @@ +

Choose Containers

+
+ + + + \ No newline at end of file diff --git a/gui/app/views/modal/deleteConfirm.html b/gui/app/views/modal/deleteConfirm.html new file mode 100644 index 000000000..1659b884b --- /dev/null +++ b/gui/app/views/modal/deleteConfirm.html @@ -0,0 +1,19 @@ +
Confirm delete {{deleteName}} ?
+ +
+ + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/gui/app/views/modal/environmentDialog.html b/gui/app/views/modal/environmentDialog.html new file mode 100644 index 000000000..a5b88d240 --- /dev/null +++ b/gui/app/views/modal/environmentDialog.html @@ -0,0 +1,330 @@ + + +
+
+

Environment Name

+ + +
+ +
+
+ + +
+
+

{{name}} -- Openrc + + +

+ +
+ + +
+ +
+
+
+ +

+ You have already set up the openrc parameters +

+
+
+ + {{key}} : + {{value}} + +
+ +
+
+ +
+ +
+
+ + + + +
+ + + +
+ + + +
+
+ + + +
+ +
+
+
+ + + + +
+
+
+ + + +
+ + +
+ +
+
+
+ +

{{name}} -- Image + + + + +

+ + +
+ + + + + + + + + +
+

Current Images

+ +
+ + + + + + + + + + + + + + + + + + +
namesizestatustime
{{image.name}}{{image.size/1024}} mb{{image.status}}{{image.time}}
+
+ + +
+ + +
+
+ +
+
+
+ + +

{{name}} -- Pod File +
+ + + + +
+ +

+ +
+ + + + +
+ +
+

Current Pod Configuration

+ + + + + + + + + + + + + + + + + + + + + + + + +
ipnamepasswordroleuser
{{pod.ip}}{{pod.name}}{{pod.password}}{{pod.role}}{{pod.user}}
no data
+
+ + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ +

{{name}} -- Container +
+ + + +
+ +

+ + +
+ + + + + + +
+ +
+

Current Contain

+ + + + + + + + + + + + + + + + + + +
namestatustime
{{con.name}}{{con.status}}{{con.time}}
+
+ + + + + + + + + + +
+ + +
+ +
+ + + + + + +
+ + + diff --git a/gui/app/views/modal/projectCreate.html b/gui/app/views/modal/projectCreate.html new file mode 100644 index 000000000..74839e798 --- /dev/null +++ b/gui/app/views/modal/projectCreate.html @@ -0,0 +1,21 @@ +
+ +

Enter Project Name

+ + +
+ +
+ + + +
+ + + diff --git a/gui/app/views/modal/suiteName.html b/gui/app/views/modal/suiteName.html new file mode 100644 index 000000000..981d24210 --- /dev/null +++ b/gui/app/views/modal/suiteName.html @@ -0,0 +1,18 @@ +

Enter Suite Name

+
You have choose: +
{{selected}}
+
+ + +
+ +
+ + + diff --git a/gui/app/views/modal/taskCreate.html b/gui/app/views/modal/taskCreate.html new file mode 100644 index 000000000..e7812cf2b --- /dev/null +++ b/gui/app/views/modal/taskCreate.html @@ -0,0 +1,134 @@ + +

Create Task

+
+
+
Name
+ +
+
+ +
+
+
+
Choose Environment : {{displayEnvName}}
+ +
+
+
+
+
{{env.name}}
+ + + + +
+ +
+
+ +
+ +
+
+
+
Source of Content
+ + + + +
+
+
Choose Source: {{selectCase}}
+ + +
+
+ +
+
+
+
+
{{test.Name}}
+
{{test.Description}}
+ + + +
+ +
+
+ +
+
+ +
+
+
+
{{suite}}
+ + + + +
+ +
+
+ +
+
+
+ +
+ + + +
+ + + + +
+
+ +
+ + +
+ + + diff --git a/gui/app/views/podupload.html b/gui/app/views/podupload.html new file mode 100644 index 000000000..99e83aca2 --- /dev/null +++ b/gui/app/views/podupload.html @@ -0,0 +1,136 @@ + + +
+
+
+ + + +

{{name}} -- Pod File + +

+ + +
+ + + + + + +
+ +
+

No Pod Configuration

+
+

Current Pod Configuration

+ + + + + + + + + + + + + + + + + + + + +
ipnamepasswordroleuser
{{pod.ip}}{{pod.name}}{{pod.password}}{{pod.role}}{{pod.user}}
+
+
+ + + + + + + + + + +
+ + +
+ +
+ + + diff --git a/gui/app/views/projectList.html b/gui/app/views/projectList.html new file mode 100644 index 000000000..ea6e63d6b --- /dev/null +++ b/gui/app/views/projectList.html @@ -0,0 +1,57 @@ +
+ +

Projects + +

+ +
+ + + +
+
+
Name
+
Action
+ +
+ +
+
+ +
+ + +
+ + +
+
+ +
+ +
+
+ +
+ +
+ + +
+ + + + + diff --git a/gui/app/views/projectdetail.html b/gui/app/views/projectdetail.html new file mode 100644 index 000000000..ff61c5fed --- /dev/null +++ b/gui/app/views/projectdetail.html @@ -0,0 +1,97 @@ +
+ + +

Project -- Task + +

+ +
+ +
+ +

{{projectData.name}}

+
{{projectData.time}}
+
+

Tasks + +

+
No task in this project
+ + + + + + + + + + + + + + + +
Name StatusAction
{{task.name}} +
+
+
+
+
+
+
+
+
+ +
+ + +
+ + +
+ + + +
+
+ +
+ +
+ + + + + + + + diff --git a/gui/app/views/report.html b/gui/app/views/report.html new file mode 100644 index 000000000..78ac6a0c9 --- /dev/null +++ b/gui/app/views/report.html @@ -0,0 +1,56 @@ +
+ +

Yardstick Report

+
+
+ +
Task ID : {{result.result.task_id}}
+
Criteria : + {{result.result.criteria}} + {{result.result.criteria}} +
+
+ Information + + + + + + + + + + + + + + +
#keyvalue
{{$index}}{{key}}{{value}}
+
+ + Test Cases + + + + + + + + + + + + + + + + + +
#keyvaluegrafana
{{$index}}{{key}}{{value.criteria}}
+ +
+
+ + + + diff --git a/gui/app/views/suite.html b/gui/app/views/suite.html new file mode 100644 index 000000000..8e1348333 --- /dev/null +++ b/gui/app/views/suite.html @@ -0,0 +1,149 @@ +
+ + + +
+ Test Suites + + + +
+ + +
+
+
Name
+
Operate
+ +
+ + +
+
+ +
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + + +
+ + + + +
+ + + + diff --git a/gui/app/views/suitedetail.html b/gui/app/views/suitedetail.html new file mode 100644 index 000000000..6122f6560 --- /dev/null +++ b/gui/app/views/suitedetail.html @@ -0,0 +1,110 @@ +
+ +
+ + +

Detail

+
+ + + + + + + + + +
+ + + + +
+ + + + diff --git a/gui/app/views/taskList.html b/gui/app/views/taskList.html new file mode 100644 index 000000000..159fed5c9 --- /dev/null +++ b/gui/app/views/taskList.html @@ -0,0 +1,62 @@ +
+ +

Detail

+
+
+
+

{{taskDetailData.name}}

+
{{taskDetailData.time}}
+
+
+
+
+ +
+
+
+
+
+ + + +
+ +
Environment : {{displayEnv.name}}
+
Name : {{taskDetailData.case_name}}
+ + +
+ +
+
+ + + diff --git a/gui/app/views/taskmodify.html b/gui/app/views/taskmodify.html new file mode 100644 index 000000000..a4593f745 --- /dev/null +++ b/gui/app/views/taskmodify.html @@ -0,0 +1,162 @@ +
+ +

Modify

+ +
+ +
+
Name
+ + +
+
+ +
+
+
+
Choose Environment : {{envName}}
+ +
+
+
+
+
{{env.name}}
+ + + + +
+ +
+
+ +
+ +
+
+
+ + +
Test Case
+
Test Suite
+ +
+ + + + +
+
+
Source of Content
+ + + + +
+ +
+
Choose Source : {{selectCase}}
+ + +
+
+ +
+
+
+
+
{{test.Name}}
+
{{test.Description}}
+ + + +
+ +
+
+ +
+
+ +
+
+
+
{{suite}}
+ + + + +
+ +
+
+ +
+
+
+ +
+ + + +
+
+ + + + +
+
+ + +
+ + + + diff --git a/gui/app/views/testcasechoose.html b/gui/app/views/testcasechoose.html new file mode 100644 index 000000000..12bdb834f --- /dev/null +++ b/gui/app/views/testcasechoose.html @@ -0,0 +1,48 @@ +
+ +
+ Test case list + + + +
You have choose : +
{{selected}}
+
+ + +
+
+ + +
{{test.Name}}
+
{{test.Description}}
+ +
+
+
+
+ +
+ + + +
+ + + diff --git a/gui/app/views/testcasedetail.html b/gui/app/views/testcasedetail.html new file mode 100644 index 000000000..43a51537f --- /dev/null +++ b/gui/app/views/testcasedetail.html @@ -0,0 +1,110 @@ +
+ +
+ + +

Detail

+
+ + + + + + + + + +
+ + + + +
+ + + + diff --git a/gui/app/views/testcaselist.html b/gui/app/views/testcaselist.html new file mode 100644 index 000000000..3e8cfccf9 --- /dev/null +++ b/gui/app/views/testcaselist.html @@ -0,0 +1,150 @@ +
+ + + +
+ Test Cases + + + +
+ + +
+
+
Name
+
Operate
+ +
+ +
+
+ +
{{test.Description}}
+
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + +
+ + + + +
+ + + + diff --git a/gui/app/views/uploadImage.html b/gui/app/views/uploadImage.html new file mode 100644 index 000000000..17ccfdb8b --- /dev/null +++ b/gui/app/views/uploadImage.html @@ -0,0 +1,145 @@ + + +
+
+
+ +

{{baseElementInfo.name}} -- Image + +

+ + +
+ + + + + +
+

Current Images

+ +
+ + + + + + + + + + + + + + + + + + +
namesizestatustime
{{image.name}}{{image.size/1024}} MB{{image.status}}{{image.time}}
+
+ + + + + + + + + +
+ + +
+ +
+ + + diff --git a/gui/bower.json b/gui/bower.json new file mode 100644 index 000000000..6da3bee3c --- /dev/null +++ b/gui/bower.json @@ -0,0 +1,45 @@ +{ + "name": "yard-stick-gui2", + "version": "0.0.0", + "dependencies": { + "angular": "^1.4.0", + "bootstrap": "^3.2.0", + "angular-strap": "^2.3.12", + "angular-ui-router": "^1.0.3", + "angular-animate": "^1.6.4", + "angular-breadcrumb": "^0.5.0", + "angular-wizard": "^0.10.0", + "angular-resource": "^1.6.4", + "ng-file-upload": "^12.2.13", + "AngularJS-Toaster": "angularjs-toaster#^2.1.0", + "ng-dialog": "^1.3.0", + "angularUtils-pagination": "angular-utils-pagination#^0.11.1", + "components-font-awesome": "^4.7.0", + "ngstorage": "^0.3.11", + "v-accordion": "^1.6.0", + "angular-loading": "^0.1.4", + "angular-bootstrap": "^2.5.0", + "angular-sanitize": "^1.6.5" + }, + "devDependencies": { + "angular-mocks": "^1.4.0" + }, + "appPath": "app", + "moduleName": "yardStickGui2App", + "overrides": { + "bootstrap": { + "main": [ + "less/bootstrap.less", + "dist/css/bootstrap.css", + "dist/js/bootstrap.js" + ] + }, + "angular-loading": { + "main": [ + "angular-loading.css", + "angular-loading.js", + "../spin.js/spin.js" + ] + } + } +} diff --git a/gui/gui.sh b/gui/gui.sh new file mode 100755 index 000000000..12a14923e --- /dev/null +++ b/gui/gui.sh @@ -0,0 +1,8 @@ +apt-get install -y nodejs +apt-get install -y npm +ln -s /usr/bin/nodejs /usr/bin/node +npm install +npm install -g grunt +npm install -g bower +bower install --force --allow-root +grunt build diff --git a/gui/package.json b/gui/package.json new file mode 100644 index 000000000..b85c75469 --- /dev/null +++ b/gui/package.json @@ -0,0 +1,43 @@ +{ + "name": "yardstickgui2", + "private": true, + "devDependencies": { + "autoprefixer-core": "^5.2.1", + "grunt": "^0.4.5", + "grunt-angular-templates": "^0.5.7", + "grunt-concurrent": "^1.0.0", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-connect": "^0.9.0", + "grunt-contrib-copy": "^0.7.0", + "grunt-contrib-cssmin": "^0.12.0", + "grunt-contrib-htmlmin": "^0.4.0", + "grunt-contrib-imagemin": "^1.0.0", + "grunt-contrib-jshint": "^0.11.0", + "grunt-contrib-uglify": "^0.7.0", + "grunt-contrib-watch": "^0.6.1", + "grunt-filerev": "^2.1.2", + "grunt-google-cdn": "^0.4.3", + "grunt-jscs": "^1.8.0", + "grunt-newer": "^1.1.0", + "grunt-ng-annotate": "^0.9.2", + "grunt-postcss": "^0.5.5", + "grunt-svgmin": "^2.0.0", + "grunt-usemin": "^3.0.0", + "grunt-wiredep": "^2.0.0", + "jasmine-core": "^2.6.2", + "jit-grunt": "^0.9.1", + "jshint-stylish": "^1.0.0", + "karma": "^1.7.0", + "karma-jasmine": "^1.1.0", + "karma-phantomjs-launcher": "^1.0.4", + "phantomjs-prebuilt": "^2.1.14", + "time-grunt": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "karma start test\\karma.conf.js" + } +} diff --git a/gui/test/.jshintrc b/gui/test/.jshintrc new file mode 100644 index 000000000..b2ce4eff4 --- /dev/null +++ b/gui/test/.jshintrc @@ -0,0 +1,18 @@ +{ + "bitwise": true, + "browser": true, + "curly": true, + "eqeqeq": true, + "esnext": true, + "jasmine": true, + "latedef": true, + "noarg": true, + "node": true, + "strict": true, + "undef": true, + "unused": true, + "globals": { + "angular": false, + "inject": false + } +} diff --git a/gui/test/karma.conf.js b/gui/test/karma.conf.js new file mode 100644 index 000000000..a9ab3a824 --- /dev/null +++ b/gui/test/karma.conf.js @@ -0,0 +1,93 @@ +// Karma configuration +// Generated on 2017-05-31 + +module.exports = function(config) { + 'use strict'; + + config.set({ + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // base path, that will be used to resolve files and exclude + basePath: '../', + + // testing framework to use (jasmine/mocha/qunit/...) + // as well as any additional frameworks (requirejs/chai/sinon/...) + frameworks: [ + 'jasmine' + ], + + // list of files / patterns to load in the browser + files: [ + // bower:js + 'bower_components/jquery/dist/jquery.js', + 'bower_components/angular/angular.js', + 'bower_components/bootstrap/dist/js/bootstrap.js', + 'bower_components/angular-strap/dist/angular-strap.js', + 'bower_components/angular-strap/dist/angular-strap.tpl.js', + 'bower_components/angular-ui-router/release/angular-ui-router.js', + 'bower_components/angular-animate/angular-animate.js', + 'bower_components/angular-breadcrumb/release/angular-breadcrumb.js', + 'bower_components/angular-wizard/dist/angular-wizard.min.js', + 'bower_components/angular-resource/angular-resource.js', + 'bower_components/ng-file-upload/ng-file-upload.js', + 'bower_components/AngularJS-Toaster/toaster.js', + 'bower_components/ng-dialog/js/ngDialog.js', + 'bower_components/angularUtils-pagination/dirPagination.js', + 'bower_components/ngstorage/ngStorage.js', + 'bower_components/v-accordion/dist/v-accordion.js', + 'bower_components/spin.js/spin.js', + 'bower_components/angular-loading/angular-loading.js', + 'bower_components/spin.js/spin.js', + 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'bower_components/angular-sanitize/angular-sanitize.js', + 'bower_components/angular-mocks/angular-mocks.js', + // endbower + 'app/scripts/**/*.js', + 'test/mock/**/*.js', + 'test/spec/**/*.js' + ], + + // list of files / patterns to exclude + exclude: [ + ], + + // web server port + port: 8080, + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: [ + 'PhantomJS' + ], + + // Which plugins to enable + plugins: [ + 'karma-phantomjs-launcher', + 'karma-jasmine' + ], + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false, + + colors: true, + + // level of logging + // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG + logLevel: config.LOG_INFO, + + // Uncomment the following lines if you are using grunt's server to run the tests + // proxies: { + // '/': 'http://localhost:9000/' + // }, + // URL root prevent conflicts with the site root + // urlRoot: '_karma_' + }); +}; diff --git a/gui/test/spec/controllers/main.js b/gui/test/spec/controllers/main.js new file mode 100644 index 000000000..27e0a5ad3 --- /dev/null +++ b/gui/test/spec/controllers/main.js @@ -0,0 +1,23 @@ +'use strict'; + +describe('Controller: MainCtrl', function () { + + // load the controller's module + beforeEach(module('yardStickGui2App')); + + var MainCtrl, + scope; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + MainCtrl = $controller('MainCtrl', { + $scope: scope + // place here mocked dependencies + }); + })); + + it('should attach a list of awesomeThings to the scope', function () { + expect(MainCtrl.awesomeThings.length).toBe(3); + }); +}); diff --git a/install.sh b/install.sh index ad14b8e0b..e82ae0233 100755 --- a/install.sh +++ b/install.sh @@ -86,7 +86,10 @@ easy_install -U pip pip install -r requirements.txt pip install -e . -/bin/bash "$(pwd)/api/api-prepare.sh" +/bin/bash "${PWD}/docker/uwsgi.sh" +/bin/bash "${PWD}/docker/nginx.sh" +cd "${PWD}/gui" && /bin/bash gui.sh +mv dist /etc/nginx/yardstick/gui service nginx restart uwsgi -i /etc/yardstick/yardstick.ini -- 2.16.6