You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by se...@apache.org on 2015/08/10 18:00:50 UTC

[08/34] incubator-ignite git commit: IGNITE-843 WIP on password reset.

IGNITE-843 WIP on password reset.


Project: http://git-wip-us.apache.org/repos/asf/incubator-ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ignite/commit/2aa1ce8a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ignite/tree/2aa1ce8a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ignite/diff/2aa1ce8a

Branch: refs/heads/ignite-1201
Commit: 2aa1ce8ab021aaf2e2af26233a39daeb262d2081
Parents: 675a0cf
Author: AKuznetsov <ak...@gridgain.com>
Authored: Thu Aug 6 03:03:16 2015 +0700
Committer: AKuznetsov <ak...@gridgain.com>
Committed: Thu Aug 6 03:03:16 2015 +0700

----------------------------------------------------------------------
 .../src/main/js/controllers/common-module.js    |  12 +-
 .../main/js/controllers/metadata-controller.js  |  32 +++++-
 .../main/js/controllers/models/metadata.json    |  44 +++----
 .../control-center-web/src/main/js/package.json |   1 +
 .../src/main/js/public/stylesheets/style.scss   |  12 +-
 .../src/main/js/routes/notebooks.js             |  38 +++----
 .../src/main/js/routes/public.js                | 114 +++++++++++++++++--
 .../main/js/views/configuration/summary.jade    |   2 +-
 .../src/main/js/views/includes/controls.jade    |   6 +-
 .../src/main/js/views/login.jade                |  29 ++---
 .../src/main/js/views/reset.jade                |  22 ++++
 .../src/main/js/views/resetModal.jade           |  34 ++++++
 .../src/main/js/views/templates/layout.jade     |   2 +-
 13 files changed, 272 insertions(+), 76 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/controllers/common-module.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/common-module.js b/modules/control-center-web/src/main/js/controllers/common-module.js
index 186914e..05186a9 100644
--- a/modules/control-center-web/src/main/js/controllers/common-module.js
+++ b/modules/control-center-web/src/main/js/controllers/common-module.js
@@ -613,11 +613,11 @@ controlCenterModule.controller('auth', [
             $scope.userDropdown.push({text: 'Log Out', href: '/logout'});
         }
 
-        // Pre-fetch an external template populated with a custom scope
+        // Pre-fetch modal dialogs.
         var authModal = $modal({scope: $scope, templateUrl: '/login', show: false});
+        var resetModal = $modal({scope: $scope, templateUrl: '/resetModal', show: false});
 
         $scope.login = function () {
-            // Show when some event occurs (use $promise property to ensure the template has been loaded)
             authModal.$promise.then(function () {
                 authModal.show();
 
@@ -625,6 +625,14 @@ controlCenterModule.controller('auth', [
             });
         };
 
+        $scope.reset = function () {
+            resetModal.$promise.then(function () {
+                resetModal.show();
+
+                $focus('user_password');
+            });
+        };
+
         $scope.auth = function (action, user_info) {
             $http.post('/' + action, user_info)
                 .success(function () {

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/controllers/metadata-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/metadata-controller.js b/modules/control-center-web/src/main/js/controllers/metadata-controller.js
index be616f8..64ec7d2 100644
--- a/modules/control-center-web/src/main/js/controllers/metadata-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/metadata-controller.js
@@ -61,7 +61,37 @@ controlCenterModule.controller('metadataController', [
                 {value: 'h2', label: 'H2 database'}
             ];
 
-            $scope.rdbms = 'oracle';
+            $scope.presets = {
+                oracle: {
+                    drvClass: 'oracle.jdbc.OracleDriver',
+                    drvUrl: 'jdbc:oracle:thin:@[host]:[port]:[database]',
+                    user: 'system'
+                },
+                db2: {
+                    drvClass: 'com.ibm.db2.jcc.DB2Driver',
+                    drvUrl: 'jdbc:db2://[host]:[port]/[database]',
+                    user: 'db2admin'
+                },
+                mssql: {
+                    drvClass: 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
+                    drvUrl: 'jdbc:sqlserver://[host]:[port][;databaseName=database]',
+                    user: 'sa'
+                },
+                postgre: {
+                    drvClass: 'org.postgresql.Driver', drvUrl: 'jdbc:postgresql://[host]:[port]/[database]',
+                    user: 'sa'
+                },
+                mysql: {
+                    drvClass: 'com.mysql.jdbc.Driver',
+                    drvUrl: 'jdbc:mysql://[host]:[port]/[database]', user: 'root'
+                },
+                h2: {drvClass: 'org.h2.Driver', drvUrl: 'jdbc:h2:[database]', user: 'sa'}
+            };
+
+            $scope.preset = {
+                rdbms: 'oracle'
+
+            };
 
             $scope.jdbcTypes = [
                 {value: 'BIT', label: 'BIT'},

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/controllers/models/metadata.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/models/metadata.json b/modules/control-center-web/src/main/js/controllers/models/metadata.json
index 8e2b5b5..84ad9e4 100644
--- a/modules/control-center-web/src/main/js/controllers/models/metadata.json
+++ b/modules/control-center-web/src/main/js/controllers/models/metadata.json
@@ -196,43 +196,40 @@
   ],
   "metadataDb": [
     {
-      "label": "Name",
-      "type": "text",
-      "model": "name"
-    },
-    {
-      "label": "Database type",
+      "label": "Database preset",
       "type": "select",
-      "model": "rdbms",
+      "model": "preset",
       "items": "databases",
       "tip": [
         "Select database type to connect for loading tables metadata."
       ]
     },
     {
-      "label": "Database name",
+      "label": "Driver JAR",
       "type": "text",
-      "model": "dbName",
+      "model": "drvJar",
+      "placeholder": "JAR with JDBC driver",
       "tip": [
-        "Database name to connect for loading tables metadata."
+        "Select appropriate JAR with JDBC driver."
       ]
     },
     {
-      "label": "Host",
+      "label": "JDBC Driver",
       "type": "text",
-      "model": "host",
-      "placeholder": "IP address or host",
+      "model": "drvClass",
+      "placeholder": "Full class name of JDBC driver",
       "tip": [
-        "IP address or host name where database server deployed."
+        "Full class name of JDBC driver that will be used to connect to database."
       ]
     },
     {
-      "label": "Port",
-      "type": "number",
-      "model": "port",
-      "max": 65535,
+      "label": "JDBC URL",
+      "type": "text",
+      "model": "jdbcUrl",
+      "placeholder": "JDBC URL",
       "tip": [
-        "Port number for connecting to database."
+        "JDBC URL for connecting to database.",
+        "Refer to your database documentation for details."
       ]
     },
     {
@@ -251,6 +248,15 @@
         "Password for connecting to database.",
         "Note, password would not be saved."
       ]
+    },
+    {
+      "label": "Tables only",
+      "type": "check",
+      "model": "tablesOnly",
+      "tip": [
+        "If selected then only tables metadata will be parsed.",
+        "Otherwise table and view metadata will be parsed."
+      ]
     }
   ]
 }

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/package.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/package.json b/modules/control-center-web/src/main/js/package.json
index 39c138b..72a0497 100644
--- a/modules/control-center-web/src/main/js/package.json
+++ b/modules/control-center-web/src/main/js/package.json
@@ -37,6 +37,7 @@
     "passport": "^0.2.1",
     "passport-local": "^1.0.0",
     "passport-local-mongoose": "^1.0.0",
+    "nodemailer": "1.4.0",
     "serve-favicon": "~2.2.0",
     "ws": "~0.7.2"
   },

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/public/stylesheets/style.scss
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/stylesheets/style.scss b/modules/control-center-web/src/main/js/public/stylesheets/style.scss
index c5eba0c..4b0e754 100644
--- a/modules/control-center-web/src/main/js/public/stylesheets/style.scss
+++ b/modules/control-center-web/src/main/js/public/stylesheets/style.scss
@@ -898,6 +898,10 @@ label {
     overflow: hidden;
 }
 
+.labelHeader {
+    font-weight: bold;
+}
+
 .labelField {
     float: left;
     margin-right: 5px;
@@ -908,6 +912,10 @@ label {
     line-height: $input-height;
 }
 
+.labelLogin {
+    margin-right: 10px;
+}
+
 .form-horizontal .form-group {
     margin: 0;
 }
@@ -1212,10 +1220,6 @@ a {
     }
 }
 
-.labelHeader {
-    font-weight: bold;
-}
-
 .ace_editor, #ace_document {
     margin: 0.65em 0 0 0;
 

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/routes/notebooks.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/notebooks.js b/modules/control-center-web/src/main/js/routes/notebooks.js
index 0560d3b..0c37d85 100644
--- a/modules/control-center-web/src/main/js/routes/notebooks.js
+++ b/modules/control-center-web/src/main/js/routes/notebooks.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You 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.
+ *      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.
  */
 
 var router = require('express').Router();
@@ -68,7 +66,7 @@ router.post('/get', function (req, res) {
         });
 
         // Get all metadata for spaces.
-        db.Notebook.findOne({$or : [{space: {$in: space_ids}}, {_id: req.body.noteId}]}).exec(function (err, notebook) {
+        db.Notebook.findOne({$or: [{space: {$in: space_ids}}, {_id: req.body.noteId}]}).exec(function (err, notebook) {
             if (err)
                 return res.status(500).send(err.message);
 
@@ -77,10 +75,10 @@ router.post('/get', function (req, res) {
     });
 });
 
-function _randomValueHex (len) {
-    return crypto.randomBytes(Math.ceil(len/2))
+function _randomValueHex(len) {
+    return crypto.randomBytes(Math.ceil(len / 2))
         .toString('hex') // convert to hexadecimal format
-        .slice(0,len);   // return required number of characters
+        .slice(0, len);  // return required number of characters
 }
 
 /**
@@ -97,7 +95,7 @@ router.get('/new', function (req, res) {
         if (err)
             return res.status(500).send(err.message);
 
-        var name =  'Notebook'  + ' ' + _randomValueHex(8);
+        var name = 'Notebook' + ' ' + _randomValueHex(8);
 
         (new db.Notebook({space: space.id, name: name, paragraph: []})).save(function (err, notebook) {
             if (err)
@@ -108,4 +106,4 @@ router.get('/new', function (req, res) {
     });
 });
 
-module.exports = router;
\ No newline at end of file
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/routes/public.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/public.js b/modules/control-center-web/src/main/js/routes/public.js
index b3cb983..817c93b 100644
--- a/modules/control-center-web/src/main/js/routes/public.js
+++ b/modules/control-center-web/src/main/js/routes/public.js
@@ -17,6 +17,8 @@
 
 var router = require('express').Router();
 var passport = require('passport');
+var crypto = require('crypto');
+var nodemailer = require('nodemailer');
 var db = require('../db');
 
 // GET dropdown-menu template.
@@ -39,11 +41,21 @@ router.get('/copy', function (req, res) {
     res.render('templates/copy', {});
 });
 
-/* GET login page. */
+/* GET login dialog. */
 router.get('/login', function (req, res) {
     res.render('login');
 });
 
+/* GET reset password page. */
+router.get('/reset', function (req, res) {
+    res.render('reset');
+});
+
+/* GET reset password page. */
+router.get('/resetModal', function (req, res) {
+    res.render('resetModal');
+});
+
 /**
  * Register new account.
  */
@@ -73,6 +85,96 @@ router.post('/register', function (req, res) {
     });
 });
 
+router.post('/restore', function(req, res) {
+    var token = crypto.randomBytes(20).toString('hex');
+
+    db.Account.findOne({ email: req.body.email }, function(err, user) {
+        if (err)
+            return res.status(401).send('No account with that email address exists!');
+
+        user.resetPasswordToken = token;
+        user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
+
+        user.save(function(err) {
+            if (err)
+                return res.status(401).send('Failed to send e-mail with reset link!');
+
+            var transporter  = nodemailer.createTransport({
+                service: 'gmail',
+                auth: {
+                    user: '!!! YOUR USERNAME !!!',
+                    pass: '!!! YOUR PASSWORD !!!'
+                }
+            });
+
+            var mailOptions = {
+                from: 'passwordreset@YOUR.DOMAIN',
+                to: user.email,
+                subject: 'Password Reset',
+                text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
+                'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
+                'http://' + req.headers.host + '/reset/' + token + '\n\n' +
+                'If you did not request this, please ignore this email and your password will remain unchanged.\n' +
+                'Link will be valid for one hour.\n'
+            };
+
+            transporter.sendMail(mailOptions, function(err, info){
+                if (err)
+                    return res.status(401).send('Failed to send e-mail with reset link!');
+
+                console.log('Message sent: ' + info.response);
+
+                return res.status(500).send('An e-mail has been sent with further instructions.');
+            });
+        });
+    });
+});
+
+router.post('/reset/:token', function(req, res) {
+    db.Account.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) {
+        if (err)
+            return res.status(500).send(err);
+
+        user.setPassword(newPassword, function (err, updatedUser) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            user.resetPasswordToken = undefined;
+            user.resetPasswordExpires = undefined;
+
+            updatedUser.save(function (err) {
+                if (err)
+                    return res.status(500).send(err.message);
+
+                var transporter  = nodemailer.createTransport({
+                    service: 'gmail',
+                    auth: {
+                        user: '!!! YOUR USERNAME !!!',
+                        pass: '!!! YOUR PASSWORD !!!'
+                    }
+                });
+
+                var mailOptions = {
+                    from: 'passwordreset@YOUR.DOMAIN',
+                    to: user.email,
+                    subject: 'Your password has been changed',
+                    text: 'Hello,\n\n' +
+                    'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
+                };
+
+                transporter.sendMail(mailOptions, function(err, info){
+                    if (err)
+                        return res.status(401).send('Failed to send e-mail with reset link!');
+
+                    console.log('Message sent: ' + info.response);
+
+                    res.redirect('/login');
+                });
+            });
+        });
+    });
+});
+
 /**
  * Login in exist account.
  */
@@ -110,14 +212,4 @@ router.get('/', function (req, res) {
         res.render('index');
 });
 
-///* GET sql page. */
-//router.get('/sql', function(req, res) {
-//    res.render('sql', { user: req.user });
-//});
-//
-///* GET clients page. */
-//router.get('/clients', function(req, res) {
-//    res.render('clients', { user: req.user });
-//});
-
 module.exports = router;

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/views/configuration/summary.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/summary.jade b/modules/control-center-web/src/main/js/views/configuration/summary.jade
index 86661d1..07e177e 100644
--- a/modules/control-center-web/src/main/js/views/configuration/summary.jade
+++ b/modules/control-center-web/src/main/js/views/configuration/summary.jade
@@ -97,7 +97,7 @@ block content
                     .panel-collapse(role='tabpanel' bs-collapse-target)
                         div(ng-show='selectedItem')
                             .details-row(ng-repeat='field in clientFields')
-                                +form-row-custom(['col-sm-3'], ['col-sm-3'])
+                                +form-row-custom(['col-sm-3'], ['col-sm-3'], 'backupItem')
                             div(bs-tabs style='margin-top: 0.65em')
                                 div(title='<img src="/images/xml.png" width="16px" height="16px"/> XML' bs-pane)
                                     div(ui-ace='{ onLoad: aceInit, mode: "xml" }' ng-model='xmlClient')

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/views/includes/controls.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/includes/controls.jade b/modules/control-center-web/src/main/js/views/includes/controls.jade
index fccafac..3f4cd49 100644
--- a/modules/control-center-web/src/main/js/views/includes/controls.jade
+++ b/modules/control-center-web/src/main/js/views/includes/controls.jade
@@ -209,10 +209,10 @@ mixin table-group-item-edit(fieldName, className, direction, index)
             select.form-control(id=focusIdNext2 ng-model=direction ng-options='item.value as item.label for item in {{sortDirections}}' on-enter=btnVisibleAndSave on-escape='tableReset()')
 
 mixin form-row
-    +form-row-custom(['col-sm-2'], ['col-sm-4'])
+    +form-row-custom(['col-sm-2'], ['col-sm-4'], 'backupItem')
 
-mixin form-row-custom(lblClasses, fieldClasses)
-    - var fieldMdl = 'getModel(backupItem, field)[field.model]';
+mixin form-row-custom(lblClasses, fieldClasses, dataSource)
+    - var fieldMdl = 'getModel('+ dataSource + ', field)[field.model]';
     - var fieldCommon = {'ng-model': fieldMdl, 'ng-required': 'field.required || required(field)'};
     - var fieldRequiredClass = '{true: "required"}[field.required || required(field)]'
     - var fieldHide = '{{field.hide}}'

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/views/login.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/login.jade b/modules/control-center-web/src/main/js/views/login.jade
index d32378f..47bb2c5 100644
--- a/modules/control-center-web/src/main/js/views/login.jade
+++ b/modules/control-center-web/src/main/js/views/login.jade
@@ -20,16 +20,15 @@ mixin lbl(txt)
 .modal.center(role='dialog')
     .modal-dialog
         .modal-content
-            .modal-header.header
-                div(id='errors-container')
+            #errors-container.modal-header.header
                 button.close(type='button' ng-click='$hide()' aria-hidden='true') &times;
                 h1.navbar-brand
                     a(href='/') Apache Ignite Web Configurator
                 h4.modal-title(style='padding-right: 55px') Authentication
                 p(style='padding-right: 55px') Log in or register in order to collaborate
-            form.form-horizontal(name='loginForm')
+            form.form-horizontal(name='loginForm' ng-init='action == "login"')
                 .modal-body.row
-                    .col-sm-9.login.col-sm-offset-1
+                    .col-sm-9.col-sm-offset-1
                         .details-row(ng-show='action == "register"')
                             +lbl('Full Name:')
                             .col-sm-9
@@ -38,17 +37,19 @@ mixin lbl(txt)
                             +lbl('Email:')
                             .col-sm-9
                                 input#user_email.form-control(enter-focus-next enter-focus-next-id='user_password' type='email' ng-model='user_info.email' placeholder='you@domain.com' required)
-                        .details-row
+                        .details-row(ng-show='action != "restore"')
                             +lbl('Password:')
                             .col-sm-9
-                                input#user_password.form-control(enter-focus-next enter-focus-next-id='user_confirm' type='password' ng-model='user_info.password' placeholder='Password' required on-enter='action == "login" && auth(action, user_info)')
-                        .details-row(ng-show='action == "register"')
+                                input#user_password.form-control(enter-focus-next enter-focus-next-id='user_confirm' type='password' ng-model='user_info.password' placeholder='Password' ng-required='action != "restore"' on-enter='action == "login" && auth(action, user_info)')
+                        .details-row(ng-if='action == "register"')
                             +lbl('Confirm:')
-                            .col-sm-9.input-tip.has-feedback
-                                input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm password' required on-enter='auth(action, user_info)')
+                            .col-sm-9
+                                input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm password' ng-required='action == "register"' on-enter='auth(action, user_info)')
             .modal-footer
-                a.show-signup.ng-hide(event-focus='click' event-focus-id='user_email' ng-show='action != "login"' ng-click='action = "login";') log in
-                a.show-signup(event-focus='click' event-focus-id='user_name' ng-show='action != "register"' ng-click='action = "register"') sign up
-                | &nbsp;or&nbsp;
-                button.btn.btn-primary(ng-show='action == "login"' ng-click='auth(action, user_info)') Log In
-                button.btn.btn-primary(ng-show='action == "register"' ng-disabled='loginForm.$invalid || user_info.password != user_info.confirm' ng-click='auth(action, user_info)') Sign Up
+                a.labelField(ng-show='action != "restore"' ng-click='action = "restore"' event-focus='click' event-focus-id='user_email') Forgot password?
+                a.labelField(ng-show='action == "restore"' ng-click='action = "login"' event-focus='click' event-focus-id='user_email') Log In
+                a.labelLogin(ng-show='action == "register"' event-focus='click' event-focus-id='user_email' ng-click='action = "login";') Log In
+                a.labelLogin(ng-show='action == "login"' event-focus='click' event-focus-id='user_name' ng-click='action = "register"') Sign Up
+                button.btn.btn-primary(ng-show='action == "login"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Log In
+                button.btn.btn-primary(ng-show='action == "register"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Sign Up
+                button.btn.btn-primary(ng-show='action == "restore"' ng-disabled='loginForm.$invalid' ng-click='auth(action, user_info)') Reset Password

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/views/reset.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/reset.jade b/modules/control-center-web/src/main/js/views/reset.jade
new file mode 100644
index 0000000..c9c4d88
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/reset.jade
@@ -0,0 +1,22 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+
+extends templates/layout
+
+block container
+    .row
+        .text-center(ng-controller='auth')
+            button.btn.btn-primary(ng-click='reset()' href='#') Reset Password

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/views/resetModal.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/resetModal.jade b/modules/control-center-web/src/main/js/views/resetModal.jade
new file mode 100644
index 0000000..0878525
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/resetModal.jade
@@ -0,0 +1,34 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+
+.modal.center(role='dialog')
+    .modal-dialog
+        .modal-content
+            #errors-container.modal-header.header
+                button.close(type='button' ng-click='$hide()' aria-hidden='true') &times;
+                h4.modal-title(style='padding-right: 55px') Reset password
+            form.form-horizontal(name='loginForm' ng-init='action == "login"')
+                .modal-body.row
+                    .col-sm-9.col-sm-offset-1
+                        .details-row
+                            label.col-sm-4.required New password:
+                            .col-sm-8
+                                input#user_password.form-control(enter-focus-next enter-focus-next-id='user_confirm' type='password' ng-model='user_info.password' placeholder='New password' required)
+                        .details-row
+                            label.col-sm-4.required Confirm:
+                            .col-sm-8
+                                input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm new password' ng-required='action == "register"' on-enter='resetPassword(user_info)')
+            .modal-footer
+                button.btn.btn-primary(ng-disabled='resetForm.$invalid' ng-click='resetPassword(user_info)') Reset Password
+

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/2aa1ce8a/modules/control-center-web/src/main/js/views/templates/layout.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/layout.jade b/modules/control-center-web/src/main/js/views/templates/layout.jade
index 2e3fc5d..10aaa75 100644
--- a/modules/control-center-web/src/main/js/views/templates/layout.jade
+++ b/modules/control-center-web/src/main/js/views/templates/layout.jade
@@ -17,7 +17,7 @@
 doctype html
 html(ng-app='ignite-web-control-center' ng-init='user = #{JSON.stringify(user)}; becomeUsed = #{becomeUsed}')
     head
-        title= title
+        title=title
 
         block css
             // Bootstrap