You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by an...@apache.org on 2015/07/31 04:28:50 UTC

[37/41] incubator-ignite git commit: # ignite-843 Rename

# ignite-843 Rename


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

Branch: refs/heads/ignite-1121
Commit: 96522874ebc7809a8f4256ce2e4aa681c2b9241c
Parents: cb2ecb5
Author: Andrey <an...@gridgain.com>
Authored: Wed Jul 29 10:39:34 2015 +0700
Committer: Andrey <an...@gridgain.com>
Committed: Wed Jul 29 10:39:34 2015 +0700

----------------------------------------------------------------------
 .../control-center-web/licenses/apache-2.0.txt  |  202 +++
 .../control-center-web/src/main/js/.gitignore   |    4 +
 .../control-center-web/src/main/js/DEVNOTES.txt |   21 +
 modules/control-center-web/src/main/js/app.js   |  156 +++
 modules/control-center-web/src/main/js/bin/www  |   85 ++
 .../src/main/js/config/default.json             |    8 +
 .../src/main/js/controllers/admin-controller.js |   68 +
 .../js/controllers/cache-viewer-controller.js   |   77 ++
 .../main/js/controllers/caches-controller.js    |  349 +++++
 .../main/js/controllers/clusters-controller.js  |  308 +++++
 .../src/main/js/controllers/common-module.js    |  484 +++++++
 .../main/js/controllers/metadata-controller.js  |  709 ++++++++++
 .../src/main/js/controllers/models/caches.json  |  943 +++++++++++++
 .../main/js/controllers/models/clusters.json    |  909 +++++++++++++
 .../main/js/controllers/models/metadata.json    |  252 ++++
 .../src/main/js/controllers/models/sql.json     |    5 +
 .../src/main/js/controllers/models/summary.json |  163 +++
 .../main/js/controllers/profile-controller.js   |   51 +
 .../src/main/js/controllers/sql-controller.js   |   84 ++
 .../main/js/controllers/summary-controller.js   |  170 +++
 modules/control-center-web/src/main/js/db.js    |  370 +++++
 .../src/main/js/helpers/configuration-loader.js |   22 +
 .../src/main/js/helpers/data-structures.js      |   84 ++
 .../control-center-web/src/main/js/package.json |   49 +
 .../src/main/js/public/favicon.ico              |  Bin 0 -> 1150 bytes
 .../src/main/js/public/images/docker.png        |  Bin 0 -> 994 bytes
 .../src/main/js/public/images/java.png          |  Bin 0 -> 170 bytes
 .../src/main/js/public/images/logo.png          |  Bin 0 -> 8148 bytes
 .../src/main/js/public/images/xml.png           |  Bin 0 -> 232 bytes
 .../src/main/js/public/stylesheets/style.scss   | 1270 ++++++++++++++++++
 .../src/main/js/routes/admin.js                 |   79 ++
 .../src/main/js/routes/caches.js                |  105 ++
 .../src/main/js/routes/clusters.js              |  104 ++
 .../src/main/js/routes/generator/common.js      |  312 +++++
 .../src/main/js/routes/generator/docker.js      |   58 +
 .../src/main/js/routes/generator/java.js        |  788 +++++++++++
 .../src/main/js/routes/generator/xml.js         |  736 ++++++++++
 .../src/main/js/routes/metadata.js              |   95 ++
 .../src/main/js/routes/profile.js               |   97 ++
 .../src/main/js/routes/public.js                |  123 ++
 .../src/main/js/routes/sql.js                   |   24 +
 .../src/main/js/routes/summary.js               |  108 ++
 .../src/main/js/views/configuration/caches.jade |   74 +
 .../main/js/views/configuration/clusters.jade   |   77 ++
 .../main/js/views/configuration/metadata.jade   |  121 ++
 .../main/js/views/configuration/sidebar.jade    |   39 +
 .../main/js/views/configuration/summary.jade    |  113 ++
 .../src/main/js/views/error.jade                |   22 +
 .../src/main/js/views/includes/controls.jade    |  350 +++++
 .../src/main/js/views/includes/footer.jade      |   22 +
 .../src/main/js/views/includes/header.jade      |   39 +
 .../src/main/js/views/index.jade                |   30 +
 .../src/main/js/views/login.jade                |   55 +
 .../src/main/js/views/settings/admin.jade       |   58 +
 .../src/main/js/views/settings/profile.jade     |   58 +
 .../src/main/js/views/sql/sql.jade              |   85 ++
 .../src/main/js/views/templates/confirm.jade    |   27 +
 .../src/main/js/views/templates/copy.jade       |   31 +
 .../src/main/js/views/templates/layout.jade     |   61 +
 .../src/main/js/views/templates/select.jade     |   26 +
 .../src/main/js/views/templates/tab.jade        |   26 +
 .../web-control-center/licenses/apache-2.0.txt  |  202 ---
 .../web-control-center/src/main/js/.gitignore   |    4 -
 .../web-control-center/src/main/js/DEVNOTES.txt |   21 -
 modules/web-control-center/src/main/js/app.js   |  156 ---
 modules/web-control-center/src/main/js/bin/www  |   85 --
 .../src/main/js/config/default.json             |    8 -
 .../src/main/js/controllers/admin-controller.js |   68 -
 .../js/controllers/cache-viewer-controller.js   |   77 --
 .../main/js/controllers/caches-controller.js    |  349 -----
 .../main/js/controllers/clusters-controller.js  |  308 -----
 .../src/main/js/controllers/common-module.js    |  484 -------
 .../main/js/controllers/metadata-controller.js  |  709 ----------
 .../src/main/js/controllers/models/caches.json  |  943 -------------
 .../main/js/controllers/models/clusters.json    |  909 -------------
 .../main/js/controllers/models/metadata.json    |  252 ----
 .../src/main/js/controllers/models/sql.json     |    5 -
 .../src/main/js/controllers/models/summary.json |  163 ---
 .../main/js/controllers/profile-controller.js   |   51 -
 .../src/main/js/controllers/sql-controller.js   |   84 --
 .../main/js/controllers/summary-controller.js   |  170 ---
 modules/web-control-center/src/main/js/db.js    |  370 -----
 .../src/main/js/helpers/configuration-loader.js |   22 -
 .../src/main/js/helpers/data-structures.js      |   84 --
 .../web-control-center/src/main/js/package.json |   49 -
 .../src/main/js/public/favicon.ico              |  Bin 1150 -> 0 bytes
 .../src/main/js/public/images/docker.png        |  Bin 994 -> 0 bytes
 .../src/main/js/public/images/java.png          |  Bin 170 -> 0 bytes
 .../src/main/js/public/images/logo.png          |  Bin 8148 -> 0 bytes
 .../src/main/js/public/images/xml.png           |  Bin 232 -> 0 bytes
 .../src/main/js/public/stylesheets/style.scss   | 1270 ------------------
 .../src/main/js/routes/admin.js                 |   79 --
 .../src/main/js/routes/caches.js                |  105 --
 .../src/main/js/routes/clusters.js              |  104 --
 .../src/main/js/routes/generator/common.js      |  312 -----
 .../src/main/js/routes/generator/docker.js      |   58 -
 .../src/main/js/routes/generator/java.js        |  788 -----------
 .../src/main/js/routes/generator/xml.js         |  736 ----------
 .../src/main/js/routes/metadata.js              |   95 --
 .../src/main/js/routes/profile.js               |   97 --
 .../src/main/js/routes/public.js                |  123 --
 .../src/main/js/routes/sql.js                   |   24 -
 .../src/main/js/routes/summary.js               |  108 --
 .../src/main/js/views/configuration/caches.jade |   74 -
 .../main/js/views/configuration/clusters.jade   |   77 --
 .../main/js/views/configuration/metadata.jade   |  121 --
 .../main/js/views/configuration/sidebar.jade    |   39 -
 .../main/js/views/configuration/summary.jade    |  113 --
 .../src/main/js/views/error.jade                |   22 -
 .../src/main/js/views/includes/controls.jade    |  350 -----
 .../src/main/js/views/includes/footer.jade      |   22 -
 .../src/main/js/views/includes/header.jade      |   39 -
 .../src/main/js/views/index.jade                |   30 -
 .../src/main/js/views/login.jade                |   55 -
 .../src/main/js/views/settings/admin.jade       |   58 -
 .../src/main/js/views/settings/profile.jade     |   58 -
 .../src/main/js/views/sql/sql.jade              |   85 --
 .../src/main/js/views/templates/confirm.jade    |   27 -
 .../src/main/js/views/templates/copy.jade       |   31 -
 .../src/main/js/views/templates/layout.jade     |   61 -
 .../src/main/js/views/templates/select.jade     |   26 -
 .../src/main/js/views/templates/tab.jade        |   26 -
 122 files changed, 10786 insertions(+), 10786 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/licenses/apache-2.0.txt
----------------------------------------------------------------------
diff --git a/modules/control-center-web/licenses/apache-2.0.txt b/modules/control-center-web/licenses/apache-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/modules/control-center-web/licenses/apache-2.0.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/.gitignore
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/.gitignore b/modules/control-center-web/src/main/js/.gitignore
new file mode 100644
index 0000000..65f2596
--- /dev/null
+++ b/modules/control-center-web/src/main/js/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+*.idea
+*.log
+*.css
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/DEVNOTES.txt
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/DEVNOTES.txt b/modules/control-center-web/src/main/js/DEVNOTES.txt
new file mode 100644
index 0000000..e8c558c
--- /dev/null
+++ b/modules/control-center-web/src/main/js/DEVNOTES.txt
@@ -0,0 +1,21 @@
+Ignite Web Control Center Instructions
+======================================
+
+How to deploy:
+
+1. Install locally NodeJS using installer from site https://nodejs.org for your OS.
+2. Install locally MongoDB folow instructions from site http://docs.mongodb.org/manual/installation
+3. Checkout ignite-843 branch.
+4. Change directory '$IGNITE_HOME/modules/web-control-center/src/main/js'.
+5. Run "npm install" in terminal for download all dependencies.
+
+Steps 1 - 5 should be executed once.
+
+How to run:
+
+1. Run MongoDB.
+ 1.1 In terminal change dir to $MONGO_ISNTALL_DIR/server/3.0/bin.
+ 1.2 Run "mongod".
+2. In new terminal change directory '$IGNITE_HOME/modules/web-control-center/src/main/js'.
+3. Start application by executing "npm start".
+4. In browser open: http://localhost:3000
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/app.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app.js b/modules/control-center-web/src/main/js/app.js
new file mode 100644
index 0000000..a67afc8
--- /dev/null
+++ b/modules/control-center-web/src/main/js/app.js
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+var flash = require('connect-flash');
+var express = require('express');
+var path = require('path');
+var favicon = require('serve-favicon');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var session = require('express-session');
+var mongoStore = require('connect-mongo')(session);
+
+var publicRoutes = require('./routes/public');
+var clustersRouter = require('./routes/clusters');
+var cachesRouter = require('./routes/caches');
+var metadataRouter = require('./routes/metadata');
+var summary = require('./routes/summary');
+var adminRouter = require('./routes/admin');
+var profileRouter = require('./routes/profile');
+var sqlRouter = require('./routes/sql');
+
+var passport = require('passport');
+
+var db = require('./db');
+
+var app = express();
+
+// Views engine setup.
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'jade');
+
+// Site favicon.
+app.use(favicon(__dirname + '/public/favicon.ico'));
+
+app.use(logger('dev'));
+
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({extended: false}));
+
+app.use(require('node-sass-middleware')({
+    /* Options */
+    src: path.join(__dirname, 'public'),
+    dest: path.join(__dirname, 'public'),
+    debug: true,
+    outputStyle: 'nested'
+}));
+
+app.use(express.static(path.join(__dirname, 'public')));
+app.use(express.static(path.join(__dirname, 'controllers')));
+app.use(express.static(path.join(__dirname, 'helpers')));
+
+app.use(cookieParser('keyboard cat'));
+
+app.use(session({
+    secret: 'keyboard cat',
+    resave: false,
+    saveUninitialized: true,
+    store: new mongoStore({
+        mongooseConnection: db.mongoose.connection
+    })
+}));
+
+app.use(flash());
+
+app.use(passport.initialize());
+app.use(passport.session());
+
+passport.serializeUser(db.Account.serializeUser());
+passport.deserializeUser(db.Account.deserializeUser());
+
+passport.use(db.Account.createStrategy());
+
+var mustAuthenticated = function (req, res, next) {
+    req.isAuthenticated() ? next() : res.redirect('/');
+};
+
+var adminOnly = function(req, res, next) {
+    req.isAuthenticated() && req.user.admin ? next() : res.sendStatus(403);
+};
+
+app.all('/configuration/*', mustAuthenticated);
+
+app.all('*', function(req, res, next) {
+    var becomeUsed = req.session.viewedUser && req.user.admin;
+
+    res.locals.user = becomeUsed ? req.session.viewedUser : req.user;
+    res.locals.becomeUsed = becomeUsed;
+
+    req.currentUserId = function() {
+        if (!req.user)
+            return null;
+
+        if (req.session.viewedUser && req.user.admin)
+            return req.session.viewedUser._id;
+
+        return req.user._id;
+    };
+
+    next();
+});
+
+app.use('/', publicRoutes);
+app.use('/admin', mustAuthenticated, adminOnly, adminRouter);
+app.use('/profile', mustAuthenticated, profileRouter);
+
+app.use('/configuration/clusters', clustersRouter);
+app.use('/configuration/caches', cachesRouter);
+app.use('/configuration/metadata', metadataRouter);
+app.use('/configuration/summary', summary);
+app.use('/sql', sqlRouter);
+
+// Catch 404 and forward to error handler.
+app.use(function (req, res, next) {
+    var err = new Error('Not Found: ' + req.originalUrl);
+    err.status = 404;
+    next(err);
+});
+
+// Error handlers.
+
+// Development error handler: will print stacktrace.
+if (app.get('env') === 'development') {
+    app.use(function (err, req, res) {
+        res.status(err.status || 500);
+        res.render('error', {
+            message: err.message,
+            error: err
+        });
+    });
+}
+
+// Production error handler: no stacktraces leaked to user.
+app.use(function (err, req, res) {
+    res.status(err.status || 500);
+    res.render('error', {
+        message: err.message,
+        error: {}
+    });
+});
+
+module.exports = app;

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/bin/www
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/bin/www b/modules/control-center-web/src/main/js/bin/www
new file mode 100644
index 0000000..4cf0583
--- /dev/null
+++ b/modules/control-center-web/src/main/js/bin/www
@@ -0,0 +1,85 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+var app = require('../app');
+var config = require('../helpers/configuration-loader.js');
+var debug = require('debug')('ignite-web-control-center:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+var port = normalizePort(process.env.PORT || config.get('express:port'));
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+function normalizePort(val) {
+  var port = parseInt(val, 10);
+
+  if (isNaN(port)) {
+    // named pipe
+    return val;
+  }
+
+  if (port >= 0) {
+    // port number
+    return port;
+  }
+
+  return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+function onError(error) {
+  if (error.syscall !== 'listen') {
+    throw error;
+  }
+
+  var bind = typeof port === 'string'
+    ? 'Pipe ' + port
+    : 'Port ' + port;
+
+  // handle specific listen errors with friendly messages
+  switch (error.code) {
+    case 'EACCES':
+      console.error(bind + ' requires elevated privileges');
+      process.exit(1);
+      break;
+    case 'EADDRINUSE':
+      console.error(bind + ' is already in use');
+      process.exit(1);
+      break;
+    default:
+      throw error;
+  }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+function onListening() {
+  var addr = server.address();
+  var bind = typeof addr === 'string'
+    ? 'pipe ' + addr
+    : 'port ' + addr.port;
+
+  debug('Listening on ' + bind);
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/config/default.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/config/default.json b/modules/control-center-web/src/main/js/config/default.json
new file mode 100644
index 0000000..72dbd4e
--- /dev/null
+++ b/modules/control-center-web/src/main/js/config/default.json
@@ -0,0 +1,8 @@
+{
+    "express": {
+        "port": 3000
+    },
+    "mongoDB": {
+        "url": "mongodb://localhost/web-control-center"
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/controllers/admin-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/admin-controller.js b/modules/control-center-web/src/main/js/controllers/admin-controller.js
new file mode 100644
index 0000000..09490fe
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/admin-controller.js
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+controlCenterModule.controller('adminController', ['$scope', '$http', '$common', '$confirm', function ($scope, $http, $common, $confirm) {
+    $scope.users = null;
+
+    function reload() {
+        $http.post('admin/list')
+            .success(function (data) {
+                $scope.users = data;
+            })
+            .error(function (errMsg) {
+                $common.showError($common.errorMessage(errMsg));
+            });
+    }
+
+    reload();
+
+    $scope.removeUser = function (user) {
+        $confirm.show('Are you sure you want to remove user: "' + user.username + '"?').then(function () {
+            $http.post('admin/remove', {userId: user._id}).success(
+                function () {
+                    var i = _.findIndex($scope.users, function (u) {
+                        return u._id == user._id;
+                    });
+
+                    if (i >= 0)
+                        $scope.users.splice(i, 1);
+
+                    $common.showInfo('User has been removed: "' + user.username + '"');
+                }).error(function (errMsg) {
+                    $common.showError('Failed to remove user: "' + $common.errorMessage(errMsg) + '"');
+                });
+        });
+    };
+
+    $scope.toggleAdmin = function (user) {
+        if (user.adminChanging)
+            return;
+
+        user.adminChanging = true;
+
+        $http.post('admin/save', {userId: user._id, adminFlag: user.admin}).success(
+            function () {
+                $common.showInfo('Admin right was successfully toggled for user: "' + user.username + '"');
+
+                user.adminChanging = false;
+            }).error(function (errMsg) {
+                $common.showError('Failed to toggle admin right for user: "' + $common.errorMessage(errMsg) + '"');
+
+                user.adminChanging = false;
+            });
+    }
+}]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js b/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js
new file mode 100644
index 0000000..6e0c130
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/cache-viewer-controller.js
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+var demoResults = [
+    {
+        id: 256,
+        s: 'com.foo.User@3213',
+        fields: {
+            id: 256,
+            firstName: 'Ivan',
+            lastName: 'Ivanov',
+            old: 23
+        }
+    },
+
+    {
+        id: 384,
+        s: 'com.foo.User@23214',
+        fields: {
+            id: 384,
+            firstName: 'Sergey',
+            lastName: 'Petrov',
+            old: 28
+        }
+    },
+
+    {
+        id: 923,
+        s: 'com.foo.User@93494',
+        fields: {
+            id: 923,
+            firstName: 'Andrey',
+            lastName: 'Sidorov',
+            old: 28
+        }
+    }
+];
+
+var demoCaches = ['Users', 'Organizations', 'Cities'];
+
+controlCenterModule.controller('cacheViewerController', ['$scope', '$http', '$common', function ($scope, $http, $common) {
+    $scope.results = demoResults;
+
+    $scope.caches = demoCaches;
+
+    $scope.defCache = $scope.caches.length > 0 ? $scope.caches[0] : null;
+
+    var sqlEditor = ace.edit('querySql');
+
+    sqlEditor.setOptions({
+        highlightActiveLine: false,
+        showPrintMargin: false,
+        showGutter: true,
+        theme: "ace/theme/chrome",
+        mode: "ace/mode/sql",
+        fontSize: 14
+    });
+
+    sqlEditor.setValue("select u.id from User u where u.name like 'aaaa';");
+
+    sqlEditor.selection.clearSelection()
+
+}]);

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/controllers/caches-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/caches-controller.js b/modules/control-center-web/src/main/js/controllers/caches-controller.js
new file mode 100644
index 0000000..e995233
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/caches-controller.js
@@ -0,0 +1,349 @@
+/*
+ * 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.
+ */
+
+controlCenterModule.controller('cachesController', ['$scope', '$http', '$common', '$confirm', '$copy', '$table', function ($scope, $http, $common, $confirm, $copy, $table) {
+        $scope.joinTip = $common.joinTip;
+        $scope.getModel = $common.getModel;
+        $scope.javaBuildInTypes = $common.javaBuildInTypes;
+
+        $scope.tableReset = $table.tableReset;
+        $scope.tableNewItem = $table.tableNewItem;
+        $scope.tableNewItemActive = $table.tableNewItemActive;
+        $scope.tableEditing = $table.tableEditing;
+        $scope.tableStartEdit = $table.tableStartEdit;
+        $scope.tableRemove = $table.tableRemove;
+
+        $scope.tableSimpleSave = $table.tableSimpleSave;
+        $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+        $scope.tableSimpleUp = $table.tableSimpleUp;
+        $scope.tableSimpleDown = $table.tableSimpleDown;
+        $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
+
+        $scope.tablePairSave = $table.tablePairSave;
+        $scope.tablePairSaveVisible = $table.tablePairSaveVisible;
+
+        $scope.atomicities = [
+            {value: 'ATOMIC', label: 'ATOMIC'},
+            {value: 'TRANSACTIONAL', label: 'TRANSACTIONAL'}
+        ];
+
+        $scope.modes = [
+            {value: 'PARTITIONED', label: 'PARTITIONED'},
+            {value: 'REPLICATED', label: 'REPLICATED'},
+            {value: 'LOCAL', label: 'LOCAL'}
+        ];
+
+        $scope.atomicWriteOrderModes = [
+            {value: 'CLOCK', label: 'CLOCK'},
+            {value: 'PRIMARY', label: 'PRIMARY'}
+        ];
+
+        $scope.memoryModes = [
+            {value: 'ONHEAP_TIERED', label: 'ONHEAP_TIERED'},
+            {value: 'OFFHEAP_TIERED', label: 'OFFHEAP_TIERED'},
+            {value: 'OFFHEAP_VALUES', label: 'OFFHEAP_VALUES'}
+        ];
+
+        $scope.evictionPolicies = [
+            {value: 'LRU', label: 'LRU'},
+            {value: 'RND', label: 'Random'},
+            {value: 'FIFO', label: 'FIFO'},
+            {value: 'SORTED', label: 'Sorted'},
+            {value: undefined, label: 'Not set'}
+        ];
+
+        $scope.rebalanceModes = [
+            {value: 'SYNC', label: 'SYNC'},
+            {value: 'ASYNC', label: 'ASYNC'},
+            {value: 'NONE', label: 'NONE'}
+        ];
+
+        $scope.cacheStoreFactories = [
+            {value: 'CacheJdbcPojoStoreFactory', label: 'JDBC POJO store factory'},
+            {value: 'CacheJdbcBlobStoreFactory', label: 'JDBC BLOB store factory'},
+            {value: 'CacheHibernateBlobStoreFactory', label: 'Hibernate BLOB store factory'},
+            {value: undefined, label: 'Not set'}
+        ];
+
+        $scope.cacheStoreJdbcDialects = [
+            {value: 'Oracle', label: 'Oracle'},
+            {value: 'DB2', label: 'IBM DB2'},
+            {value: 'SQLServer', label: 'Microsoft SQL Server'},
+            {value: 'MySQL', label: 'My SQL'},
+            {value: 'PostgreSQL', label: 'Postgre SQL'},
+            {value: 'H2', label: 'H2 database'}
+        ];
+
+        $scope.general = [];
+        $scope.advanced = [];
+
+        $http.get('/models/caches.json')
+            .success(function (data) {
+                $scope.screenTip = data.screenTip;
+                $scope.general = data.general;
+                $scope.advanced = data.advanced;
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+
+        $scope.caches = [];
+        $scope.queryMetadata = [];
+        $scope.storeMetadata = [];
+
+        $scope.required = function (field) {
+            var model = $common.isDefined(field.path) ? field.path + '.' + field.model : field.model;
+
+            var backupItem = $scope.backupItem;
+
+            var memoryMode = backupItem.memoryMode;
+
+            var onHeapTired = memoryMode == 'ONHEAP_TIERED';
+            var offHeapTired = memoryMode == 'OFFHEAP_TIERED';
+
+            var offHeapMaxMemory = backupItem.offHeapMaxMemory;
+
+            if (model == 'offHeapMaxMemory' && offHeapTired)
+                return true;
+
+            if (model == 'evictionPolicy.kind' && onHeapTired)
+                return backupItem.swapEnabled || ($common.isDefined(offHeapMaxMemory) && offHeapMaxMemory >= 0);
+
+            return false;
+        };
+
+        $scope.tableSimpleValid = function (item, field, fx, index) {
+            var model = item[field.model];
+
+            if ($common.isDefined(model)) {
+                var idx = _.indexOf(model, fx);
+
+                // Found itself.
+                if (index >= 0 && index == idx)
+                    return true;
+
+                // Found duplicate.
+                if (idx >= 0) {
+                    $common.showError('SQL function such class name already exists!');
+
+                    return false;
+                }
+            }
+
+            return true;
+        };
+
+        $scope.tablePairValid = function (item, field, keyCls, valCls, index) {
+            var model = item[field.model];
+
+            if ($common.isDefined(model)) {
+                var idx = _.findIndex(model, function (pair) {
+                    return pair.keyClass == keyCls
+                });
+
+                // Found itself.
+                if (index >= 0 && index == idx)
+                    return true;
+
+                // Found duplicate.
+                if (idx >= 0) {
+                    $common.showError('Indexed type with such key class already exists!');
+
+                    return false;
+                }
+            }
+
+            return true;
+        };
+
+        // When landing on the page, get caches and show them.
+        $http.post('caches/list')
+            .success(function (data) {
+                $scope.spaces = data.spaces;
+                $scope.caches = data.caches;
+
+                _.forEach(data.metadatas, function (meta) {
+                    var kind = meta.kind;
+
+                    if (kind == 'query' || kind == 'both')
+                        $scope.queryMetadata.push(meta);
+
+                    if (kind == 'store' || kind == 'both')
+                        $scope.storeMetadata.push(meta);
+                });
+
+                var restoredItem = angular.fromJson(sessionStorage.cacheBackupItem);
+
+                if (restoredItem) {
+                    if (restoredItem._id) {
+                        var idx = _.findIndex($scope.caches, function (cache) {
+                            return cache._id == restoredItem._id;
+                        });
+
+                        if (idx >= 0) {
+                            $scope.selectedItem = $scope.caches[idx];
+                            $scope.backupItem = restoredItem;
+                        }
+                        else
+                            sessionStorage.removeItem('cacheBackupItem');
+                    }
+                    else
+                        $scope.backupItem = restoredItem;
+                }
+                else if ($scope.caches.length > 0)
+                    $scope.selectItem($scope.caches[0]);
+
+                $scope.$watch('backupItem', function (val) {
+                    if (val)
+                        sessionStorage.cacheBackupItem = angular.toJson(val);
+                }, true);
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+
+        $scope.selectItem = function (item) {
+            $table.tableReset();
+
+            $scope.selectedItem = item;
+            $scope.backupItem = angular.copy(item);
+        };
+
+        // Add new cache.
+        $scope.createItem = function () {
+            $table.tableReset();
+
+            $scope.backupItem = {mode: 'PARTITIONED', atomicityMode: 'ATOMIC', readFromBackup: true, copyOnRead: true};
+            $scope.backupItem.queryMetadata = [];
+            $scope.backupItem.spaceMetadata = [];
+            $scope.backupItem.space = $scope.spaces[0]._id;
+        };
+
+        // Check cache logical consistency.
+        function validate(item) {
+            var cacheStoreFactorySelected = item.cacheStoreFactory && item.cacheStoreFactory.kind;
+
+            if (cacheStoreFactorySelected && !(item.readThrough || item.writeThrough)) {
+                $common.showError('Store is configured but read/write through are not enabled!');
+
+                return false;
+            }
+
+            if ((item.readThrough || item.writeThrough) && !cacheStoreFactorySelected) {
+                $common.showError('Read / write through are enabled but store is not configured!');
+
+                return false;
+            }
+
+            if (item.writeBehindEnabled && !cacheStoreFactorySelected) {
+                $common.showError('Write behind enabled but store is not configured!');
+
+                return false;
+            }
+
+            return true;
+        }
+
+        // Save cache into database.
+        function save(item) {
+            $http.post('caches/save', item)
+                .success(function (_id) {
+                    var idx = _.findIndex($scope.caches, function (cache) {
+                        return cache._id == _id;
+                    });
+
+                    if (idx >= 0)
+                        angular.extend($scope.caches[idx], item);
+                    else {
+                        item._id = _id;
+
+                        $scope.caches.push(item);
+                    }
+
+                    $scope.selectItem(item);
+
+                    $common.showInfo('Cache "' + item.name + '" saved.');
+                })
+                .error(function (errMsg) {
+                    $common.showError(errMsg);
+                });
+        }
+
+        // Save cache.
+        $scope.saveItem = function () {
+            $table.tableReset();
+
+            var item = $scope.backupItem;
+
+            if (validate(item))
+                save(item);
+        };
+
+        // Save cache with new name.
+        $scope.saveItemAs = function () {
+            $table.tableReset();
+
+            if (validate($scope.backupItem))
+                $copy.show($scope.backupItem.name).then(function (newName) {
+                    var item = angular.copy($scope.backupItem);
+
+                    item._id = undefined;
+                    item.name = newName;
+
+                    save(item);
+                });
+        };
+
+        // Remove cache from db.
+        $scope.removeItem = function () {
+            $table.tableReset();
+
+            var selectedItem = $scope.selectedItem;
+
+            $confirm.show('Are you sure you want to remove cache: "' + selectedItem.name + '"?').then(
+                function () {
+                    var _id = selectedItem._id;
+
+                    $http.post('caches/remove', {_id: _id})
+                        .success(function () {
+                            $common.showInfo('Cache has been removed: ' + selectedItem.name);
+
+                            var caches = $scope.caches;
+
+                            var idx = _.findIndex(caches, function (cache) {
+                                return cache._id == _id;
+                            });
+
+                            if (idx >= 0) {
+                                caches.splice(idx, 1);
+
+                                if (caches.length > 0)
+                                    $scope.selectItem(caches[0]);
+                                else {
+                                    $scope.selectedItem = undefined;
+                                    $scope.backupItem = undefined;
+                                }
+                            }
+                        })
+                        .error(function (errMsg) {
+                            $common.showError(errMsg);
+                        });
+                }
+            );
+        };
+    }]
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/controllers/clusters-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/clusters-controller.js b/modules/control-center-web/src/main/js/controllers/clusters-controller.js
new file mode 100644
index 0000000..0b18868
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/clusters-controller.js
@@ -0,0 +1,308 @@
+/*
+ * 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.
+ */
+
+controlCenterModule.controller('clustersController', ['$scope', '$http', '$common', '$confirm', '$copy', '$table', function ($scope, $http, $common, $confirm, $copy, $table) {
+        $scope.joinTip = $common.joinTip;
+        $scope.getModel = $common.getModel;
+
+        $scope.tableReset = $table.tableReset;
+        $scope.tableNewItem = $table.tableNewItem;
+        $scope.tableNewItemActive = $table.tableNewItemActive;
+        $scope.tableEditing = $table.tableEditing;
+        $scope.tableStartEdit = $table.tableStartEdit;
+        $scope.tableRemove = $table.tableRemove;
+
+        $scope.tableSimpleSave = $table.tableSimpleSave;
+        $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+        $scope.tableSimpleUp = $table.tableSimpleUp;
+        $scope.tableSimpleDown = $table.tableSimpleDown;
+        $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
+
+        $scope.templates = [
+            {value: {discovery: {kind: 'Multicast', Vm: {addresses: ['127.0.0.1:47500..47510']}, Multicast: {}}},label: 'multicast'},
+            {value: {discovery: {kind: 'Vm', Vm: {addresses: ['127.0.0.1:47500..47510']}}}, label: 'local'}
+        ];
+
+        $scope.discoveries = [
+            {value: 'Vm', label: 'static IPs'},
+            {value: 'Multicast', label: 'multicast'},
+            {value: 'S3', label: 'AWS S3'},
+            {value: 'Cloud', label: 'apache jclouds'},
+            {value: 'GoogleStorage', label: 'google cloud storage'},
+            {value: 'Jdbc', label: 'JDBC'},
+            {value: 'SharedFs', label: 'shared filesystem'}
+        ];
+
+        $scope.swapSpaceSpis = [
+            {value: 'FileSwapSpaceSpi', label: 'File-based swap'},
+            {value: undefined, label: 'Not set'}
+        ];
+
+        $scope.events = [];
+
+        for (var eventGroupName in eventGroups) {
+            if (eventGroups.hasOwnProperty(eventGroupName)) {
+                $scope.events.push({value: eventGroupName, label: eventGroupName});
+            }
+        }
+
+        $scope.cacheModes = [
+            {value: 'LOCAL', label: 'LOCAL'},
+            {value: 'REPLICATED', label: 'REPLICATED'},
+            {value: 'PARTITIONED', label: 'PARTITIONED'}
+        ];
+
+        $scope.deploymentModes = [
+            {value: 'PRIVATE', label: 'PRIVATE'},
+            {value: 'ISOLATED', label: 'ISOLATED'},
+            {value: 'SHARED', label: 'SHARED'},
+            {value: 'CONTINUOUS', label: 'CONTINUOUS'}
+        ];
+
+        $scope.transactionConcurrency = [
+            {value: 'OPTIMISTIC', label: 'OPTIMISTIC'},
+            {value: 'PESSIMISTIC', label: 'PESSIMISTIC'}
+        ];
+
+        $scope.transactionIsolation = [
+            {value: 'READ_COMMITTED', label: 'READ_COMMITTED'},
+            {value: 'REPEATABLE_READ', label: 'REPEATABLE_READ'},
+            {value: 'SERIALIZABLE', label: 'SERIALIZABLE'}
+        ];
+
+        $scope.segmentationPolicy = [
+            {value: 'RESTART_JVM', label: 'RESTART_JVM'},
+            {value: 'STOP', label: 'STOP'},
+            {value: 'NOOP', label: 'NOOP'}
+        ];
+
+        $scope.marshallers = [
+            {value: 'OptimizedMarshaller', label: 'OptimizedMarshaller'},
+            {value: 'JdkMarshaller', label: 'JdkMarshaller'}
+        ];
+
+        $scope.tableSimpleValid = function (item, field, val, index) {
+            var model = $common.getModel(item, field)[field.model];
+
+            if ($common.isDefined(model)) {
+                var idx = _.indexOf(model, val);
+
+                // Found itself.
+                if (index >= 0 && index == idx)
+                    return true;
+
+                // Found duplicate.
+                if (idx >= 0) {
+                    var msg = 'Such IP address already exists!';
+
+                    if (field.model == 'regions')
+                        msg = 'Such region already exists!';
+                    if (field.model == 'zones')
+                        msg = 'Such zone already exists!';
+
+                    $common.showError(msg);
+
+                    return false;
+                }
+            }
+
+            return true;
+        };
+
+        $scope.clusters = [];
+
+        $http.get('/models/clusters.json')
+            .success(function (data) {
+                $scope.screenTip = data.screenTip;
+                $scope.templateTip = data.templateTip;
+
+                $scope.general = data.general;
+                $scope.advanced = data.advanced;
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+
+        // When landing on the page, get clusters and show them.
+        $http.post('clusters/list')
+            .success(function (data) {
+                $scope.caches = data.caches;
+                $scope.spaces = data.spaces;
+                $scope.clusters = data.clusters;
+
+                var restoredItem = angular.fromJson(sessionStorage.clusterBackupItem);
+
+                if (restoredItem) {
+                    if (restoredItem._id) {
+                        var idx = _.findIndex($scope.clusters, function (cluster) {
+                            return cluster._id == restoredItem._id;
+                        });
+
+                        if (idx >= 0) {
+                            $scope.selectedItem = $scope.clusters[idx];
+                            $scope.backupItem = restoredItem;
+                        }
+                        else
+                            sessionStorage.removeItem('clusterBackupItem');
+                    }
+                    else
+                        $scope.backupItem = restoredItem;
+                }
+                else if ($scope.clusters.length > 0)
+                    $scope.selectItem($scope.clusters[0]);
+
+                $scope.$watch('backupItem', function (val) {
+                    if (val)
+                        sessionStorage.clusterBackupItem = angular.toJson(val);
+                }, true);
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+
+        $scope.selectItem = function (item) {
+            $table.tableReset();
+
+            $scope.selectedItem = item;
+            $scope.backupItem = angular.copy(item);
+        };
+
+        // Add new cluster.
+        $scope.createItem = function () {
+            $table.tableReset();
+
+            $scope.backupItem = angular.copy($scope.create.template);
+            $scope.backupItem.caches = [];
+            $scope.backupItem.space = $scope.spaces[0]._id;
+        };
+
+        $scope.indexOfCache = function (cacheId) {
+            return _.findIndex($scope.caches, function (cache) {
+                return cache.value == cacheId;
+            });
+        };
+
+        // Check cluster logical consistency.
+        function validate(item) {
+            if (!item.swapSpaceSpi || !item.swapSpaceSpi.kind && item.caches) {
+                for (var i = 0; i < item.caches.length; i++) {
+                    var idx = $scope.indexOfCache(item.caches[i]);
+
+                    if (idx >= 0) {
+                        var cache = $scope.caches[idx];
+
+                        if (cache.swapEnabled) {
+                            $common.showError('Swap space SPI is not configured, but cache "' + cache.label + '" configured to use swap!');
+
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        // Save cluster in database.
+        function save(item) {
+            $http.post('clusters/save', item)
+                .success(function (_id) {
+                    var idx = _.findIndex($scope.clusters, function (cluster) {
+                        return cluster._id == _id;
+                    });
+
+                    if (idx >= 0)
+                        angular.extend($scope.clusters[idx], item);
+                    else {
+                        item._id = _id;
+
+                        $scope.clusters.push(item);
+                    }
+
+                    $scope.selectItem(item);
+
+                    $common.showInfo('Cluster "' + item.name + '" saved.');
+                })
+                .error(function (errMsg) {
+                    $common.showError(errMsg);
+                });
+        }
+
+        // Save cluster.
+        $scope.saveItem = function () {
+            $table.tableReset();
+
+            var item = $scope.backupItem;
+
+            if (validate(item))
+                save(item);
+        };
+
+        // Save cluster with new name.
+        $scope.saveItemAs = function () {
+            $table.tableReset();
+
+            if (validate($scope.backupItem))
+                $copy.show($scope.backupItem.name).then(function (newName) {
+                    var item = angular.copy($scope.backupItem);
+
+                    item._id = undefined;
+                    item.name = newName;
+
+                    save(item);
+                });
+        };
+
+        // Remove cluster from db.
+        $scope.removeItem = function () {
+            $table.tableReset();
+
+            var selectedItem = $scope.selectedItem;
+
+            $confirm.show('Are you sure you want to remove cluster: "' + selectedItem.name + '"?').then(
+                function () {
+                    var _id = selectedItem._id;
+
+                    $http.post('clusters/remove', {_id: _id})
+                        .success(function () {
+                            $common.showInfo('Cluster has been removed: ' + selectedItem.name);
+
+                            var clusters = $scope.clusters;
+
+                            var idx = _.findIndex(clusters, function (cluster) {
+                                return cluster._id == _id;
+                            });
+
+                            if (idx >= 0) {
+                                clusters.splice(idx, 1);
+
+                                if (clusters.length > 0)
+                                    $scope.selectItem(clusters[0]);
+                                else {
+                                    $scope.selectedItem = undefined;
+                                    $scope.backupItem = undefined;
+                                }
+                            }
+                        })
+                        .error(function (errMsg) {
+                            $common.showError(errMsg);
+                        });
+                }
+            );
+        };
+    }]
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/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
new file mode 100644
index 0000000..df2ff19
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/common-module.js
@@ -0,0 +1,484 @@
+/*
+ * 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.
+ */
+
+var controlCenterModule = angular.module('ignite-web-control-center', ['smart-table', 'mgcrea.ngStrap', 'ui.ace', 'ngSanitize']);
+
+// Modal popup configuration.
+controlCenterModule.config(function ($modalProvider) {
+    angular.extend($modalProvider.defaults, {
+        html: true
+    });
+});
+
+// Tooltips configuration.
+controlCenterModule.config(function ($tooltipProvider) {
+    angular.extend($tooltipProvider.defaults, {
+        container: 'body',
+        placement: 'right',
+        html: 'true',
+        trigger: 'click hover'
+    });
+});
+
+// Comboboxes configuration.
+controlCenterModule.config(function ($selectProvider) {
+    angular.extend($selectProvider.defaults, {
+        maxLength: '1',
+        allText: 'Select All',
+        noneText: 'Clear All',
+        templateUrl: '/select',
+        iconCheckmark: 'fa fa-check',
+        caretHtml: '<span class="caret"></span>'
+    });
+});
+
+// Alerts configuration.
+controlCenterModule.config(function ($alertProvider) {
+    angular.extend($alertProvider.defaults, {
+        container: 'body',
+        placement: 'top-right',
+        duration: '5',
+        type: 'danger'
+    });
+});
+
+// Common functions to be used in controllers.
+controlCenterModule.service('$common', ['$alert', function ($alert) {
+    var msgModal = undefined;
+
+    function errorMessage(errMsg) {
+        return errMsg ? errMsg : 'Internal server error.';
+    }
+
+    function isDefined(v) {
+        return !(v === undefined || v === null);
+    }
+
+    return {
+        getModel: function (obj, field) {
+            var path = field.path;
+
+            if (!isDefined(path))
+                return obj;
+
+            path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
+            path = path.replace(/^\./, '');           // strip a leading dot
+
+            var segs = path.split('.');
+            var root = obj;
+
+            while (segs.length > 0) {
+                var pathStep = segs.shift();
+
+                if (typeof root[pathStep] === 'undefined')
+                    root[pathStep] = {};
+
+                root = root[pathStep];
+            }
+
+            return root;
+        },
+        joinTip: function (arr) {
+            if (!arr) {
+                return arr;
+            }
+
+            var lines = arr.map(function (line) {
+                var rtrimmed = line.replace(/\s+$/g, '');
+
+                if (rtrimmed.indexOf('>', this.length - 1) == -1) {
+                    rtrimmed = rtrimmed + '<br/>';
+                }
+
+                return rtrimmed;
+            });
+
+            return lines.join("");
+        },
+        isDefined: isDefined,
+        isNonEmpty: function (s) {
+            return isDefined(s) && s.trim().length > 0;
+        },
+        errorMessage: errorMessage,
+        showError: function (msg) {
+            if (msgModal)
+                msgModal.hide();
+
+            msgModal = $alert({title: errorMessage(msg)});
+        },
+        showInfo: function (msg) {
+            if (msgModal)
+                msgModal.hide();
+
+            msgModal = $alert({
+                type: 'success',
+                title: msg,
+                duration: 2
+            });
+        },
+        javaBuildInTypes: [
+            'Boolean', 'Byte', 'Date', 'Double', 'Float', 'Integer', 'Long', 'Short', 'String', 'Time', 'Timestamp', 'UUID'
+        ]
+    }
+}]);
+
+// Confirm popup service.
+controlCenterModule.service('$confirm', function ($modal, $rootScope, $q) {
+    var scope = $rootScope.$new();
+
+    var deferred;
+
+    scope.ok = function () {
+        deferred.resolve();
+
+        confirmModal.hide();
+    };
+
+    var confirmModal = $modal({templateUrl: '/confirm', scope: scope, placement: 'center', show: false});
+
+    var parentShow = confirmModal.show;
+
+    confirmModal.show = function (content) {
+        scope.content = content || 'Confirm deletion?';
+
+        deferred = $q.defer();
+
+        parentShow();
+
+        return deferred.promise;
+    };
+
+    return confirmModal;
+});
+
+// "Save as" popup service.
+controlCenterModule.service('$copy', function ($modal, $rootScope, $q) {
+    var scope = $rootScope.$new();
+
+    var deferred;
+
+    scope.ok = function (newName) {
+        deferred.resolve(newName);
+
+        copyModal.hide();
+    };
+
+    var copyModal = $modal({templateUrl: '/copy', scope: scope, placement: 'center', show: false});
+
+    var parentShow = copyModal.show;
+
+    copyModal.show = function (oldName) {
+        scope.newName = oldName + '(1)';
+
+        deferred = $q.defer();
+
+        parentShow();
+
+        return deferred.promise;
+    };
+
+    return copyModal;
+});
+
+// Tables support service.
+controlCenterModule.service('$table', ['$common', function ($common) {
+    function _swapSimpleItems(a, ix1, ix2) {
+        var tmp = a[ix1];
+
+        a[ix1] = a[ix2];
+        a[ix2] = tmp;
+    }
+
+    function _model(item, field) {
+        return $common.getModel(item, field);
+    }
+
+    var table = {name: 'none', editIndex: -1};
+
+    function _tableReset() {
+        table.name = 'none';
+        table.editIndex = -1;
+    }
+
+    function _tableState(name, editIndex) {
+        table.name = name;
+        table.editIndex = editIndex;
+    }
+
+    return {
+        tableState: function (name, editIndex) {
+            _tableState(name, editIndex);
+        },
+        tableReset: function () {
+            _tableReset();
+        },
+        tableNewItem: function (field) {
+            _tableState(field.model, -1);
+        },
+        tableNewItemActive: function (field) {
+            return table.name == field.model && table.editIndex < 0;
+        },
+        tableEditing: function (field, index) {
+            return table.name == field.model && table.editIndex == index;
+        },
+        tableStartEdit: function (item, field, index) {
+            _tableState(field.model, index);
+
+            return _model(item, field)[field.model][index];
+        },
+        tableRemove: function (item, field, index) {
+            _tableReset();
+
+            _model(item, field)[field.model].splice(index, 1);
+        },
+        tableSimpleSave: function (valueValid, item, field, newValue, index) {
+            if (valueValid(item, field, newValue, index)) {
+                _tableReset();
+
+                if (index < 0) {
+                    if (_model(item, field)[field.model])
+                        _model(item, field)[field.model].push(newValue);
+                    else
+                        _model(item, field)[field.model] = [newValue];
+                }
+                else
+                    _model(item, field)[field.model][index] = newValue;
+            }
+        },
+        tableSimpleSaveVisible: function (newValue) {
+            return $common.isNonEmpty(newValue);
+        },
+        tableSimpleUp: function (item, field, index) {
+            _tableReset();
+
+            _swapSimpleItems(_model(item, field)[field.model], index, index - 1);
+        },
+        tableSimpleDown: function (item, field, index) {
+            _tableReset();
+
+            _swapSimpleItems(_model(item, field)[field.model], index, index + 1);
+        },
+        tableSimpleDownVisible: function (item, field, index) {
+            return index < _model(item, field)[field.model].length - 1;
+        },
+        tablePairSave: function (pairValid, item, field, newKey, newValue, index) {
+            if (pairValid(item, field, newKey, newValue, index)) {
+                _tableReset();
+
+                var pair = {};
+
+                if (index < 0) {
+                    pair[field.keyName] = newKey;
+                    pair[field.valueName] = newValue;
+
+                    if (item[field.model])
+                        item[field.model].push(pair);
+                    else
+                        item[field.model] = [pair];
+                }
+                else {
+                    pair = item[field.model][index];
+
+                    pair[field.keyName] = newKey;
+                    pair[field.valueName] = newValue;
+                }
+            }
+        },
+        tablePairSaveVisible: function (newKey, newValue) {
+            return $common.isNonEmpty(newKey) && $common.isNonEmpty(newValue);
+        }
+    }
+}]);
+
+
+// Filter to decode name using map(value, label).
+controlCenterModule.filter('displayValue', function () {
+    return function (v, m, dflt) {
+        var i = _.findIndex(m, function (item) {
+            return item.value == v;
+        });
+
+        if (i >= 0) {
+            return m[i].label;
+        }
+
+        if (dflt) {
+            return dflt;
+        }
+
+        return 'Unknown value';
+    }
+});
+
+/**
+ * Filter for replacing all occurrences of {@code org.apache.ignite.} with {@code o.a.i.},
+ * {@code org.apache.ignite.internal.} with {@code o.a.i.i.},
+ * {@code org.apache.ignite.internal.visor.} with {@code o.a.i.i.v.} and
+ * {@code org.apache.ignite.scalar.} with {@code o.a.i.s.}.
+ *
+ * @param s String to replace in.
+ * @return Replaces string.
+ */
+controlCenterModule.filter('compact', function () {
+    return function (s) {
+        return s.replace("org.apache.ignite.internal.visor.", "o.a.i.i.v.").
+            replace("org.apache.ignite.internal.", "o.a.i.i.").
+            replace("org.apache.ignite.scalar.", "o.a.i.s.").
+            replace("org.apache.ignite.", "o.a.i.");
+    }
+});
+
+// Directive to enable validation for IP addresses.
+controlCenterModule.directive('ipaddress', function () {
+    const ip = '(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])';
+    const port = '([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])';
+    const portRange = '(:' + port + '(..' + port + ')?)?';
+    const host = '(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])';
+
+    return {
+        require: 'ngModel',
+        link: function (scope, elem, attrs, ctrl) {
+            ctrl.$validators.ipaddress = function (modelValue, viewValue) {
+                if (ctrl.$isEmpty(modelValue) || !attrs['ipaddress'])
+                    return true;
+
+                return viewValue.match(new RegExp('(^' + ip + portRange + '$)|(^' + host + portRange + '$)')) != null;
+            }
+        }
+    }
+});
+
+// Directive to enable validation to match specified value.
+controlCenterModule.directive('match', function ($parse) {
+    return {
+        require: 'ngModel',
+        link: function (scope, elem, attrs, ctrl) {
+            scope.$watch(function () {
+                return $parse(attrs.match)(scope) === ctrl.$modelValue;
+            }, function (currentValue) {
+                ctrl.$setValidity('mismatch', currentValue);
+            });
+        }
+    };
+});
+
+// Directive to bind ENTER key press with some user action.
+controlCenterModule.directive('ngEnter', function() {
+    return function(scope, element, attrs) {
+        element.bind('keydown keypress', function(event) {
+            if (event.which === 13) {
+                scope.$apply(function() {
+                    scope.$eval(attrs.ngEnter);
+                });
+
+                event.preventDefault();
+            }
+        });
+    };
+});
+
+// Directive to bind ESC key press with some user action.
+controlCenterModule.directive('ngEscape', function() {
+    return function(scope, element, attrs) {
+        element.bind('keydown keyup', function(event) {
+            if (event.which === 27) {
+                scope.$apply(function() {
+                    scope.$eval(attrs.ngEscape);
+                });
+
+                event.preventDefault();
+            }
+        });
+    };
+});
+
+// Factory function to focus element.
+controlCenterModule.factory('focus', function ($timeout, $window) {
+    return function (id) {
+        // Timeout makes sure that is invoked after any other event has been triggered.
+        // E.g. click events that need to run before the focus or inputs elements that are
+        // in a disabled state but are enabled when those events are triggered.
+        $timeout(function () {
+            var element = $window.document.getElementById(id);
+
+            if (element)
+                element.focus();
+        });
+    };
+});
+
+// Directive to mark elements to focus.
+controlCenterModule.directive('eventFocus', function (focus) {
+    return function (scope, elem, attr) {
+        elem.on(attr.eventFocus, function () {
+            focus(attr.eventFocusId);
+        });
+
+        // Removes bound events in the element itself when the scope is destroyed
+        scope.$on('$destroy', function () {
+            element.off(attr.eventFocus);
+        });
+    };
+});
+
+// Navigation bar controller.
+controlCenterModule.controller('activeLink', [
+    '$scope', function ($scope) {
+        $scope.isActive = function (path) {
+            return window.location.pathname.substr(0, path.length) == path;
+        };
+    }]);
+
+// Login popup controller.
+controlCenterModule.controller('auth', [
+    '$scope', '$modal', '$alert', '$http', '$window', '$common',
+    function ($scope, $modal, $alert, $http, $window, $common) {
+        $scope.errorMessage = $common.errorMessage;
+
+        $scope.action = 'login';
+
+        $scope.valid = false;
+
+        $scope.userDropdown = [{"text": "Profile", "href": "/profile"}];
+
+        if (!$scope.becomeUsed) {
+            if ($scope.user && $scope.user.admin)
+                $scope.userDropdown.push({"text": "Admin Panel", "href": "/admin"});
+
+            $scope.userDropdown.push({"text": "Log Out", "href": "/logout"});
+        }
+
+        // Pre-fetch an external template populated with a custom scope
+        var authModal = $modal({scope: $scope, templateUrl: '/login', show: false});
+
+        $scope.login = function () {
+            // Show when some event occurs (use $promise property to ensure the template has been loaded)
+            authModal.$promise.then(authModal.show);
+        };
+
+        $scope.auth = function (action, user_info) {
+            $http.post('/' + action, user_info)
+                .success(function () {
+                    authModal.hide();
+
+                    $window.location = '/configuration/clusters';
+                })
+                .error(function (data) {
+                    $alert({placement: 'top', container: '#errors-container', title: $scope.errorMessage(data)});
+                });
+        };
+    }]);
\ No newline at end of file