From 6304df2eb8fef653456d47f5d0112a9fd8baeb34 Mon Sep 17 00:00:00 2001 From: Vitaly Kramskikh Date: Wed, 16 Jul 2014 20:15:58 +0400 Subject: [PATCH] React.js integration Related to blueprint backbone-to-react Change-Id: Idd801b607fad77c130d528b7fa9c90a5cb19edcd --- nailgun/Gruntfile.js | 111 +- nailgun/bower.json | 5 + nailgun/npm-shrinkwrap.json | 944 ++++++++++++------ nailgun/package.json | 2 + nailgun/static/index.html | 3 + nailgun/static/js/app.js | 68 +- nailgun/static/js/component_mixins.jsx | 85 ++ nailgun/static/js/libs/custom/jsx.js | 68 ++ nailgun/static/js/main.js | 16 +- nailgun/static/js/utils.js | 27 +- nailgun/static/js/views/cluster_page.js | 26 +- .../js/views/cluster_page_tabs/actions_tab.js | 2 +- .../cluster_page_tabs/healthcheck_tab.js | 2 +- .../js/views/cluster_page_tabs/network_tab.js | 2 +- .../nodes_tab_screens/node_list_screen.js | 2 +- .../views/cluster_page_tabs/settings_tab.js | 2 +- nailgun/static/js/views/clusters_page.js | 182 ---- nailgun/static/js/views/clusters_page.jsx | 196 ++++ nailgun/static/js/views/common.js | 275 +---- .../js/views/{dialogs.js => dialogs.jsx} | 121 ++- nailgun/static/js/views/layout.jsx | 299 ++++++ nailgun/static/js/views/login_page.js | 6 +- nailgun/static/js/views/notifications_page.js | 2 +- nailgun/static/js/views/releases_page.js | 2 +- nailgun/static/js/views/wizard.js | 2 +- .../static/templates/clusters/cluster.html | 29 - nailgun/static/templates/clusters/new.html | 4 - nailgun/static/templates/clusters/page.html | 2 - .../templates/clusters/register_trial.html | 8 - .../static/templates/common/breadcrumb.html | 15 - nailgun/static/templates/common/footer.html | 21 - nailgun/static/templates/common/navbar.html | 24 - .../static/templates/common/nodes_stats.html | 6 - .../templates/common/notifications.html | 6 - .../common/notifications_popover.html | 22 - nailgun/ui_tests/bind-polyfill.js | 35 + nailgun/ui_tests/helpers.js | 4 + 37 files changed, 1616 insertions(+), 1010 deletions(-) create mode 100644 nailgun/static/js/component_mixins.jsx create mode 100644 nailgun/static/js/libs/custom/jsx.js delete mode 100644 nailgun/static/js/views/clusters_page.js create mode 100644 nailgun/static/js/views/clusters_page.jsx rename nailgun/static/js/views/{dialogs.js => dialogs.jsx} (81%) create mode 100644 nailgun/static/js/views/layout.jsx delete mode 100644 nailgun/static/templates/clusters/cluster.html delete mode 100644 nailgun/static/templates/clusters/new.html delete mode 100644 nailgun/static/templates/clusters/page.html delete mode 100644 nailgun/static/templates/clusters/register_trial.html delete mode 100644 nailgun/static/templates/common/breadcrumb.html delete mode 100644 nailgun/static/templates/common/footer.html delete mode 100644 nailgun/static/templates/common/navbar.html delete mode 100644 nailgun/static/templates/common/nodes_stats.html delete mode 100644 nailgun/static/templates/common/notifications.html delete mode 100644 nailgun/static/templates/common/notifications_popover.html create mode 100644 nailgun/ui_tests/bind-polyfill.js diff --git a/nailgun/Gruntfile.js b/nailgun/Gruntfile.js index 65971b0d1a..e0762583db 100644 --- a/nailgun/Gruntfile.js +++ b/nailgun/Gruntfile.js @@ -16,14 +16,17 @@ module.exports = function(grunt) { var pkg = grunt.file.readJSON('package.json'); var staticDir = grunt.option('static-dir') || '/tmp/static_compressed'; + var staticBuildPreparationDir = staticDir + '/_prepare_build'; + var staticBuildDir = staticDir + '/_build'; + grunt.initConfig({ pkg: pkg, requirejs: { compile: { options: { baseUrl: '.', - appDir: 'static', - dir: staticDir, + appDir: staticBuildPreparationDir + '/static', + dir: staticBuildDir, mainConfigFile: 'static/js/main.js', waitSeconds: 60, optimize: 'uglify2', @@ -33,7 +36,8 @@ module.exports = function(grunt) { }, map: { '*': { - 'css': 'require-css' + 'css': 'require-css', + 'JSXTransformer': 'empty:' } }, modules: [ @@ -68,7 +72,7 @@ module.exports = function(grunt) { less: { all: { src: 'static/css/styles.less', - dest: 'static/css/styles.css', + dest: staticBuildPreparationDir + '/static/css/styles.css', } }, bower: { @@ -88,21 +92,94 @@ module.exports = function(grunt) { } } }, + react: { + compile: { + files: [ + { + expand: true, + src: [staticBuildPreparationDir + '/static/**/*.jsx'], + ext: '.js' + } + ] + } + }, + copy: { + prepare_build: { + files: [ + { + expand: true, + src: [ + 'static/**', + '!**/*.less', + '!**/*.js', + '!**/*.jsx', + '!**/*.jison' + ], + dest: staticBuildPreparationDir + '/' + } + ] + }, + preprocess_js: { + files: [ + { + expand: true, + src: [ + 'static/**/*.js', + 'static/**/*.jsx', + '!**/JSXTransformer.js' + ], + dest: staticBuildPreparationDir + '/' + } + ], + options: { + process: function (content, path) { + content = content.replace(/jsx!/g, ''); + if (/\.jsx$/.test(path)) { + content = '/** @jsx React.DOM */\n' + content; + } + return content; + } + } + }, + finalize_build: { + files: [ + { + expand: true, + cwd: staticBuildDir, + src: ['**'], + dest: staticDir + } + ], + options: { + force: true + } + } + }, clean: { trim: { expand: true, - cwd: staticDir, + cwd: staticBuildDir, src: [ '**/*.js', '!js/main.js', '!js/libs/bower/requirejs/js/require.js', '**/*.css', - '**/*.less', '!css/styles.css', 'templates', 'i18n' ] }, + jsx: { + expand: true, + cwd: staticBuildPreparationDir, + src: ['**/*.jsx'] + }, + prepare_build: { + src: [staticDir] + }, + finalize_build: { + src: [staticBuildDir, staticBuildPreparationDir] + }, options: { force: true } @@ -110,7 +187,7 @@ module.exports = function(grunt) { cleanempty: { trim: { expand: true, - cwd: staticDir, + cwd: staticBuildDir, src: ['**'] }, options: { @@ -121,7 +198,7 @@ module.exports = function(grunt) { replace: { sha: { src: 'static/index.html', - dest: staticDir + '/', + dest: staticBuildDir + '/', replacements: [{ from: '__COMMIT_SHA__', to: function() { @@ -150,8 +227,22 @@ module.exports = function(grunt) { .filter(function(npmTaskName) { return npmTaskName.indexOf('grunt-') === 0; }) .forEach(grunt.loadNpmTasks.bind(grunt)); - grunt.registerTask('trimstatic', ['clean', 'cleanempty']); - grunt.registerTask('build', ['bower', 'less', 'requirejs', 'trimstatic', 'revision', 'replace']); + grunt.registerTask('build', [ + 'bower', + 'clean:prepare_build', + 'copy:prepare_build', + 'copy:preprocess_js', + 'less', + 'react', + 'clean:jsx', + 'requirejs', + 'clean:trim', + 'cleanempty:trim', + 'revision', + 'replace', + 'copy:finalize_build', + 'clean:finalize_build' + ]); grunt.registerTask('default', ['build']); grunt.task.loadTasks('grunt'); }; diff --git a/nailgun/bower.json b/nailgun/bower.json index bb6692caef..35b7dd40e5 100644 --- a/nailgun/bower.json +++ b/nailgun/bower.json @@ -2,6 +2,8 @@ "name": "fuel-web", "dependencies": { "jquery": "1.9.1", + "react": "0.11.1", + "react.backbone": "0.4.0", "requirejs": "2.1.9", "requirejs-text": "2.0.10", "require-css": "0.1.0", @@ -17,6 +19,9 @@ "jquery": { "js": "jquery.js" }, + "react": { + "js": ["JSXTransformer.js", "react-with-addons.js"] + }, "requirejs": { "js": "require.js" }, diff --git a/nailgun/npm-shrinkwrap.json b/nailgun/npm-shrinkwrap.json index 4409b3b2ae..62af6147c6 100644 --- a/nailgun/npm-shrinkwrap.json +++ b/nailgun/npm-shrinkwrap.json @@ -176,10 +176,10 @@ }, "grunt-bower-task": { "version": "0.4.0", - "from": "grunt-bower-task@", + "from": "grunt-bower-task@~0.4.0", "dependencies": { "bower": { - "version": "1.3.8", + "version": "1.3.9", "from": "bower@~1.3.0", "dependencies": { "abbrev": { @@ -188,7 +188,7 @@ }, "archy": { "version": "0.0.2", - "from": "archy@~0.0.2" + "from": "archy@0.0.2" }, "bower-config": { "version": "0.5.2", @@ -227,7 +227,7 @@ "from": "bower-json@~0.4.0", "dependencies": { "deep-extend": { - "version": "0.2.10", + "version": "0.2.11", "from": "deep-extend@~0.2.5" }, "graceful-fs": { @@ -250,7 +250,7 @@ "dependencies": { "async": { "version": "0.2.10", - "from": "async@~0.2.6" + "from": "async@~0.2.8" }, "graceful-fs": { "version": "2.0.3", @@ -382,7 +382,7 @@ "dependencies": { "esprima": { "version": "1.0.4", - "from": "esprima@~ 1.0.2" + "from": "esprima@~1.0.4" } } }, @@ -393,20 +393,40 @@ } }, "chalk": { - "version": "0.4.0", - "from": "chalk@~0.4.0", + "version": "0.5.1", + "from": "chalk@~0.5.0", "dependencies": { - "has-color": { - "version": "0.1.7", - "from": "has-color@~0.1.0" - }, "ansi-styles": { - "version": "1.0.0", - "from": "ansi-styles@~1.0.0" + "version": "1.1.0", + "from": "ansi-styles@^1.1.0" + }, + "escape-string-regexp": { + "version": "1.0.1", + "from": "escape-string-regexp@^1.0.0" + }, + "has-ansi": { + "version": "0.1.0", + "from": "has-ansi@^0.1.0", + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "from": "ansi-regex@^0.2.0" + } + } }, "strip-ansi": { - "version": "0.1.1", - "from": "strip-ansi@~0.1.0" + "version": "0.3.0", + "from": "strip-ansi@^0.3.0", + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "from": "ansi-regex@^0.2.0" + } + } + }, + "supports-color": { + "version": "0.2.0", + "from": "supports-color@^0.2.0" } } }, @@ -415,8 +435,8 @@ "from": "chmodr@~0.1.0" }, "decompress-zip": { - "version": "0.0.8", - "from": "decompress-zip@~0.0.6", + "version": "0.0.6", + "from": "decompress-zip@0.0.6", "dependencies": { "mkpath": { "version": "0.1.0", @@ -453,7 +473,7 @@ } }, "readable-stream": { - "version": "1.1.13-1", + "version": "1.1.13", "from": "readable-stream@~1.1.8", "dependencies": { "core-util-is": { @@ -465,7 +485,7 @@ "from": "isarray@0.0.1" }, "string_decoder": { - "version": "0.10.25-1", + "version": "0.10.31", "from": "string_decoder@~0.10.x" }, "inherits": { @@ -481,41 +501,37 @@ } }, "fstream": { - "version": "0.1.28", + "version": "0.1.31", "from": "fstream@~0.1.22", "dependencies": { "inherits": { "version": "2.0.1", "from": "inherits@~2.0.0" - }, - "mkdirp": { - "version": "0.3.5", - "from": "mkdirp@0.3" } } }, "fstream-ignore": { - "version": "0.0.10", - "from": "fstream-ignore@~0.0.6", + "version": "0.0.6", + "from": "fstream-ignore@0.0.6", "dependencies": { - "inherits": { - "version": "2.0.1", - "from": "inherits@2" - }, "minimatch": { - "version": "0.3.0", - "from": "minimatch@^0.3.0", + "version": "0.2.14", + "from": "minimatch@~0.2.0", "dependencies": { "sigmund": { "version": "1.0.0", "from": "sigmund@~1.0.0" } } + }, + "inherits": { + "version": "1.0.0", + "from": "inherits@~1.0.0" } } }, "glob": { - "version": "4.0.4", + "version": "4.0.5", "from": "glob@~4.0.2", "dependencies": { "inherits": { @@ -523,8 +539,8 @@ "from": "inherits@2" }, "minimatch": { - "version": "0.3.0", - "from": "minimatch@^0.3.0", + "version": "1.0.0", + "from": "minimatch@^1.0.0", "dependencies": { "sigmund": { "version": "1.0.0", @@ -540,7 +556,7 @@ }, "graceful-fs": { "version": "3.0.2", - "from": "graceful-fs@~3.0.1" + "from": "graceful-fs@^3.0.2" }, "handlebars": { "version": "1.3.0", @@ -565,7 +581,7 @@ "from": "async@~0.2.6" }, "source-map": { - "version": "0.1.37", + "version": "0.1.39", "from": "source-map@~0.1.7", "dependencies": { "amdefine": { @@ -609,7 +625,7 @@ } }, "memoizee": { - "version": "0.3.5", + "version": "0.3.7", "from": "memoizee@0.3.x", "dependencies": { "event-emitter": { @@ -644,7 +660,7 @@ }, "mute-stream": { "version": "0.0.4", - "from": "mute-stream@~0.0.4" + "from": "mute-stream@0.0.4" }, "readline2": { "version": "0.1.0", @@ -653,123 +669,39 @@ "through": { "version": "2.3.4", "from": "through@~2.3.4" + }, + "chalk": { + "version": "0.4.0", + "from": "chalk@~0.4.0", + "dependencies": { + "has-color": { + "version": "0.1.7", + "from": "has-color@~0.1.0" + }, + "ansi-styles": { + "version": "1.0.0", + "from": "ansi-styles@~1.0.0" + }, + "strip-ansi": { + "version": "0.1.1", + "from": "strip-ansi@~0.1.0" + } + } } } }, "insight": { - "version": "0.3.1", - "from": "insight@~0.3.0", + "version": "0.4.3", + "from": "insight@~0.4.1", "dependencies": { - "request": { - "version": "2.27.0", - "from": "request@~2.27.0", - "dependencies": { - "qs": { - "version": "0.6.6", - "from": "qs@~0.6.0" - }, - "json-stringify-safe": { - "version": "5.0.0", - "from": "json-stringify-safe@~5.0.0" - }, - "forever-agent": { - "version": "0.5.2", - "from": "forever-agent@~0.5.0" - }, - "tunnel-agent": { - "version": "0.3.0", - "from": "tunnel-agent@~0.3.0" - }, - "http-signature": { - "version": "0.10.0", - "from": "http-signature@~0.10.0", - "dependencies": { - "assert-plus": { - "version": "0.1.2", - "from": "assert-plus@0.1.2" - }, - "asn1": { - "version": "0.1.11", - "from": "asn1@0.1.11" - }, - "ctype": { - "version": "0.5.2", - "from": "ctype@0.5.2" - } - } - }, - "hawk": { - "version": "1.0.0", - "from": "hawk@~1.0.0", - "dependencies": { - "hoek": { - "version": "0.9.1", - "from": "hoek@0.9.x" - }, - "boom": { - "version": "0.4.2", - "from": "boom@0.4.x" - }, - "cryptiles": { - "version": "0.2.2", - "from": "cryptiles@0.2.x" - }, - "sntp": { - "version": "0.2.4", - "from": "sntp@0.2.x" - } - } - }, - "aws-sign": { - "version": "0.3.0", - "from": "aws-sign@~0.3.0" - }, - "oauth-sign": { - "version": "0.3.0", - "from": "oauth-sign@~0.3.0" - }, - "cookie-jar": { - "version": "0.3.0", - "from": "cookie-jar@~0.3.0" - }, - "node-uuid": { - "version": "1.4.1", - "from": "node-uuid@~1.4.0" - }, - "mime": { - "version": "1.2.11", - "from": "mime@~1.2.9" - }, - "form-data": { - "version": "0.1.4", - "from": "form-data@~0.1.0", - "dependencies": { - "combined-stream": { - "version": "0.0.5", - "from": "combined-stream@~0.0.4", - "dependencies": { - "delayed-stream": { - "version": "0.0.5", - "from": "delayed-stream@0.0.5" - } - } - }, - "async": { - "version": "0.9.0", - "from": "async@~0.9.0" - } - } - } - } + "async": { + "version": "0.9.0", + "from": "async@^0.9.0" }, "configstore": { - "version": "0.2.3", - "from": "configstore@~0.2.1", + "version": "0.3.1", + "from": "configstore@^0.3.1", "dependencies": { - "mkdirp": { - "version": "0.3.5", - "from": "mkdirp@0.3" - }, "js-yaml": { "version": "3.0.2", "from": "js-yaml@~3.0.1", @@ -794,13 +726,9 @@ } } }, - "osenv": { - "version": "0.0.3", - "from": "osenv@0.0.3" - }, - "graceful-fs": { - "version": "2.0.3", - "from": "graceful-fs@~2.0.1" + "object-assign": { + "version": "0.3.1", + "from": "object-assign@~0.3.1" }, "uuid": { "version": "1.4.1", @@ -808,63 +736,107 @@ } } }, - "async": { - "version": "0.2.10", - "from": "async@~0.2.9" - }, "inquirer": { - "version": "0.4.1", - "from": "inquirer@~0.4.0", + "version": "0.6.0", + "from": "inquirer@^0.6.0", "dependencies": { - "lodash": { - "version": "2.4.1", - "from": "lodash@~2.4.1" - }, "cli-color": { - "version": "0.2.3", - "from": "cli-color@~0.2.2", + "version": "0.3.2", + "from": "cli-color@~0.3.2", "dependencies": { + "d": { + "version": "0.1.1", + "from": "d@~0.1.1" + }, "es5-ext": { - "version": "0.9.2", - "from": "es5-ext@~0.9.2" + "version": "0.10.4", + "from": "es5-ext@~0.10.2", + "dependencies": { + "es6-iterator": { + "version": "0.1.1", + "from": "es6-iterator@~0.1.1" + }, + "es6-symbol": { + "version": "0.1.0", + "from": "es6-symbol@0.1.x" + } + } }, "memoizee": { - "version": "0.2.6", - "from": "memoizee@~0.2.5", + "version": "0.3.7", + "from": "memoizee@0.3.x", "dependencies": { "event-emitter": { - "version": "0.2.2", - "from": "event-emitter@~0.2.2" + "version": "0.3.1", + "from": "event-emitter@~0.3.1" + }, + "lru-queue": { + "version": "0.1.0", + "from": "lru-queue@0.1.x" }, "next-tick": { - "version": "0.1.0", - "from": "next-tick@0.1.x" + "version": "0.2.2", + "from": "next-tick@~0.2.2" + } + } + }, + "timers-ext": { + "version": "0.1.0", + "from": "timers-ext@0.1.x", + "dependencies": { + "next-tick": { + "version": "0.2.2", + "from": "next-tick@~0.2.2" } } } } }, + "lodash": { + "version": "2.4.1", + "from": "lodash@~2.4.1" + }, "mute-stream": { "version": "0.0.4", "from": "mute-stream@0.0.4" }, + "readline2": { + "version": "0.1.0", + "from": "readline2@~0.1.0", + "dependencies": { + "chalk": { + "version": "0.4.0", + "from": "chalk@~0.4.0", + "dependencies": { + "has-color": { + "version": "0.1.7", + "from": "has-color@~0.1.0" + }, + "ansi-styles": { + "version": "1.0.0", + "from": "ansi-styles@~1.0.0" + }, + "strip-ansi": { + "version": "0.1.1", + "from": "strip-ansi@~0.1.0" + } + } + } + } + }, + "rx": { + "version": "2.3.9", + "from": "rx@^2.2.27" + }, "through": { "version": "2.3.4", "from": "through@~2.3.4" - }, - "readline2": { - "version": "0.1.0", - "from": "readline2@~0.1.0" } } }, - "object-assign": { - "version": "0.1.2", - "from": "object-assign@~0.1.2" - }, "lodash.debounce": { "version": "2.4.1", - "from": "lodash.debounce@~2.4.1", + "from": "lodash.debounce@^2.4.1", "dependencies": { "lodash.isfunction": { "version": "2.4.1", @@ -891,6 +863,168 @@ } } } + }, + "object-assign": { + "version": "1.0.0", + "from": "object-assign@^1.0.0" + }, + "os-name": { + "version": "1.0.0", + "from": "os-name@^1.0.0", + "dependencies": { + "minimist": { + "version": "1.1.0", + "from": "minimist@^1.1.0" + }, + "osx-release": { + "version": "1.0.0", + "from": "osx-release@^1.0.0" + } + } + }, + "request": { + "version": "2.42.0", + "from": "request@^2.40.0", + "dependencies": { + "bl": { + "version": "0.9.3", + "from": "bl@~0.9.0", + "dependencies": { + "readable-stream": { + "version": "1.0.31", + "from": "readable-stream@~1.0.26", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "from": "core-util-is@~1.0.0" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@~0.10.x" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1" + } + } + } + } + }, + "caseless": { + "version": "0.6.0", + "from": "caseless@~0.6.0" + }, + "forever-agent": { + "version": "0.5.2", + "from": "forever-agent@~0.5.0" + }, + "qs": { + "version": "1.2.2", + "from": "qs@~1.2.0" + }, + "json-stringify-safe": { + "version": "5.0.0", + "from": "json-stringify-safe@~5.0.0" + }, + "mime-types": { + "version": "1.0.2", + "from": "mime-types@~1.0.1" + }, + "node-uuid": { + "version": "1.4.1", + "from": "node-uuid@~1.4.0" + }, + "tunnel-agent": { + "version": "0.4.0", + "from": "tunnel-agent@~0.4.0" + }, + "form-data": { + "version": "0.1.4", + "from": "form-data@~0.1.0", + "dependencies": { + "combined-stream": { + "version": "0.0.5", + "from": "combined-stream@~0.0.4", + "dependencies": { + "delayed-stream": { + "version": "0.0.5", + "from": "delayed-stream@0.0.5" + } + } + }, + "mime": { + "version": "1.2.11", + "from": "mime@~1.2.9" + } + } + }, + "http-signature": { + "version": "0.10.0", + "from": "http-signature@~0.10.0", + "dependencies": { + "assert-plus": { + "version": "0.1.2", + "from": "assert-plus@0.1.2" + }, + "asn1": { + "version": "0.1.11", + "from": "asn1@0.1.11" + }, + "ctype": { + "version": "0.5.2", + "from": "ctype@0.5.2" + } + } + }, + "oauth-sign": { + "version": "0.4.0", + "from": "oauth-sign@~0.4.0" + }, + "hawk": { + "version": "1.1.1", + "from": "hawk@1.1.1", + "dependencies": { + "hoek": { + "version": "0.9.1", + "from": "hoek@0.9.x" + }, + "boom": { + "version": "0.4.2", + "from": "boom@0.4.x" + }, + "cryptiles": { + "version": "0.2.2", + "from": "cryptiles@0.2.x" + }, + "sntp": { + "version": "0.2.4", + "from": "sntp@0.2.x" + } + } + }, + "aws-sign2": { + "version": "0.5.0", + "from": "aws-sign2@~0.5.0" + }, + "stringstream": { + "version": "0.0.4", + "from": "stringstream@~0.0.4" + } + } + }, + "tough-cookie": { + "version": "0.12.1", + "from": "tough-cookie@^0.12.1", + "dependencies": { + "punycode": { + "version": "1.3.1", + "from": "punycode@>=0.2.0" + } + } } } }, @@ -903,12 +1037,12 @@ "from": "junk@~0.3.0" }, "lockfile": { - "version": "0.4.2", + "version": "0.4.3", "from": "lockfile@~0.4.2" }, "lru-cache": { "version": "2.5.0", - "from": "lru-cache@~2.5.0" + "from": "lru-cache@2" }, "mkdirp": { "version": "0.5.0", @@ -926,7 +1060,7 @@ }, "nopt": { "version": "3.0.1", - "from": "nopt@~3.0.0" + "from": "nopt@^3.0.0" }, "opn": { "version": "0.1.2", @@ -938,7 +1072,7 @@ }, "p-throttler": { "version": "0.0.1", - "from": "p-throttler@~0.0.1", + "from": "p-throttler@0.0.1", "dependencies": { "q": { "version": "0.9.7", @@ -980,7 +1114,7 @@ }, "mime": { "version": "1.2.11", - "from": "mime@~1.2.9" + "from": "mime@~1.2.11" }, "forever-agent": { "version": "0.5.2", @@ -992,10 +1126,10 @@ }, "tough-cookie": { "version": "0.12.1", - "from": "tough-cookie@>=0.12.0", + "from": "tough-cookie@^0.12.1", "dependencies": { "punycode": { - "version": "1.3.0", + "version": "1.3.1", "from": "punycode@>=0.2.0" } } @@ -1016,7 +1150,7 @@ }, "async": { "version": "0.9.0", - "from": "async@~0.9.0" + "from": "async@>= 0.1.18" } } }, @@ -1093,7 +1227,7 @@ "from": "rimraf@~2.2.0" }, "semver": { - "version": "2.3.1", + "version": "2.3.2", "from": "semver@~2.3.0" }, "shell-quote": { @@ -1132,7 +1266,7 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@~2.0.1" + "from": "inherits@2" } } }, @@ -1141,50 +1275,12 @@ "from": "tmp@0.0.23" }, "update-notifier": { - "version": "0.2.0", + "version": "0.2.1", "from": "update-notifier@~0.2.0", "dependencies": { - "chalk": { - "version": "0.5.1", - "from": "chalk@^0.5.0", - "dependencies": { - "ansi-styles": { - "version": "1.1.0", - "from": "ansi-styles@^1.1.0" - }, - "escape-string-regexp": { - "version": "1.0.1", - "from": "escape-string-regexp@^1.0.0" - }, - "has-ansi": { - "version": "0.1.0", - "from": "has-ansi@^0.1.0", - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "from": "ansi-regex@^0.2.1" - } - } - }, - "strip-ansi": { - "version": "0.3.0", - "from": "strip-ansi@^0.3.0", - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "from": "ansi-regex@^0.2.1" - } - } - }, - "supports-color": { - "version": "0.2.0", - "from": "supports-color@^0.2.0" - } - } - }, "configstore": { "version": "0.3.1", - "from": "configstore@^0.3.0", + "from": "configstore@^0.3.1", "dependencies": { "js-yaml": { "version": "3.0.2", @@ -1221,16 +1317,16 @@ } }, "latest-version": { - "version": "0.2.0", - "from": "latest-version@^0.2.0", + "version": "1.0.0", + "from": "latest-version@^1.0.0", "dependencies": { "package-json": { - "version": "0.2.0", - "from": "package-json@^0.2.0", + "version": "1.0.0", + "from": "package-json@^1.0.0", "dependencies": { "got": { - "version": "0.3.0", - "from": "got@^0.3.0", + "version": "1.2.0", + "from": "got@^1.0.1", "dependencies": { "object-assign": { "version": "0.3.1", @@ -1239,11 +1335,11 @@ } }, "registry-url": { - "version": "0.1.1", - "from": "registry-url@^0.1.0", + "version": "1.0.0", + "from": "registry-url@^1.0.0", "dependencies": { "npmconf": { - "version": "2.0.3", + "version": "2.0.8", "from": "npmconf@^2.0.1", "dependencies": { "config-chain": { @@ -1264,10 +1360,6 @@ "version": "1.2.1", "from": "ini@^1.2.0" }, - "mkdirp": { - "version": "0.3.5", - "from": "mkdirp@~0.3.3" - }, "once": { "version": "1.3.0", "from": "once@~1.3.0" @@ -1285,20 +1377,26 @@ } }, "semver-diff": { - "version": "0.1.0", - "from": "semver-diff@^0.1.0" + "version": "1.0.0", + "from": "semver-diff@^1.0.0", + "dependencies": { + "semver": { + "version": "3.0.1", + "from": "semver@^3.0.1" + } + } }, "string-length": { - "version": "0.1.2", - "from": "string-length@^0.1.2", + "version": "1.0.0", + "from": "string-length@^1.0.0", "dependencies": { "strip-ansi": { - "version": "0.2.2", - "from": "strip-ansi@^0.2.1", + "version": "2.0.0", + "from": "strip-ansi@^2.0.0", "dependencies": { "ansi-regex": { - "version": "0.1.0", - "from": "ansi-regex@^0.1.0" + "version": "1.1.0", + "from": "ansi-regex@^1.0.0" } } } @@ -1354,6 +1452,10 @@ } } }, + "grunt-contrib-copy": { + "version": "0.5.0", + "from": "grunt-contrib-copy@~0.5.0" + }, "grunt-contrib-less": { "version": "0.8.3", "from": "grunt-contrib-less@~0.8.2", @@ -1375,7 +1477,7 @@ "from": "grunt-contrib-requirejs@~0.4.1" }, "grunt-debug-task": { - "version": "0.1.4", + "version": "0.1.5", "from": "grunt-debug-task@~0.1.3", "dependencies": { "open": { @@ -1399,16 +1501,16 @@ "from": "uglify-js@1.2.5" }, "ws": { - "version": "0.4.31", + "version": "0.4.32", "from": "ws@0.4.x", "dependencies": { "commander": { - "version": "0.6.1", - "from": "commander@~0.6.1" + "version": "2.1.0", + "from": "commander@~2.1.0" }, "nan": { - "version": "0.3.2", - "from": "nan@~0.3.0" + "version": "1.0.0", + "from": "nan@~1.0.0" }, "tinycolor": { "version": "0.0.1", @@ -1464,7 +1566,7 @@ }, "qs": { "version": "0.6.6", - "from": "qs@0.6.6" + "from": "qs@~0.6.0" }, "bytes": { "version": "0.2.1", @@ -1491,7 +1593,7 @@ "from": "multiparty@2.2.0", "dependencies": { "readable-stream": { - "version": "1.1.13-1", + "version": "1.1.13", "from": "readable-stream@~1.1.9", "dependencies": { "core-util-is": { @@ -1503,7 +1605,7 @@ "from": "isarray@0.0.1" }, "string_decoder": { - "version": "0.10.25-1", + "version": "0.10.31", "from": "string_decoder@~0.10.x" }, "inherits": { @@ -1536,7 +1638,7 @@ }, "mkdirp": { "version": "0.3.5", - "from": "mkdirp@0.3.5" + "from": "mkdirp@~0.3.5" }, "cookie": { "version": "0.1.0", @@ -1576,11 +1678,11 @@ }, "async": { "version": "0.2.10", - "from": "async@~0.2.8" + "from": "async@~0.2.6" }, "glob": { "version": "3.2.11", - "from": "glob@~3.2.1", + "from": "glob@~3.2.9", "dependencies": { "inherits": { "version": "2.0.1", @@ -1611,7 +1713,7 @@ "from": "minimist@~0.0.7" }, "deep-extend": { - "version": "0.2.10", + "version": "0.2.11", "from": "deep-extend@~0.2.5" }, "ini": { @@ -1651,7 +1753,7 @@ "from": "grunt-jslint@~1.1.1", "dependencies": { "jslint": { - "version": "0.6.1", + "version": "0.6.2", "from": "jslint@>=0.5.0", "dependencies": { "exit": { @@ -1659,16 +1761,16 @@ "from": "exit@^0.1.2" }, "glob": { - "version": "4.0.4", - "from": "glob@^4.0.4", + "version": "4.0.5", + "from": "glob@~4.0.2", "dependencies": { "inherits": { "version": "2.0.1", "from": "inherits@2" }, "minimatch": { - "version": "0.3.0", - "from": "minimatch@^0.3.0", + "version": "1.0.0", + "from": "minimatch@^1.0.0", "dependencies": { "lru-cache": { "version": "2.5.0", @@ -1704,12 +1806,186 @@ } } }, + "grunt-react": { + "version": "0.9.0", + "from": "grunt-react@~0.9.0", + "dependencies": { + "react-tools": { + "version": "0.11.1", + "from": "react-tools@^0.11.0", + "dependencies": { + "commoner": { + "version": "0.9.8", + "from": "commoner@^0.9.6", + "dependencies": { + "q": { + "version": "1.0.1", + "from": "q@~1.0.1" + }, + "recast": { + "version": "0.7.1", + "from": "recast@~0.7.0", + "dependencies": { + "esprima-fb": { + "version": "5001.1.0-dev-harmony-fb", + "from": "esprima-fb@~5001.1.0-dev-harmony-fb" + }, + "source-map": { + "version": "0.1.32", + "from": "source-map@0.1.32", + "dependencies": { + "amdefine": { + "version": "0.1.0", + "from": "amdefine@>=0.0.4" + } + } + }, + "cls": { + "version": "0.1.5", + "from": "cls@~0.1.3" + }, + "ast-types": { + "version": "0.4.9", + "from": "ast-types@~0.4.7" + } + } + }, + "commander": { + "version": "2.2.0", + "from": "commander@~2.2.0" + }, + "graceful-fs": { + "version": "2.0.3", + "from": "graceful-fs@~2.0.3" + }, + "glob": { + "version": "3.2.11", + "from": "glob@~3.2.9", + "dependencies": { + "inherits": { + "version": "2.0.1", + "from": "inherits@2" + }, + "minimatch": { + "version": "0.3.0", + "from": "minimatch@0.3", + "dependencies": { + "lru-cache": { + "version": "2.5.0", + "from": "lru-cache@2" + }, + "sigmund": { + "version": "1.0.0", + "from": "sigmund@~1.0.0" + } + } + } + } + }, + "mkdirp": { + "version": "0.3.5", + "from": "mkdirp@0.3.5" + }, + "private": { + "version": "0.1.5", + "from": "private@~0.1.2" + }, + "install": { + "version": "0.1.7", + "from": "install@~0.1.7", + "dependencies": { + "whiskey": { + "version": "0.6.13", + "from": "whiskey@0.6.x", + "dependencies": { + "sprintf": { + "version": "0.1.4", + "from": "sprintf@>= 0.1.1" + }, + "async": { + "version": "0.9.0", + "from": "async@~0.9.0" + }, + "magic-templates": { + "version": "0.1.1", + "from": "magic-templates@= 0.1.1" + }, + "rimraf": { + "version": "1.0.1", + "from": "rimraf@= 1.0.1" + }, + "terminal": { + "version": "0.1.3", + "from": "terminal@= 0.1.3" + }, + "gex": { + "version": "0.0.1", + "from": "gex@= 0.0.1" + }, + "simplesets": { + "version": "1.1.6", + "from": "simplesets@= 1.1.6" + }, + "logmagic": { + "version": "0.1.4", + "from": "logmagic@= 0.1.4" + }, + "underscore": { + "version": "1.7.0", + "from": "underscore@>= 1.4.2" + } + } + } + } + }, + "iconv-lite": { + "version": "0.2.11", + "from": "iconv-lite@~0.2.11" + } + } + }, + "esprima-fb": { + "version": "4001.3001.0-dev-harmony-fb", + "from": "esprima-fb@^4001.3001.0-dev-harmony-fb" + }, + "jstransform": { + "version": "6.2.0", + "from": "jstransform@^6.0.1", + "dependencies": { + "base62": { + "version": "0.1.1", + "from": "base62@0.1.1" + }, + "esprima-fb": { + "version": "6001.1.0-dev-harmony-fb", + "from": "esprima-fb@~6001.1.0-dev-harmony-fb" + }, + "source-map": { + "version": "0.1.31", + "from": "source-map@0.1.31", + "dependencies": { + "amdefine": { + "version": "0.1.0", + "from": "amdefine@>=0.0.4" + } + } + } + } + } + } + }, + "through": { + "version": "2.3.4", + "from": "through@~2.3.4" + } + } + }, "grunt-text-replace": { "version": "0.3.12", "from": "grunt-text-replace@~0.3.12" }, "jison": { - "version": "0.4.13", + "version": "0.4.15", "from": "jison@~0.4.13", "dependencies": { "JSONSelect": { @@ -1717,20 +1993,24 @@ "from": "JSONSelect@0.4.0" }, "esprima": { - "version": "1.0.4", - "from": "esprima@1.0.x" + "version": "1.1.1", + "from": "esprima@1.1.x" }, "escodegen": { - "version": "0.0.21", - "from": "escodegen@0.0.21", + "version": "1.3.3", + "from": "escodegen@1.3.x", "dependencies": { + "esutils": { + "version": "1.0.0", + "from": "esutils@~1.0.0" + }, "estraverse": { - "version": "0.0.4", - "from": "estraverse@~0.0.4" + "version": "1.5.1", + "from": "estraverse@~1.5.0" }, "source-map": { - "version": "0.1.37", - "from": "source-map@>= 0.1.2", + "version": "0.1.39", + "from": "source-map@~0.1.33", "dependencies": { "amdefine": { "version": "0.1.0", @@ -1741,12 +2021,12 @@ } }, "jison-lex": { - "version": "0.2.1", - "from": "jison-lex@0.2.x" + "version": "0.3.4", + "from": "jison-lex@0.3.x" }, "ebnf-parser": { "version": "0.1.10", - "from": "ebnf-parser@~0.1.9" + "from": "ebnf-parser@0.1.10" }, "lex-parser": { "version": "0.1.4", @@ -1767,8 +2047,20 @@ } }, "cjson": { - "version": "0.2.1", - "from": "cjson@~0.2.1" + "version": "0.3.0", + "from": "cjson@0.3.0", + "dependencies": { + "jsonlint": { + "version": "1.6.0", + "from": "jsonlint@1.6.0", + "dependencies": { + "JSV": { + "version": "4.0.2", + "from": "JSV@>= 4.0.x" + } + } + } + } } } }, @@ -1797,35 +2089,71 @@ "from": "mime@1.2.x" }, "request": { - "version": "2.38.0", + "version": "2.42.0", "from": "request@>=2.12.0", "dependencies": { + "bl": { + "version": "0.9.3", + "from": "bl@~0.9.0", + "dependencies": { + "readable-stream": { + "version": "1.0.31", + "from": "readable-stream@~1.0.26", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "from": "core-util-is@~1.0.0" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@~0.10.x" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1" + } + } + } + } + }, + "caseless": { + "version": "0.6.0", + "from": "caseless@~0.6.0" + }, + "forever-agent": { + "version": "0.5.2", + "from": "forever-agent@~0.5.0" + }, "qs": { - "version": "0.6.6", - "from": "qs@~0.6.0" + "version": "1.2.2", + "from": "qs@~1.2.0" }, "json-stringify-safe": { "version": "5.0.0", "from": "json-stringify-safe@~5.0.0" }, "mime-types": { - "version": "1.0.1", + "version": "1.0.2", "from": "mime-types@~1.0.1" }, - "forever-agent": { - "version": "0.5.2", - "from": "forever-agent@~0.5.0" - }, "node-uuid": { "version": "1.4.1", "from": "node-uuid@~1.4.0" }, + "tunnel-agent": { + "version": "0.4.0", + "from": "tunnel-agent@~0.4.0" + }, "tough-cookie": { "version": "0.12.1", - "from": "tough-cookie@>=0.12.0", + "from": "tough-cookie@^0.12.1", "dependencies": { "punycode": { - "version": "1.3.0", + "version": "1.3.1", "from": "punycode@>=0.2.0" } } @@ -1846,14 +2174,10 @@ }, "async": { "version": "0.9.0", - "from": "async@~0.9.0" + "from": "async@>= 0.1.18" } } }, - "tunnel-agent": { - "version": "0.4.0", - "from": "tunnel-agent@~0.4.0" - }, "http-signature": { "version": "0.10.0", "from": "http-signature@~0.10.0", @@ -1873,8 +2197,8 @@ } }, "oauth-sign": { - "version": "0.3.0", - "from": "oauth-sign@~0.3.0" + "version": "0.4.0", + "from": "oauth-sign@~0.4.0" }, "hawk": { "version": "1.1.1", @@ -1910,7 +2234,7 @@ }, "mkdirp": { "version": "0.3.5", - "from": "mkdirp@~0.3.5" + "from": "mkdirp@0.3.5" }, "clean-css": { "version": "2.0.8", @@ -1923,7 +2247,7 @@ } }, "source-map": { - "version": "0.1.37", + "version": "0.1.39", "from": "source-map@0.1.x", "dependencies": { "amdefine": { @@ -1939,7 +2263,7 @@ "from": "lodash-node@~2.4.1" }, "requirejs": { - "version": "2.1.14", + "version": "2.1.15", "from": "requirejs@~2.1.9" }, "uglifyjs": { @@ -1951,8 +2275,8 @@ "from": "async@~0.2.6" }, "source-map": { - "version": "0.1.37", - "from": "source-map@~0.1.7", + "version": "0.1.39", + "from": "source-map@0.1.x", "dependencies": { "amdefine": { "version": "0.1.0", diff --git a/nailgun/package.json b/nailgun/package.json index b1ce1512dd..b5ecf97d23 100644 --- a/nailgun/package.json +++ b/nailgun/package.json @@ -10,12 +10,14 @@ "grunt-bower-task": "~0.4.0", "grunt-cleanempty": "~0.2.0", "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-copy": "~0.5.0", "grunt-contrib-less": "~0.8.2", "grunt-contrib-requirejs": "~0.4.1", "grunt-debug-task": "~0.1.3", "grunt-git-revision": "~0.0.1", "grunt-jison": "~1.2.1", "grunt-jslint": "~1.1.1", + "grunt-react": "~0.9.0", "grunt-text-replace": "~0.3.12", "jison": "~0.4.13", "jslint": "~0.2.5", diff --git a/nailgun/static/index.html b/nailgun/static/index.html index 35c1106a86..175282acd8 100644 --- a/nailgun/static/index.html +++ b/nailgun/static/index.html @@ -16,6 +16,8 @@
+ +
@@ -23,5 +25,6 @@
+ diff --git a/nailgun/static/js/app.js b/nailgun/static/js/app.js index 25d93f652b..8f481254f8 100644 --- a/nailgun/static/js/app.js +++ b/nailgun/static/js/app.js @@ -15,6 +15,9 @@ **/ define( [ + 'react', + 'utils', + 'jsx!views/layout', 'coccyx', 'js/coccyx_mixins', 'models', @@ -23,13 +26,13 @@ define( 'views/login_page', 'views/cluster_page', 'views/cluster_page_tabs/nodes_tab', - 'views/clusters_page', + 'jsx!views/clusters_page', 'views/releases_page', 'views/notifications_page', 'views/support_page', 'views/capacity_page' ], -function(Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, ClusterPage, NodesTab, ClustersPage, ReleasesPage, NotificationsPage, SupportPage, CapacityPage) { +function(React, utils, layoutComponents, Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, ClusterPage, NodesTab, ClustersPage, ReleasesPage, NotificationsPage, SupportPage, CapacityPage) { 'use strict'; var AppRouter = Backbone.Router.extend({ @@ -71,7 +74,7 @@ function(Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, C }); var version = this.version = new models.FuelVersion(); - version.fetch().done(_.bind(function() { + version.fetch().then(_.bind(function() { this.user = new models.User({authenticated: !version.get('auth_required')}); var originalSync = Backbone.sync; @@ -126,49 +129,48 @@ function(Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, C return originalSync.call(this, method, model, options); }; - this.renderLayout(); - if (version.get('auth_required')) { _.extend(keystoneClient, this.user.pick('username', 'password')); - keystoneClient.authenticate() + return keystoneClient.authenticate() .done(function() { app.user.set({authenticated: true}); - }) - .always(function() { - Backbone.history.start(); - }) - .fail(function() { - app.navigate('#login', {trigger: true}); }); - } else { - Backbone.history.start(); + } + return $.Deferred().resolve(); + }, this)).always(_.bind(function() { + this.renderLayout(); + Backbone.history.start(); + if (version.get('auth_required') && !this.user.get('authenticated')) { + app.navigate('#login', {trigger: true}); } }, this)); }, renderLayout: function() { this.content = $('#content'); - this.navbar = new commonViews.Navbar({elements: [ - {label: 'environments', url: '#clusters'}, - {label: 'releases', url:'#releases'}, - {label: 'support', url:'#support'} - ]}); - this.content.before(this.navbar.render().el); - this.breadcrumbs = new commonViews.Breadcrumbs(); - this.content.before(this.breadcrumbs.render().el); - this.footer = new commonViews.Footer(); - $('#footer').html(this.footer.render().el); + this.navbar = React.renderComponent(new layoutComponents.Navbar({ + elements: [ + {label: 'environments', url: '#clusters'}, + {label: 'releases', url:'#releases'}, + {label: 'support', url:'#support'} + ], + user: this.user, + version: this.version, + statistics: new models.NodesStatistics(), + notifications: new models.Notifications() + }), $('#navbar')[0]); + this.breadcrumbs = React.renderComponent(new layoutComponents.Breadcrumbs(), $('#breadcrumbs')[0]); + this.footer = React.renderComponent(new layoutComponents.Footer({version: this.version}), $('#footer')[0]); this.content.find('.loading').addClass('layout-loaded'); }, setPage: function(NewPage, options) { if (this.page) { - this.page.tearDown(); + utils.universalUnmount(this.page); } - this.page = new NewPage(options); - this.page.updateNavbar(); - this.page.updateBreadcrumbs(); - this.page.updateTitle(); - this.content.html(this.page.render().el); - + this.page = utils.universalMount(new NewPage(options), this.content); + this.navbar.setActive(_.result(this.page, 'navbarActiveElement')); + this.breadcrumbs.setPath(_.result(this.page, 'breadcrumbsPath')); + var newTitle = _.result(this.page, 'title'); + document.title = $.t('common.title') + (newTitle ? ' - ' + newTitle : ''); }, // routes login: function() { @@ -259,7 +261,7 @@ function(Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, C cluster.get('nodes').deferred = nodes.deferred; cluster.set('tasks', new models.Tasks(tasks.where({cluster: cluster.id}))); }, this); - this.setPage(ClustersPage, {collection: clusters}); + this.setPage(ClustersPage, {clusters: clusters}); }, this)); }, listReleases: function() { @@ -276,7 +278,7 @@ function(Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, C }, this)); }, showNotifications: function() { - this.setPage(NotificationsPage, {notifications: app.navbar.notifications}); + this.setPage(NotificationsPage, {notifications: app.navbar.props.notifications}); }, showSupportPage: function() { this.setPage(SupportPage); diff --git a/nailgun/static/js/component_mixins.jsx b/nailgun/static/js/component_mixins.jsx new file mode 100644 index 0000000000..26277bbe8a --- /dev/null +++ b/nailgun/static/js/component_mixins.jsx @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Mirantis, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. +**/ +define(['jquery', 'underscore', 'react'], function($, _, React) { + 'use strict'; + + return { + pollingMixin: function(updateInterval) { + updateInterval = updateInterval * 1000; + return { + scheduleDataFetch: function() { + var shouldDataBeFetched = !_.isFunction(this.shouldDataBeFetched) || this.shouldDataBeFetched(); + if (this.isMounted() && !this.activeTimeout && shouldDataBeFetched) { + this.activeTimeout = $.timeout(updateInterval).done(_.bind(this.startPolling, this)); + } + }, + startPolling: function() { + var shouldDataBeFetched = !_.isFunction(this.shouldDataBeFetched) || this.shouldDataBeFetched(); + if (shouldDataBeFetched) { + this.stopPolling(); + this.fetchData().always(_.bind(this.scheduleDataFetch, this)); + } + }, + stopPolling: function() { + if (this.activeTimeout) { + this.activeTimeout.clear(); + } + delete this.activeTimeout; + }, + componentDidMount: function() { + this.startPolling(); + } + }; + }, + dialogMixin: { + componentDidMount: function() { + var $el = $(this.getDOMNode()); + var modalOptions = _.clone(this.props.modalOptions) || {}; + _.defaults(modalOptions, {background: true, keyboard: true}); + $el.modal(modalOptions); + $el.on('hidden', this.handleHidden); + $el.on('shown', function() { + $el.find('input:first').focus(); + }); + }, + componentWillUnmount: function() { + $(this.getDOMNode()).off('shown hidden'); + }, + handleHidden: function() { + React.unmountComponentAtNode(this.getDOMNode().parentNode); + }, + close: function() { + $(this.getDOMNode()).modal('hide'); + }, + render: function() { + return ( +
+
+ +

{this.props.title}

+
+
+ {this.renderBody()} +
+
+ {this.renderFooter ? this.renderFooter() : } +
+
+ ); + } + } + }; +}); diff --git a/nailgun/static/js/libs/custom/jsx.js b/nailgun/static/js/libs/custom/jsx.js new file mode 100644 index 0000000000..4257a36162 --- /dev/null +++ b/nailgun/static/js/libs/custom/jsx.js @@ -0,0 +1,68 @@ +/** + * @license The MIT License (MIT) + * + * Copyright (c) 2014 Felipe O. Carvalho + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +define(['JSXTransformer', 'text'], function (JSXTransformer, text) { + + 'use strict'; + + var buildMap = {}; + + var jsx = { + version: '0.2.1', + + load: function (name, req, onLoadNative, config) { + var fileExtension = config.jsx && config.jsx.fileExtension || '.js'; + + var onLoad = function(content) { + try { + if (-1 === content.indexOf('@jsx React.DOM')) { + content = "/** @jsx React.DOM */" + content; + } + content = JSXTransformer.transform(content).code; + } catch (err) { + onLoadNative.error(err); + } + + if (config.isBuild) { + buildMap[name] = content; + } else { + content += "\n//# sourceURL=" + location.protocol + "//" + location.hostname + + config.baseUrl + name + fileExtension; + } + + onLoadNative.fromText(content); + }; + + text.load(name + fileExtension, req, onLoad, config); + }, + + write: function (pluginName, moduleName, write) { + if (buildMap.hasOwnProperty(moduleName)) { + var content = buildMap[moduleName]; + write.asModule(moduleName, content); + } + } + }; + + return jsx; +}); diff --git a/nailgun/static/js/main.js b/nailgun/static/js/main.js index d312dd746b..3a5108e99d 100644 --- a/nailgun/static/js/main.js +++ b/nailgun/static/js/main.js @@ -28,6 +28,10 @@ requirejs.config({ keystone_client: 'js/keystone_client', lodash: 'js/libs/bower/lodash/js/lodash', backbone: 'js/libs/custom/backbone', + react: 'js/libs/bower/react/js/react-with-addons', + JSXTransformer: 'js/libs/bower/react/js/JSXTransformer', + jsx: 'js/libs/custom/jsx', + 'react.backbone': 'js/libs/bower/react.backbone/react.backbone', stickit: 'js/libs/bower/backbone.stickit/js/backbone.stickit', coccyx: 'js/libs/custom/coccyx', cocktail: 'js/libs/bower/cocktail/Cocktail', @@ -43,7 +47,8 @@ requirejs.config({ models: 'js/models', collections: 'js/collections', views: 'js/views', - view_mixins: 'js/view_mixins' + view_mixins: 'js/view_mixins', + component_mixins: 'js/component_mixins' }, shim: { underscore: { @@ -92,6 +97,9 @@ requirejs.config({ 'jquery-autoNumeric': { deps: ['jquery'] } + }, + jsx: { + fileExtension: '.jsx' } }); @@ -102,6 +110,8 @@ require([ 'stickit', 'deepModel', 'coccyx', + 'react', + 'react.backbone', 'cocktail', 'i18next', 'bootstrap', @@ -110,6 +120,10 @@ require([ 'jquery-ui', 'jquery-autoNumeric', 'styles', + 'text', +//>>excludeStart("compressed", pragmas.compressed); + 'jsx', +//>>excludeEnd("compressed"); 'app' ], function() { 'use strict'; diff --git a/nailgun/static/js/utils.js b/nailgun/static/js/utils.js index 306b169b9e..6371b985c0 100644 --- a/nailgun/static/js/utils.js +++ b/nailgun/static/js/utils.js @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. **/ -define(['require', 'expression_parser'], function(require, ExpressionParser) { +define(['require', 'expression_parser', 'react'], function(require, ExpressionParser, React) { 'use strict'; var utils = { @@ -101,9 +101,32 @@ define(['require', 'expression_parser'], function(require, ExpressionParser) { } return result; }, + universalMount: function(view, el, parentView) { + if (view instanceof Backbone.View) { + view.render(); + if (el) { + $(el).html(view.el); + } + if (parentView) { + parentView.registerSubView(view); + } + return view; + } + return React.renderComponent(view, $(el)[0]); + }, + universalUnmount: function(view) { + if (view instanceof Backbone.View) { + view.tearDown(); + } else { + React.unmountComponentAtNode(view.getDOMNode().parentNode); + } + }, + showDialog: function(dialog) { + return React.renderComponent(dialog, $('#modal-container')[0]); + }, showErrorDialog: function(options, parentView) { parentView = parentView || app.page; - var dialogViews = require('views/dialogs'); // avoid circular dependencies + var dialogViews = require('jsx!views/dialogs'); // avoid circular dependencies var dialog = new dialogViews.Dialog(); parentView.registerSubView(dialog); dialog.render(_.extend({error: true}, options)); diff --git a/nailgun/static/js/views/cluster_page.js b/nailgun/static/js/views/cluster_page.js index 709b851784..d732b7d6d6 100644 --- a/nailgun/static/js/views/cluster_page.js +++ b/nailgun/static/js/views/cluster_page.js @@ -18,7 +18,7 @@ define( 'utils', 'models', 'views/common', - 'views/dialogs', + 'jsx!views/dialogs', 'views/cluster_page_tabs/nodes_tab', 'views/cluster_page_tabs/network_tab', 'views/cluster_page_tabs/settings_tab', @@ -180,18 +180,10 @@ function(utils, models, commonViews, dialogViews, NodesTab, NetworkTab, Settings activeTab: this.activeTab })).i18n(); var options = {model: this.model, page: this}; - this.clusterInfo = new ClusterInfo(options); - this.registerSubView(this.clusterInfo); - this.$('.cluster-info').html(this.clusterInfo.render().el); - this.clusterCustomizationMessage = new ClusterCustomizationMessage(options); - this.registerSubView(this.clusterCustomizationMessage); - this.$('.customization-message').html(this.clusterCustomizationMessage.render().el); - this.deploymentResult = new DeploymentResult(options); - this.registerSubView(this.deploymentResult); - this.$('.deployment-result').html(this.deploymentResult.render().el); - this.deploymentControl = new DeploymentControl(options); - this.registerSubView(this.deploymentControl); - this.$('.deployment-control').html(this.deploymentControl.render().el); + this.clusterInfo = utils.universalMount(new ClusterInfo(options), this.$('.cluster-info'), this); + this.clusterCustomizationMessage = utils.universalMount(new ClusterCustomizationMessage(options), this.$('.customization-message'), this); + this.deploymentResult = utils.universalMount(new DeploymentResult(options), this.$('.deployment-result'), this); + this.deploymentControl = utils.universalMount(new DeploymentControl(options), this.$('.deployment-control'), this); var tabs = { 'nodes': NodesTab, @@ -202,9 +194,11 @@ function(utils, models, commonViews, dialogViews, NodesTab, NetworkTab, Settings 'healthcheck': HealthCheckTab }; if (_.has(tabs, this.activeTab)) { - this.tab = new tabs[this.activeTab]({model: this.model, tabOptions: this.tabOptions, page: this}); - this.$('#tab-' + this.activeTab).html(this.tab.render().el); - this.registerSubView(this.tab); + this.tab = utils.universalMount( + new tabs[this.activeTab]({model: this.model, tabOptions: this.tabOptions, page: this}), + this.$('#tab-' + this.activeTab), + this + ); } return this; diff --git a/nailgun/static/js/views/cluster_page_tabs/actions_tab.js b/nailgun/static/js/views/cluster_page_tabs/actions_tab.js index b060c17393..2752248b69 100644 --- a/nailgun/static/js/views/cluster_page_tabs/actions_tab.js +++ b/nailgun/static/js/views/cluster_page_tabs/actions_tab.js @@ -18,7 +18,7 @@ define( 'utils', 'models', 'views/common', - 'views/dialogs', + 'jsx!views/dialogs', 'text!templates/cluster/actions_tab.html', 'text!templates/cluster/actions_rename.html', 'text!templates/cluster/actions_reset.html', diff --git a/nailgun/static/js/views/cluster_page_tabs/healthcheck_tab.js b/nailgun/static/js/views/cluster_page_tabs/healthcheck_tab.js index b5e680cebe..90b6d190ab 100644 --- a/nailgun/static/js/views/cluster_page_tabs/healthcheck_tab.js +++ b/nailgun/static/js/views/cluster_page_tabs/healthcheck_tab.js @@ -19,7 +19,7 @@ define( 'models', 'view_mixins', 'views/common', - 'views/dialogs', + 'jsx!views/dialogs', 'text!templates/cluster/healthcheck_tab.html', 'text!templates/cluster/healthcheck_credentials.html', 'text!templates/cluster/healthcheck_testset.html', diff --git a/nailgun/static/js/views/cluster_page_tabs/network_tab.js b/nailgun/static/js/views/cluster_page_tabs/network_tab.js index abf047d3b7..6465b8a3a5 100644 --- a/nailgun/static/js/views/cluster_page_tabs/network_tab.js +++ b/nailgun/static/js/views/cluster_page_tabs/network_tab.js @@ -18,7 +18,7 @@ define( 'utils', 'models', 'views/common', - 'views/dialogs', + 'jsx!views/dialogs', 'text!templates/cluster/network_tab.html', 'text!templates/cluster/network.html', 'text!templates/cluster/range_field.html', diff --git a/nailgun/static/js/views/cluster_page_tabs/nodes_tab_screens/node_list_screen.js b/nailgun/static/js/views/cluster_page_tabs/nodes_tab_screens/node_list_screen.js index 8d1aa751e4..4e1573f2a9 100644 --- a/nailgun/static/js/views/cluster_page_tabs/nodes_tab_screens/node_list_screen.js +++ b/nailgun/static/js/views/cluster_page_tabs/nodes_tab_screens/node_list_screen.js @@ -17,7 +17,7 @@ define( [ 'utils', 'models', - 'views/dialogs', + 'jsx!views/dialogs', 'views/cluster_page_tabs/nodes_tab_screens/screen', 'text!templates/cluster/nodes_management_panel.html', 'text!templates/cluster/assign_roles_panel.html', diff --git a/nailgun/static/js/views/cluster_page_tabs/settings_tab.js b/nailgun/static/js/views/cluster_page_tabs/settings_tab.js index ed2d39cb77..26ddb6f240 100644 --- a/nailgun/static/js/views/cluster_page_tabs/settings_tab.js +++ b/nailgun/static/js/views/cluster_page_tabs/settings_tab.js @@ -19,7 +19,7 @@ define( 'models', 'view_mixins', 'views/common', - 'views/dialogs', + 'jsx!views/dialogs', 'text!templates/cluster/settings_tab.html', 'text!templates/cluster/settings_group.html' ], diff --git a/nailgun/static/js/views/clusters_page.js b/nailgun/static/js/views/clusters_page.js deleted file mode 100644 index d2065ea63d..0000000000 --- a/nailgun/static/js/views/clusters_page.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2013 Mirantis, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain - * a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. -**/ -define( -[ - 'models', - 'utils', - 'views/common', - 'views/dialogs', - 'views/wizard', - 'text!templates/clusters/page.html', - 'text!templates/clusters/cluster.html', - 'text!templates/clusters/new.html', - 'text!templates/clusters/register_trial.html' -], -function(models, utils, commonViews, dialogViews, wizard, clustersPageTemplate, clusterTemplate, newClusterTemplate, registerTrialTemplate) { - 'use strict'; - var ClustersPage, ClusterList, Cluster, RegisterTrial; - - ClustersPage = commonViews.Page.extend({ - navbarActiveElement: 'clusters', - breadcrumbsPath: [['home', '#'], 'environments'], - title: function() { - return $.t('clusters_page.title'); - }, - template: _.template(clustersPageTemplate), - render: function() { - this.$el.html(this.template({clusters: this.collection})).i18n(); - var clustersView = new ClusterList({collection: this.collection}); - this.registerSubView(clustersView); - this.$('.cluster-list').html(clustersView.render().el); - if (_.contains(app.version.get('feature_groups'), 'mirantis') && !localStorage.trialRemoved) { - var registerTrialView = new RegisterTrial(); - this.registerSubView(registerTrialView); - this.$('.page-title').before(registerTrialView.render().el); - } - app.footer.$el.toggle(app.user.get('authenticated')); - app.breadcrumbs.$el.toggle(app.user.get('authenticated')); - app.navbar.$el.toggle(app.user.get('authenticated')); - return this; - } - }); - - ClusterList = Backbone.View.extend({ - className: 'roles-block-row', - newClusterTemplate: _.template(newClusterTemplate), - events: { - 'click .create-cluster': 'createCluster' - }, - createCluster: function() { - app.page.registerSubView(new wizard.CreateClusterWizard({collection: this.collection})).render(); - }, - initialize: function() { - this.collection.on('sync add', this.render, this); - }, - render: function() { - this.tearDownRegisteredSubViews(); - this.$el.html(''); - this.collection.each(_.bind(function(cluster) { - var clusterView = new Cluster({model: cluster}); - this.registerSubView(clusterView); - this.$el.append(clusterView.render().el); - }, this)); - this.$el.append(this.newClusterTemplate()); - return this; - } - }); - - Cluster = Backbone.View.extend({ - tagName: 'a', - className: 'span3 clusterbox', - template: _.template(clusterTemplate), - templateHelpers: _.pick(utils, 'showDiskSize', 'showMemorySize'), - updateInterval: 3000, - scheduleUpdate: function() { - if (this.model.task('cluster_deletion', ['running', 'ready']) || this.model.tasks({group: 'deployment', status: 'running'}).length) { - this.registerDeferred($.timeout(this.updateInterval).done(_.bind(this.update, this))); - } - }, - update: function() { - var deletionTask = this.model.task('cluster_deletion'); - var deploymentTask = this.model.task({group: 'deployment', status: 'running'}); - var request; - if (deletionTask) { - request = deletionTask.fetch(); - request.done(_.bind(this.scheduleUpdate, this)); - request.fail(_.bind(function(response) { - if (response.status == 404) { - this.model.collection.remove(this.model); - this.remove(); - app.navbar.refresh(); - } - }, this)); - this.registerDeferred(request); - } else if (deploymentTask) { - request = deploymentTask.fetch(); - request.done(_.bind(function() { - if (deploymentTask.get('status') == 'running') { - this.updateProgress(); - this.scheduleUpdate(); - } else { - this.model.fetch(); - app.navbar.refresh(); - } - }, this)); - this.registerDeferred(request); - } - }, - updateProgress: function() { - var task = this.model.task({group: 'deployment', status: 'running'}); - if (task) { - var progress = task.get('progress') || 0; - this.$('.bar').css('width', (progress > 3 ? progress : 3) + '%'); - } - }, - initialize: function() { - this.model.on('change', this.render, this); - }, - render: function() { - this.$el.html(this.template(_.extend({ - cluster: this.model, - deploymentTask: this.model.task({group: 'deployment', status: 'running'}) - }, this.templateHelpers))).i18n(); - this.updateProgress(); - if (this.model.task('cluster_deletion', ['running', 'ready'])) { - this.$el.addClass('disabled-cluster'); - this.update(); - } else { - this.$el.attr('href', '#cluster/' + this.model.id + '/nodes'); - if (this.model.task({group: 'deployment', status: 'running'})) { - this.update(); - } - } - return this; - } - }); - - RegisterTrial = Backbone.View.extend({ - template: _.template(registerTrialTemplate), - events: { - 'click .register-trial .close': 'closeTrialWarning' - }, - bindings: { - '.registration-link': { - attributes: [{ - name: 'href', - observe: 'key', - onGet: function(value) { - return !_.isUndefined(value) ? 'http://fuel.mirantis.com/create-subscriber/?key=' + value : '/'; - } - }] - } - }, - initialize: function() { - this.fuelKey = new models.FuelKey(); - this.fuelKey.fetch(); - app.version.on('sync', this.render, this); - }, - closeTrialWarning: function() { - localStorage.setItem('trialRemoved', 'true'); - this.remove(); - }, - render: function() { - this.$el.html(this.template()).i18n(); - this.stickit(this.fuelKey); - return this; - } - }); - return ClustersPage; -}); diff --git a/nailgun/static/js/views/clusters_page.jsx b/nailgun/static/js/views/clusters_page.jsx new file mode 100644 index 0000000000..f2f020db3b --- /dev/null +++ b/nailgun/static/js/views/clusters_page.jsx @@ -0,0 +1,196 @@ +/* + * Copyright 2013 Mirantis, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. +**/ +define( +[ + 'react', + 'models', + 'utils', + 'jsx!component_mixins', + 'jsx!views/dialogs', + 'views/wizard' +], +function(React, models, utils, componentMixins, dialogViews, wizard) { + 'use strict'; + var ClustersPage, ClusterList, Cluster, RegisterTrial; + + ClustersPage = React.createClass({ + navbarActiveElement: 'clusters', + breadcrumbsPath: [['home', '#'], 'environments'], + title: function() { + return $.t('clusters_page.title'); + }, + componentDidMount: function() { + $(app.footer.getDOMNode()).toggle(app.user.get('authenticated')); + $(app.breadcrumbs.getDOMNode()).toggle(app.user.get('authenticated')); + $(app.navbar.getDOMNode()).toggle(app.user.get('authenticated')); + }, + getInitialState: function() { + return {fuelKey: new models.FuelKey()}; + }, + render: function() { + return ( +
+ +

{$.t('clusters_page.title')}

+ +
+ ); + } + }); + + ClusterList = React.createClass({ + mixins: [React.BackboneMixin('clusters')], + createCluster: function() { + (new wizard.CreateClusterWizard({collection: this.props.clusters})).render(); + }, + render: function() { + return ( +
+
+ {this.props.clusters.map(function(cluster) { + return ; + }, this)} +
+
+
{$.t('clusters_page.create_cluster_text')}
+
+
+
+ ); + } + }); + + Cluster = React.createClass({ + mixins: [ + React.BackboneMixin('cluster'), + React.BackboneMixin({modelOrCollection: function(props) { + return props.cluster.get('nodes'); + }}), + React.BackboneMixin({modelOrCollection: function(props) { + return props.cluster.get('tasks'); + }}), + React.BackboneMixin({modelOrCollection: function(props) { + return props.cluster.task({group: 'deployment', status: 'running'}); + }}), + componentMixins.pollingMixin(3) + ], + shouldDataBeFetched: function() { + return this.props.cluster.task('cluster_deletion', ['running', 'ready']) || this.props.cluster.task({group: 'deployment', status: 'running'}); + }, + fetchData: function() { + var request, requests = []; + var deletionTask = this.props.cluster.task('cluster_deletion'); + if (deletionTask) { + request = deletionTask.fetch(); + request.fail(_.bind(function(response) { + if (response.status == 404) { + this.props.cluster.collection.remove(this.props.cluster); + app.navbar.refresh(); + } + }, this)); + requests.push(request); + } + var deploymentTask = this.props.cluster.task({group: 'deployment', status: 'running'}); + if (deploymentTask) { + request = deploymentTask.fetch(); + request.done(_.bind(function() { + if (deploymentTask.get('status') != 'running') { + this.props.cluster.fetch(); + app.navbar.refresh(); + } + }, this)); + requests.push(request); + } + return $.when.apply($, requests); + }, + render: function() { + var cluster = this.props.cluster; + var nodes = cluster.get('nodes'); + var deletionTask = cluster.task('cluster_deletion', ['running', 'ready']); + var deploymentTask = cluster.task({group: 'deployment', status: 'running'}); + return ( + +
{cluster.get('name')}
+
+ {(!nodes.deferred || nodes.deferred.state() == 'resolved') && +
+
{$.t('clusters_page.cluster_hardware_nodes')}
+
{nodes.length}
+ {!!nodes.length && [ +
{$.t('clusters_page.cluster_hardware_cpu')}
, +
{nodes.resources('cores')}
, +
{$.t('clusters_page.cluster_hardware_hdd')}
, +
{nodes.resources('hdd') ? utils.showDiskSize(nodes.resources('hdd')) : '?GB'}
, +
{$.t('clusters_page.cluster_hardware_ram')}
, +
{nodes.resources('ram') ? utils.showMemorySize(nodes.resources('ram')) : '?GB'}
+ ]} +
+ } +
+
+ {deploymentTask ? +
+
+
3 ? deploymentTask.get('progress') : 3) + '%'}}>
+
+
+ : + $.t('cluster.status.' + cluster.get('status'), {defaultValue: cluster.get('status')}) + } +
+
+ ); + } + }); + + RegisterTrial = React.createClass({ + mixins: [React.BackboneMixin('fuelKey')], + shouldShowMessage: function() { + return _.contains(app.version.get('feature_groups'), 'mirantis') && !localStorage.trialRemoved; + }, + closeTrialWarning: function() { + localStorage.setItem('trialRemoved', 'true'); + this.forceUpdate(); + }, + componentWillMount: function() { + if (this.shouldShowMessage()) { + this.props.fuelKey.fetch(); + } + }, + render: function() { + if (this.shouldShowMessage()) { + var key = this.props.fuelKey.get('key'); + return ( +
+ +

+ + {$.t('clusters_page.register_trial_message.part1')}
+ {$.t('clusters_page.register_trial_message.part2')} + + {$.t('clusters_page.register_trial_message.part3')} + + {$.t('clusters_page.register_trial_message.part4')} +

+
+ ); + } + return null; + } + }); + + return ClustersPage; +}); diff --git a/nailgun/static/js/views/common.js b/nailgun/static/js/views/common.js index 8bc37f220a..30fcef441f 100644 --- a/nailgun/static/js/views/common.js +++ b/nailgun/static/js/views/common.js @@ -16,16 +16,9 @@ define( [ 'utils', - 'models', - 'views/dialogs', - 'text!templates/common/navbar.html', - 'text!templates/common/nodes_stats.html', - 'text!templates/common/notifications.html', - 'text!templates/common/notifications_popover.html', - 'text!templates/common/breadcrumb.html', - 'text!templates/common/footer.html' + 'models' ], -function(utils, models, dialogViews, navbarTemplate, nodesStatsTemplate, notificationsTemplate, notificationsPopoverTemplate, breadcrumbsTemplate, footerTemplate) { +function(utils, models) { 'use strict'; var views = {}; @@ -33,18 +26,7 @@ function(utils, models, dialogViews, navbarTemplate, nodesStatsTemplate, notific views.Page = Backbone.View.extend({ navbarActiveElement: null, breadcrumbsPath: null, - title: null, - updateNavbar: function() { - app.navbar.setActive(_.result(this, 'navbarActiveElement')); - }, - updateBreadcrumbs: function() { - app.breadcrumbs.setPath(_.result(this, 'breadcrumbsPath')); - }, - updateTitle: function() { - var defaultTitle = $.t('common.title'); - var title = _.result(this, 'title'); - document.title = title ? defaultTitle + ' - ' + title : defaultTitle; - } + title: null }); views.Tab = Backbone.View.extend({ @@ -53,256 +35,5 @@ function(utils, models, dialogViews, navbarTemplate, nodesStatsTemplate, notific } }); - views.Navbar = Backbone.View.extend({ - className: 'container', - template: _.template(navbarTemplate), - updateInterval: 20000, - notificationsDisplayCount: 5, - events: { - 'click .change-password': 'showChangePasswordDialog' - }, - showChangePasswordDialog: function(e) { - e.preventDefault(); - this.registerSubView(new dialogViews.ChangePasswordDialog()).render(); - }, - setActive: function(url) { - this.elements.each(function(element) { - element.set({active: element.get('url') == '#' + url}); - }); - }, - scheduleUpdate: function() { - this.registerDeferred($.timeout(this.updateInterval).done(_.bind(this.update, this))); - }, - update: function() { - this.refresh().always(_.bind(this.scheduleUpdate, this)); - }, - refresh: function() { - if (app.user.get('authenticated')) { - return $.when(this.statistics.fetch(), this.notifications.fetch({limit: this.notificationsDisplayCount})); - } - return $.Deferred().reject(); - }, - initialize: function(options) { - this.elements = new Backbone.Collection(options.elements); - this.elements.invoke('set', {active: false}); - this.elements.on('change:active', this.render, this); - app.user.on('change:authenticated', function(model, value) { - if (value) { - this.refresh(); - } else { - this.statistics.clear(); - this.notifications.reset(); - } - this.render(); - }, this); - this.statistics = new models.NodesStatistics(); - this.notifications = new models.Notifications(); - this.update(); - }, - render: function() { - this.tearDownRegisteredSubViews(); - this.$el.html(this.template({ - elements: this.elements, - user: app.user, - version: app.version - })); - this.stats = new views.NodesStats({statistics: this.statistics, navbar: this}); - this.registerSubView(this.stats); - this.$('.nodes-summary-container').html(this.stats.render().el); - this.notificationsButton = new views.Notifications({collection: this.notifications, navbar: this}); - this.registerSubView(this.notificationsButton); - this.$('.notifications').html(this.notificationsButton.render().el); - this.popover = new views.NotificationsPopover({collection: this.notifications, navbar: this}); - this.registerSubView(this.popover); - this.$('.notification-wrapper').html(this.popover.render().el); - return this; - } - }); - - views.NodesStats = Backbone.View.extend({ - template: _.template(nodesStatsTemplate), - bindings: { - '.total-nodes-count': { - observe: 'total', - onGet: 'returnValueOrNonBreakingSpace' - }, - '.total-nodes-title': { - observe: 'total', - onGet: 'formatTitle', - updateMethod: 'html' - }, - '.unallocated-nodes-count': { - observe: 'unallocated', - onGet: 'returnValueOrNonBreakingSpace' - }, - '.unallocated-nodes-title': { - observe: 'unallocated', - onGet: 'formatTitle', - updateMethod: 'html' - } - }, - returnValueOrNonBreakingSpace: function(value) { - return !_.isUndefined(value) ? value : '\u00A0'; - }, - formatTitle: function(value, options) { - return !_.isUndefined(value) ? utils.linebreaks(_.escape($.t('navbar.stats.' + options.observe, {count: value}))) : ''; - }, - initialize: function(options) { - _.defaults(this, options); - }, - render: function() { - this.$el.html(this.template({stats: this.statistics})); - this.stickit(this.statistics); - return this; - } - }); - - views.Notifications = Backbone.View.extend({ - template: _.template(notificationsTemplate), - events: { - 'click .icon-comment': 'togglePopover', - 'click .badge': 'togglePopover' - }, - togglePopover: function(e) { - this.navbar.popover.toggle(); - }, - initialize: function(options) { - _.defaults(this, options); - this.collection.on('sync reset', this.render, this); - }, - render: function() { - this.$el.html(this.template({ - notifications: this.collection.where({status: 'unread'}), - authenticated: app.user.get('authenticated') - })); - return this; - } - }); - - views.NotificationsPopover = Backbone.View.extend({ - template: _.template(notificationsPopoverTemplate), - templateHelpers: _.pick(utils, 'urlify'), - visible: false, - events: { - 'click .discover[data-node]' : 'showNodeInfo' - }, - showNodeInfo: function(e) { - this.toggle(); - var node = new models.Node({id: $(e.currentTarget).data('node')}); - node.deferred = node.fetch(); - var dialog = new dialogViews.ShowNodeInfoDialog({node: node}); - this.registerSubView(dialog); - dialog.render(); - }, - toggle: function() { - this.visible = !this.visible; - this.render(); - }, - hide: function(e) { - if (this.visible && (!e || (!$(e.target).closest(this.navbar.notificationsButton.el).length && !$(e.target).closest(this.el).length))) { - this.visible = false; - this.render(); - } - }, - markAsRead: function() { - var notificationsToMark = new models.Notifications(this.collection.where({status : 'unread'})); - if (notificationsToMark.length) { - notificationsToMark.toJSON = function() { - return notificationsToMark.map(function(notification) { - notification.set({status: 'read'}, {silent: true}); - return _.pick(notification.attributes, 'id', 'status'); - }, this); - }; - Backbone.sync('update', notificationsToMark).done(_.bind(function() { - this.collection.trigger('sync'); - }, this)); - } - }, - beforeTearDown: function() { - this.unbindEvents(); - }, - initialize: function(options) { - _.defaults(this, options); - this.collection.bind('add', this.render, this); - this.eventNamespace = 'click.click-notifications'; - }, - bindEvents: function() { - $('html').on(this.eventNamespace, _.bind(this.hide, this)); - Backbone.history.on('route', this.hide, this); - }, - unbindEvents: function() { - $('html').off(this.eventNamespace); - Backbone.history.off('route', this.hide, this); - }, - render: function() { - if (this.visible) { - this.$el.html(this.template(_.extend({ - notifications: this.collection, - displayCount: this.navbar.notificationsDisplayCount, - showMore: (Backbone.history.getHash() != 'notifications') && this.collection.length - }, this.templateHelpers))).i18n(); - this.markAsRead(); - this.bindEvents(); - } else { - this.$el.html(''); - this.unbindEvents(); - } - return this; - } - }); - - views.Breadcrumbs = Backbone.View.extend({ - className: 'container', - template: _.template(breadcrumbsTemplate), - path: [], - setPath: function(path) { - this.path = path; - this.render(); - }, - render: function() { - this.$el.html(this.template({path: this.path})); - return this; - } - }); - - views.Footer = Backbone.View.extend({ - template: _.template(footerTemplate), - events: { - 'click .footer-lang li a': 'setLocale' - }, - setLocale: function(e) { - var newLocale = _.find(this.locales, {locale: $(e.currentTarget).data('locale')}); - $.i18n.setLng(newLocale.locale, {}); - window.location.reload(); - }, - getAvailableLocales: function() { - return _.map(_.keys($.i18n.options.resStore).sort(), function(locale) { - return {locale: locale, name: $.t('language', {lng: locale})}; - }, this); - }, - getCurrentLocale: function() { - return _.find(this.locales, {locale: $.i18n.lng()}); - }, - setDefaultLocale: function() { - var currentLocale = this.getCurrentLocale(); - if (!currentLocale) { - $.i18n.setLng(this.locales[0].locale, {}); - } - }, - initialize: function(options) { - this.locales = this.getAvailableLocales(); - this.setDefaultLocale(); - app.version.on('sync', this.render, this); - }, - render: function() { - this.$el.html(this.template({ - version: app.version, - locales: this.locales, - currentLocale: this.getCurrentLocale() - })).i18n(); - return this; - } - }); - return views; }); diff --git a/nailgun/static/js/views/dialogs.js b/nailgun/static/js/views/dialogs.jsx similarity index 81% rename from nailgun/static/js/views/dialogs.js rename to nailgun/static/js/views/dialogs.jsx index 6fb6557159..2c565d1b03 100644 --- a/nailgun/static/js/views/dialogs.js +++ b/nailgun/static/js/views/dialogs.jsx @@ -16,9 +16,11 @@ define( [ 'require', + 'react', 'utils', 'models', 'view_mixins', + 'jsx!component_mixins', 'text!templates/dialogs/base_dialog.html', 'text!templates/dialogs/discard_changes.html', 'text!templates/dialogs/display_changes.html', @@ -28,12 +30,13 @@ define( 'text!templates/dialogs/update_environment.html', 'text!templates/dialogs/show_node.html', 'text!templates/dialogs/dismiss_settings.html', - 'text!templates/dialogs/delete_nodes.html', - 'text!templates/dialogs/change_password.html' + 'text!templates/dialogs/delete_nodes.html' ], -function(require, utils, models, viewMixins, baseDialogTemplate, discardChangesDialogTemplate, displayChangesDialogTemplate, removeClusterDialogTemplate, stopDeploymentDialogTemplate, resetEnvironmentDialogTemplate, updateEnvironmentDialogTemplate, showNodeInfoTemplate, discardSettingsChangesTemplate, deleteNodesTemplate, changePasswordTemplate) { +function(require, React, utils, models, viewMixins, componentMixins, baseDialogTemplate, discardChangesDialogTemplate, displayChangesDialogTemplate, removeClusterDialogTemplate, stopDeploymentDialogTemplate, resetEnvironmentDialogTemplate, updateEnvironmentDialogTemplate, showNodeInfoTemplate, discardSettingsChangesTemplate, deleteNodesTemplate) { 'use strict'; + var cx = React.addons.classSet; + var views = {}; views.Dialog = Backbone.View.extend({ @@ -408,47 +411,93 @@ function(require, utils, models, viewMixins, baseDialogTemplate, discardChangesD } }); - views.ChangePasswordDialog = views.Dialog.extend({ - template: _.template(changePasswordTemplate), - mixins: [viewMixins.toggleablePassword], - events: { - 'click .btn-change-password': 'changePassword', - 'keyup input': 'onPasswordChange', - 'keydown': 'onKeydown' + views.ChangePasswordDialog = React.createClass({ + mixins: [componentMixins.dialogMixin, React.addons.LinkedStateMixin], + getDefaultProps: function() { + return { + title: $.t('dialog.change_password.title') + }; + }, + getInitialState: function() { + return { + currentPassword: '', + newPassword: '', + validationError: false, + locked: false + }; + }, + renderBody: function() { + var ns = 'dialog.change_password.'; + return ( +
+
+
{$.t(ns + 'current_password')}
+
+ + +
+
+ {this.state.validationError && $.t('dialog.change_password.wrong_current_password')} +
+
+
+
{$.t(ns + 'new_password')}
+
+ + +
+
+
+ + ); + }, + renderFooter: function() { + return [ + , + + ]; + }, + isPasswordChangeAvailable: function() { + return !!(this.state.currentPassword && this.state.newPassword); + }, + handleKeyDown: function(e) { + if (e.key == 'Enter') { + this.changePassword(); + } + }, + handleChange: function(name, clearError, e) { + var newState = {}; + newState[name] = e.target.value; + if (clearError) { + newState.validationError = false; + } + this.setState(newState); }, changePassword: function() { - var currentPassword = this.$('[name=current_password]').val(), - newPassword = this.$('[name=new_password]').val(), - confirmedPassword= this.$('[name=confirm_new_password]').val(); - if (currentPassword && (newPassword == confirmedPassword)) { - app.keystoneClient.changePassword(currentPassword, newPassword) + if (this.isPasswordChangeAvailable()) { + this.setState({locked: true}); + app.keystoneClient.changePassword(this.state.currentPassword, this.state.newPassword) .done(_.bind(function() { app.user.set({password: app.keystoneClient.password}); - this.$el.modal('hide'); + this.close(); }, this)) .fail(_.bind(function() { - this.$('[name=current_password]').focus().addClass('error').parent().siblings('.validation-error').show(); + this.setState({validationError: true, locked: false}); + $(this.refs.currentPassword.getDOMNode()).focus(); }, this)); } - }, - onPasswordChange: function(e) { - this.$(e.currentTarget).removeClass('error').parent().siblings('.validation-error').hide(); - var newPassword = this.$('[name=new_password]'), - confirmedPassword = this.$('[name=confirm_new_password]'); - confirmedPassword.removeClass('error').parent().siblings('.validation-error').hide(); - if (newPassword.val() != confirmedPassword.val()) { - confirmedPassword.addClass('error').parent().siblings('.validation-error').show(); - } - var disabled = (!_.all(_.invoke(_.map(this.$('input'), $), 'val')) || this.$('.validation-error').is(':visible')); - _.defer(_.bind(function() { - this.$('.btn-change-password').attr('disabled', disabled); - }, this)); - }, - onKeydown: function(e) { - if (e.which == 13) { - e.preventDefault(); - this.changePassword(); - } } }); diff --git a/nailgun/static/js/views/layout.jsx b/nailgun/static/js/views/layout.jsx new file mode 100644 index 0000000000..237e176d3b --- /dev/null +++ b/nailgun/static/js/views/layout.jsx @@ -0,0 +1,299 @@ +/* + * Copyright 2014 Mirantis, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. +**/ +define( +[ + 'react', + 'utils', + 'models', + 'jsx!component_mixins', + 'jsx!views/dialogs' +], +function(React, utils, models, componentMixins, dialogs) { + 'use strict'; + + var components = {}; + + var cx = React.addons.classSet; + + components.Navbar = React.createClass({ + mixins: [ + React.BackboneMixin('user'), + React.BackboneMixin('version'), + componentMixins.pollingMixin(20) + ], + showChangePasswordDialog: function(e) { + e.preventDefault(); + utils.showDialog(dialogs.ChangePasswordDialog()); + }, + togglePopover: function(visible) { + this.setState({popoverVisible: _.isBoolean(visible) ? visible : !this.state.popoverVisible}); + }, + setActive: function(url) { + this.setState({activeElement: url}); + }, + shouldDataBeFetched: function() { + return this.props.user.get('authenticated'); + }, + fetchData: function() { + return $.when(this.props.statistics.fetch(), this.props.notifications.fetch({limit: this.props.notificationsDisplayCount})); + }, + refresh: function() { + if (this.shouldDataBeFetched()) { + return this.fetchData(); + } + return $.Deferred().reject(); + }, + componentDidMount: function() { + this.props.user.on('change:authenticated', function(model, value) { + if (value) { + this.refresh(); + } else { + this.props.statistics.clear(); + this.props.notifications.reset(); + } + }, this); + }, + getDefaultProps: function() { + return {notificationsDisplayCount: 5}; + }, + getInitialState: function() { + return { + activeElement: null, + popoverVisible: false + }; + }, + render: function() { + return ( +
+
+ {this.props.version.get('auth_required') && this.props.user.get('authenticated') && +
+ + {this.props.user.get('username')} + {$.t('common.change_password')} + {$.t('common.logout')} +
+ } +
+
+
+ +
+
+
+ {this.state.popoverVisible && + + } +
+
+ ); + } + }); + + var NodeStats = React.createClass({ + mixins: [React.BackboneMixin('statistics')], + render: function() { + return ( +
  • +
    + {_.map(['total', 'unallocated'], function(prop) { + var value = this.props.statistics.get(prop); + return _.isUndefined(value) ? '' : [ +
    {value}
    , +
    + ]; + }, this)} +
    +
  • + ); + } + }); + + var Notifications = React.createClass({ + mixins: [React.BackboneMixin('notifications')], + render: function() { + var unreadNotifications = this.props.notifications.where({status: 'unread'}); + return ( +
  • + + {unreadNotifications.length && {unreadNotifications.length}} +
  • + ); + } + }); + + var NotificationsPopover = React.createClass({ + mixins: [React.BackboneMixin('notifications')], + showNodeInfo: function(id) { + var node = new models.Node({id: id}); + node.deferred = node.fetch(); + (new dialogViews.ShowNodeInfoDialog({node: node})).render(); + }, + toggle: function(visible) { + this.props.togglePopover(visible); + }, + handleBodyClick: function(e) { + if (_.all([this.el, this._owner.refs.notifications.el], function(el) { + return !$(e.target).closest(el).length; + })) { + _.defer(_.bind(this.toggle, this, false)); + } + }, + markAsRead: function() { + var notificationsToMark = new models.Notifications(this.props.notifications.where({status: 'unread'})); + if (notificationsToMark.length) { + this.setState({unreadNotificationsIds: notificationsToMark.pluck('id')}); + notificationsToMark.toJSON = function() { + return notificationsToMark.map(function(notification) { + notification.set({status: 'read'}); + return _.pick(notification.attributes, 'id', 'status'); + }, this); + }; + Backbone.sync('update', notificationsToMark); + } + }, + componentDidMount: function() { + this.markAsRead(); + this.eventNamespace = 'click.click-notifications'; + $('html').on(this.eventNamespace, _.bind(this.handleBodyClick, this)); + Backbone.history.on('route', this.toggle, this); + }, + componentWillUnmount: function() { + $('html').off(this.eventNamespace); + Backbone.history.off('route', this.toggle, this); + }, + getInitialState: function() { + return {unreadNotificationsIds: []}; + }, + render: function() { + var showMore = (Backbone.history.getHash() != 'notifications') && this.props.notifications.length; + var notifications = this.props.notifications.last(this.props.displayCount).reverse(); + return ( +
    +
      + {this.props.notifications.length ? ( + _.map(notifications, function(notification, index, collection) { + var unread = notification.get('status') == 'unread' || _.contains(this.state.unreadNotificationsIds, notification.id); + return [ +
    • + + +
    • , + (showMore || index < (collection.length - 1)) &&
    • + ]; + }, this) + ) :
    • {$.t('notifications_popover.no_notifications_text')}
    • } +
    + {showMore && } +
    + ); + } + }); + + components.Footer = React.createClass({ + mixins: [React.BackboneMixin('version')], + render: function () { + return ( +
    + {_.contains(this.props.version.get('feature_groups'), 'mirantis') && +
    + +
    +
    + } + {this.props.version.get('release') && +
    Version: {this.props.version.get('release')}
    + } +
    +
    + +
      + {_.map(this.props.locales, function(locale) { + return
    • + {locale.name} +
    • ; + }, this)} +
    +
    +
    +
    + ); + }, + setLocale: function(newLocale) { + $.i18n.setLng(newLocale.locale, {}); + window.location.reload(); + }, + getAvailableLocales: function() { + return _.map(_.keys($.i18n.options.resStore).sort(), function(locale) { + return {locale: locale, name: $.t('language', {lng: locale})}; + }, this); + }, + getCurrentLocale: function() { + return _.find(this.props.locales, {locale: $.i18n.lng()}); + }, + setDefaultLocale: function() { + if (!this.getCurrentLocale()) { + $.i18n.setLng(this.props.locales[0].locale, {}); + } + }, + getDefaultProps: function() { + return {locales: this.prototype.getAvailableLocales()}; + }, + componentWillMount: function(options) { + this.setDefaultLocale(); + } + }); + + components.Breadcrumbs = React.createClass({ + setPath: function(path) { + this.setProps({path: path}); + }, + render: function() { + return ; + } + }); + + return components; +}); diff --git a/nailgun/static/js/views/login_page.js b/nailgun/static/js/views/login_page.js index 5b7ea2ec19..f118b15528 100644 --- a/nailgun/static/js/views/login_page.js +++ b/nailgun/static/js/views/login_page.js @@ -68,9 +68,9 @@ function(commonViews, loginPageTemplate) { _.defer(_.bind(function() { this.$('[autofocus]:first').focus(); }, this)); - app.footer.$el.hide(); - app.breadcrumbs.$el.hide(); - app.navbar.$el.hide(); + $(app.footer.getDOMNode()).hide(); + $(app.breadcrumbs.getDOMNode()).hide(); + $(app.navbar.getDOMNode()).hide(); } return this; } diff --git a/nailgun/static/js/views/notifications_page.js b/nailgun/static/js/views/notifications_page.js index 6a72d8411d..45eef9116b 100644 --- a/nailgun/static/js/views/notifications_page.js +++ b/nailgun/static/js/views/notifications_page.js @@ -18,7 +18,7 @@ define( 'utils', 'models', 'views/common', - 'views/dialogs', + 'jsx!views/dialogs', 'text!templates/notifications/list.html' ], function(utils, models, commonViews, dialogViews, notificationsListTemplate) { diff --git a/nailgun/static/js/views/releases_page.js b/nailgun/static/js/views/releases_page.js index 4290ce5142..6e98f9630d 100644 --- a/nailgun/static/js/views/releases_page.js +++ b/nailgun/static/js/views/releases_page.js @@ -17,7 +17,7 @@ define( [ 'utils', 'views/common', - 'views/dialogs', + 'jsx!views/dialogs', 'text!templates/release/list.html', 'text!templates/release/release.html' ], diff --git a/nailgun/static/js/views/wizard.js b/nailgun/static/js/views/wizard.js index 206ea26c09..4be25c04d5 100644 --- a/nailgun/static/js/views/wizard.js +++ b/nailgun/static/js/views/wizard.js @@ -19,7 +19,7 @@ define( 'utils', 'models', 'view_mixins', - 'views/dialogs', + 'jsx!views/dialogs', 'text!templates/dialogs/create_cluster_wizard.html', 'text!templates/dialogs/create_cluster_wizard/name_and_release.html', 'text!templates/dialogs/create_cluster_wizard/common_wizard_panel.html', diff --git a/nailgun/static/templates/clusters/cluster.html b/nailgun/static/templates/clusters/cluster.html deleted file mode 100644 index 9278cae99a..0000000000 --- a/nailgun/static/templates/clusters/cluster.html +++ /dev/null @@ -1,29 +0,0 @@ -
    <%- cluster.get('name') %>
    -<% var nodes = cluster.get('nodes') %> -
    - <% if (!nodes.deferred || nodes.deferred.state() == 'resolved') { %> -
    -
    -
    <%= nodes.length %>
    - <% if (nodes.length) { %> -
    -
    <%= nodes.resources('cores') %>
    -
    -
    <%= nodes.resources('hdd') ? showDiskSize(nodes.resources('hdd')) : '?GB' %>
    -
    -
    <%= nodes.resources('ram') ? showMemorySize(nodes.resources('ram')) : '?GB' %>
    - <% } %> -
    - <% } %> -
    -
    - <% if (deploymentTask) { %> -
    -
    -
    -
    -
    - <% } else { %> - <%- $.t('cluster.status.' + cluster.get('status'), {defaultValue: cluster.get('status')}) %> - <% } %> -
    diff --git a/nailgun/static/templates/clusters/new.html b/nailgun/static/templates/clusters/new.html deleted file mode 100644 index cc67be57fb..0000000000 --- a/nailgun/static/templates/clusters/new.html +++ /dev/null @@ -1,4 +0,0 @@ -
    -
    -
    <%- $.t('clusters_page.create_cluster_text') %>
    -
    diff --git a/nailgun/static/templates/clusters/page.html b/nailgun/static/templates/clusters/page.html deleted file mode 100644 index a238063bbc..0000000000 --- a/nailgun/static/templates/clusters/page.html +++ /dev/null @@ -1,2 +0,0 @@ -

    -
    diff --git a/nailgun/static/templates/clusters/register_trial.html b/nailgun/static/templates/clusters/register_trial.html deleted file mode 100644 index ca3b1caa17..0000000000 --- a/nailgun/static/templates/clusters/register_trial.html +++ /dev/null @@ -1,8 +0,0 @@ -
    - -

    - - <%- $.t('clusters_page.register_trial_message.part1') %>
    - <%- $.t('clusters_page.register_trial_message.part2') %><%- $.t('clusters_page.register_trial_message.part3') %><%- $.t('clusters_page.register_trial_message.part4') %> -

    -
    \ No newline at end of file diff --git a/nailgun/static/templates/common/breadcrumb.html b/nailgun/static/templates/common/breadcrumb.html deleted file mode 100644 index 8e5dcbd3ec..0000000000 --- a/nailgun/static/templates/common/breadcrumb.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/nailgun/static/templates/common/footer.html b/nailgun/static/templates/common/footer.html deleted file mode 100644 index c84e511696..0000000000 --- a/nailgun/static/templates/common/footer.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/nailgun/static/templates/common/navbar.html b/nailgun/static/templates/common/navbar.html deleted file mode 100644 index 66dd4fa89b..0000000000 --- a/nailgun/static/templates/common/navbar.html +++ /dev/null @@ -1,24 +0,0 @@ - - -
    diff --git a/nailgun/static/templates/common/nodes_stats.html b/nailgun/static/templates/common/nodes_stats.html deleted file mode 100644 index 174a430799..0000000000 --- a/nailgun/static/templates/common/nodes_stats.html +++ /dev/null @@ -1,6 +0,0 @@ -
    -
    -
    -
    -
    -
    diff --git a/nailgun/static/templates/common/notifications.html b/nailgun/static/templates/common/notifications.html deleted file mode 100644 index eff8b415a7..0000000000 --- a/nailgun/static/templates/common/notifications.html +++ /dev/null @@ -1,6 +0,0 @@ -<% if (authenticated) { %> - - <% if (notifications.length) { %> - <%= notifications.length %> - <% } %> -<% } %> \ No newline at end of file diff --git a/nailgun/static/templates/common/notifications_popover.html b/nailgun/static/templates/common/notifications_popover.html deleted file mode 100644 index 63a8d20d24..0000000000 --- a/nailgun/static/templates/common/notifications_popover.html +++ /dev/null @@ -1,22 +0,0 @@ -
    - - <% if (showMore) { %> -
    - <% } %> -
    diff --git a/nailgun/ui_tests/bind-polyfill.js b/nailgun/ui_tests/bind-polyfill.js new file mode 100644 index 0000000000..12fe3bba9c --- /dev/null +++ b/nailgun/ui_tests/bind-polyfill.js @@ -0,0 +1,35 @@ +(function() { + +var Ap = Array.prototype; +var slice = Ap.slice; +var Fp = Function.prototype; + +if (!Fp.bind) { + // PhantomJS doesn't support Function.prototype.bind natively, so + // polyfill it whenever this module is required. + Fp.bind = function(context) { + var func = this; + var args = slice.call(arguments, 1); + + function bound() { + var invokedAsConstructor = func.prototype && (this instanceof func); + return func.apply( + // Ignore the context parameter when invoking the bound function + // as a constructor. Note that this includes not only constructor + // invocations using the new keyword but also calls to base class + // constructors such as BaseClass.call(this, ...) or super(...). + !invokedAsConstructor && context || this, + args.concat(slice.call(arguments)) + ); + } + + // The bound function must share the .prototype of the unbound + // function so that any object created by one constructor will count + // as an instance of both constructors. + bound.prototype = func.prototype; + + return bound; + }; +} + +})(); diff --git a/nailgun/ui_tests/helpers.js b/nailgun/ui_tests/helpers.js index 2e632e3f28..1dc036ce40 100644 --- a/nailgun/ui_tests/helpers.js +++ b/nailgun/ui_tests/helpers.js @@ -24,6 +24,10 @@ casper.on('page.error', function(msg) { casper.echo(msg, 'ERROR'); }); +casper.on('page.initialized', function(msg) { + this.loadJsFile('bind-polyfill'); +}); + casper.loadPage = function(page) { //FIXME: hack to prevent ajax requests interruption (phantomjs issue) this.wait(1000);