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/10/13 05:28:06 UTC

[05/18] ignite git commit: IGNITE-843 Web console initial commit.

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/generator/generator-xml.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-xml.js b/modules/control-center-web/src/main/js/routes/generator/generator-xml.js
new file mode 100644
index 0000000..569c6dd
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-xml.js
@@ -0,0 +1,1202 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// For server side we should load required libraries.
+if (typeof window === 'undefined') {
+    _ = require('lodash');
+
+    $commonUtils = require('../../helpers/common-utils');
+    $dataStructures = require('../../helpers/data-structures');
+    $generatorCommon = require('./generator-common');
+}
+
+// XML generation entry point.
+$generatorXml = {};
+
+// Do XML escape.
+$generatorXml.escape = function (s) {
+    if (typeof(s) != 'string')
+        return s;
+
+    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+};
+
+// Add XML element.
+$generatorXml.element = function (res, tag, attr1, val1, attr2, val2) {
+    var elem = '<' + tag;
+
+    if (attr1)
+        elem += ' ' + attr1 + '="' + val1 + '"';
+
+    if (attr2)
+        elem += ' ' + attr2 + '="' + val2 + '"';
+
+    elem += '/>';
+
+    res.emptyLineIfNeeded();
+    res.line(elem);
+};
+
+// Add property.
+$generatorXml.property = function (res, obj, propName, setterName, dflt) {
+    if ($commonUtils.isDefined(obj)) {
+        var val = obj[propName];
+
+        if ($commonUtils.isDefinedAndNotEmpty(val)) {
+            var hasDflt = $commonUtils.isDefined(dflt);
+
+            // Add to result if no default provided or value not equals to default.
+            if (!hasDflt || (hasDflt && val != dflt)) {
+                $generatorXml.element(res, 'property', 'name', setterName ? setterName : propName, 'value', $generatorXml.escape(val));
+
+                return true;
+            }
+        }
+    }
+
+    return false;
+};
+
+// Add property for class name.
+$generatorXml.classNameProperty = function (res, obj, propName) {
+    var val = obj[propName];
+
+    if ($commonUtils.isDefined(val))
+        $generatorXml.element(res, 'property', 'name', propName, 'value', $dataStructures.fullClassName(val));
+};
+
+// Add list property.
+$generatorXml.listProperty = function (res, obj, propName, listType, rowFactory) {
+    var val = obj[propName];
+
+    if (val && val.length > 0) {
+        res.emptyLineIfNeeded();
+
+        if (!listType)
+            listType = 'list';
+
+        if (!rowFactory)
+            rowFactory = function (val) {
+                return '<value>' + $generatorXml.escape(val) + '</value>'
+            };
+
+        res.startBlock('<property name="' + propName + '">');
+        res.startBlock('<' + listType + '>');
+
+        for (var i = 0; i < val.length; i++)
+            res.line(rowFactory(val[i]));
+
+        res.endBlock('</' + listType + '>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+};
+
+// Add array property
+$generatorXml.arrayProperty = function (res, obj, propName, descr, rowFactory) {
+    var val = obj[propName];
+
+    if (val && val.length > 0) {
+        res.emptyLineIfNeeded();
+
+        if (!rowFactory)
+            rowFactory = function (val) {
+                return '<bean class="' + val + '"/>';
+            };
+
+        res.startBlock('<property name="' + propName + '">');
+        res.startBlock('<list>');
+
+        _.forEach(val, function (v) {
+            res.append(rowFactory(v))
+        });
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+    }
+}
+
+// Add bean property.
+$generatorXml.beanProperty = function (res, bean, beanPropName, desc, createBeanAlthoughNoProps) {
+    var props = desc.fields;
+
+    if (bean && $commonUtils.hasProperty(bean, props)) {
+        res.startSafeBlock();
+
+        res.emptyLineIfNeeded();
+        res.startBlock('<property name="' + beanPropName + '">');
+        res.startBlock('<bean class="' + desc.className + '">');
+
+        var hasData = false;
+
+        for (var propName in props) {
+            if (props.hasOwnProperty(propName)) {
+                var descr = props[propName];
+
+                if (descr) {
+                    if (descr.type == 'list')
+                        $generatorXml.listProperty(res, bean, propName, descr.setterName);
+                    else if (descr.type == 'array')
+                        $generatorXml.arrayProperty(res, bean, propName, descr);
+                    else if (descr.type == 'jdbcDialect') {
+                        if (bean[propName]) {
+                            res.startBlock('<property name="' + propName + '">');
+                            res.line('<bean class="' + $generatorCommon.jdbcDialectClassName(bean[propName]) + '"/>');
+                            res.endBlock('</property>');
+
+                            hasData = true;
+                        }
+                    }
+                    else if (descr.type == 'propertiesAsList') {
+                        var val = bean[propName];
+
+                        if (val && val.length > 0) {
+                            res.startBlock('<property name="' + propName + '">');
+                            res.startBlock('<props>');
+
+                            for (var i = 0; i < val.length; i++) {
+                                var nameAndValue = val[i];
+
+                                var eqIndex = nameAndValue.indexOf('=');
+                                if (eqIndex >= 0) {
+                                    res.line('<prop key="' + $generatorXml.escape(nameAndValue.substring(0, eqIndex)) + '">' +
+                                        $generatorXml.escape(nameAndValue.substr(eqIndex + 1)) + '</prop>');
+                                }
+                            }
+
+                            res.endBlock('</props>');
+                            res.endBlock('</property>');
+
+                            hasData = true;
+                        }
+                    }
+                    else {
+                        if ($generatorXml.property(res, bean, propName, descr.setterName, descr.dflt))
+                            hasData = true;
+                    }
+                }
+                else
+                    if ($generatorXml.property(res, bean, propName))
+                        hasData = true;
+            }
+        }
+
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+
+        if (!hasData)
+            res.rollbackSafeBlock();
+    }
+    else if (createBeanAlthoughNoProps) {
+        res.emptyLineIfNeeded();
+        res.line('<property name="' + beanPropName + '">');
+        res.line('    <bean class="' + desc.className + '"/>');
+        res.line('</property>');
+    }
+};
+
+// Generate eviction policy.
+$generatorXml.evictionPolicy = function (res, evtPlc, propName) {
+    if (evtPlc && evtPlc.kind) {
+        $generatorXml.beanProperty(res, evtPlc[evtPlc.kind.toUpperCase()], propName,
+            $generatorCommon.EVICTION_POLICIES[evtPlc.kind], true);
+    }
+};
+
+// Generate discovery.
+$generatorXml.clusterGeneral = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cluster, 'name', 'gridName');
+
+    if (cluster.discovery) {
+        res.startBlock('<property name="discoverySpi">');
+        res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">');
+        res.startBlock('<property name="ipFinder">');
+
+        var d = cluster.discovery;
+
+        switch (d.kind) {
+            case 'Multicast':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">');
+
+                if (d.Multicast) {
+                    $generatorXml.property(res, d.Multicast, 'multicastGroup');
+                    $generatorXml.property(res, d.Multicast, 'multicastPort');
+                    $generatorXml.property(res, d.Multicast, 'responseWaitTime');
+                    $generatorXml.property(res, d.Multicast, 'addressRequestAttempts');
+                    $generatorXml.property(res, d.Multicast, 'localAddress');
+                    $generatorXml.listProperty(res, d.Multicast, 'addresses');
+                }
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'Vm':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">');
+
+                if (d.Vm) {
+                    $generatorXml.listProperty(res, d.Vm, 'addresses');
+                }
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'S3':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.s3.TcpDiscoveryS3IpFinder">');
+
+                if (d.S3) {
+                    if (d.S3.bucketName)
+                        res.line('<property name="bucketName" value="' + $generatorXml.escape(d.S3.bucketName) + '" />');
+                }
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'Cloud':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.cloud.TcpDiscoveryCloudIpFinder">');
+
+                if (d.Cloud) {
+                    $generatorXml.property(res, d.Cloud, 'credential');
+                    $generatorXml.property(res, d.Cloud, 'credentialPath');
+                    $generatorXml.property(res, d.Cloud, 'identity');
+                    $generatorXml.property(res, d.Cloud, 'provider');
+                    $generatorXml.listProperty(res, d.Cloud, 'regions');
+                    $generatorXml.listProperty(res, d.Cloud, 'zones');
+                }
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'GoogleStorage':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.gce.TcpDiscoveryGoogleStorageIpFinder">');
+
+                if (d.GoogleStorage) {
+                    $generatorXml.property(res, d.GoogleStorage, 'projectName');
+                    $generatorXml.property(res, d.GoogleStorage, 'bucketName');
+                    $generatorXml.property(res, d.GoogleStorage, 'serviceAccountP12FilePath');
+                    $generatorXml.property(res, d.GoogleStorage, 'serviceAccountId');
+                }
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'Jdbc':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinder">');
+
+                if (d.Jdbc) {
+                    res.line('<property name="initSchema" value="' + ($commonUtils.isDefined(d.Jdbc.initSchema) && d.Jdbc.initSchema) + '"/>');
+                }
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'SharedFs':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinder">');
+
+                if (d.SharedFs) {
+                    $generatorXml.property(res, d.SharedFs, 'path');
+                }
+
+                res.endBlock('</bean>');
+
+                break;
+
+            default:
+                throw "Unknown discovery kind: " + d.kind;
+        }
+
+        res.endBlock('</property>');
+
+        $generatorXml.clusterDiscovery(d, res);
+
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate atomics group.
+$generatorXml.clusterAtomics = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    var atomics = cluster.atomicConfiguration;
+
+    if ($commonUtils.hasAtLeastOneProperty(atomics, ['cacheMode', 'atomicSequenceReserveSize', 'backups'])) {
+        res.startSafeBlock();
+
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="atomicConfiguration">');
+        res.startBlock('<bean class="org.apache.ignite.configuration.AtomicConfiguration">');
+
+        var cacheMode = atomics.cacheMode ? atomics.cacheMode : 'PARTITIONED';
+
+        var hasData = cacheMode != 'PARTITIONED';
+
+        $generatorXml.property(res, atomics, 'cacheMode');
+
+        hasData = $generatorXml.property(res, atomics, 'atomicSequenceReserveSize') || hasData;
+
+        if (cacheMode == 'PARTITIONED')
+            hasData = $generatorXml.property(res, atomics, 'backups') || hasData;
+
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+
+        if (!hasData)
+            res.rollbackSafeBlock();
+    }
+
+    return res;
+};
+
+// Generate communication group.
+$generatorXml.clusterCommunication = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cluster, 'networkTimeout');
+    $generatorXml.property(res, cluster, 'networkSendRetryDelay');
+    $generatorXml.property(res, cluster, 'networkSendRetryCount');
+    $generatorXml.property(res, cluster, 'segmentCheckFrequency');
+    $generatorXml.property(res, cluster, 'waitForSegmentOnStart', null, false);
+    $generatorXml.property(res, cluster, 'discoveryStartupDelay');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate deployment group.
+$generatorXml.clusterDeployment = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if ($generatorXml.property(res, cluster, 'deploymentMode', null, 'SHARED'))
+        res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate discovery group.
+$generatorXml.clusterDiscovery = function (disco, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, disco, 'localAddress');
+    $generatorXml.property(res, disco, 'localPort', undefined, 47500);
+    $generatorXml.property(res, disco, 'localPortRange', undefined, 100);
+    $generatorXml.beanProperty(res, disco, 'addressResolver', {className: disco.addressResolver}, true);
+    $generatorXml.property(res, disco, 'socketTimeout');
+    $generatorXml.property(res, disco, 'ackTimeout');
+    $generatorXml.property(res, disco, 'maxAckTimeout', undefined, 600000);
+    $generatorXml.property(res, disco, 'discoNetworkTimeout', 'setNetworkTimeout', 5000);
+    $generatorXml.property(res, disco, 'joinTimeout', undefined, 0);
+    $generatorXml.property(res, disco, 'threadPriority', undefined, 10);
+    $generatorXml.property(res, disco, 'heartbeatFrequency', undefined, 2000);
+    $generatorXml.property(res, disco, 'maxMissedHeartbeats', undefined, 1);
+    $generatorXml.property(res, disco, 'maxMissedClientHeartbeats', undefined, 5);
+    $generatorXml.property(res, disco, 'topHistorySize', undefined, 100);
+    $generatorXml.beanProperty(res, disco, 'listener', {className: disco.listener}, true);
+    $generatorXml.beanProperty(res, disco, 'dataExchange', {className: disco.dataExchange}, true);
+    $generatorXml.beanProperty(res, disco, 'metricsProvider', {className: disco.metricsProvider}, true);
+    $generatorXml.property(res, disco, 'reconnectCount', undefined, 10);
+    $generatorXml.property(res, disco, 'statisticsPrintFrequency', undefined, 0);
+    $generatorXml.property(res, disco, 'ipFinderCleanFrequency', undefined, 60000);
+    $generatorXml.beanProperty(res, disco, 'authenticator', {className: disco.authenticator}, true);
+    $generatorXml.property(res, disco, 'forceServerMode', undefined, false);
+    $generatorXml.property(res, disco, 'clientReconnectDisabled', undefined, false);
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate events group.
+$generatorXml.clusterEvents = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cluster.includeEventTypes && cluster.includeEventTypes.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="includeEventTypes">');
+
+        if (cluster.includeEventTypes.length == 1)
+            res.line('<util:constant static-field="org.apache.ignite.events.EventType.' + cluster.includeEventTypes[0] + '"/>');
+        else {
+            res.startBlock('<list>');
+
+            for (i = 0; i < cluster.includeEventTypes.length; i++) {
+                if (i > 0)
+                    res.line();
+
+                var eventGroup = cluster.includeEventTypes[i];
+
+                res.line('<!-- EventType.' + eventGroup + ' -->');
+
+                var eventList = $dataStructures.EVENT_GROUPS[eventGroup];
+
+                for (var k = 0; k < eventList.length; k++) {
+                    res.line('<util:constant static-field="org.apache.ignite.events.EventType.' + eventList[k] + '"/>')
+                }
+            }
+
+            res.endBlock('</list>');
+        }
+
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate marshaller group.
+$generatorXml.clusterMarshaller = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    var marshaller = cluster.marshaller;
+
+    if (marshaller && marshaller.kind) {
+        $generatorXml.beanProperty(res, marshaller[marshaller.kind], 'marshaller', $generatorCommon.MARSHALLERS[marshaller.kind], true);
+
+        res.needEmptyLine = true;
+    }
+
+    $generatorXml.property(res, cluster, 'marshalLocalJobs', null, false);
+    $generatorXml.property(res, cluster, 'marshallerCacheKeepAliveTime');
+    $generatorXml.property(res, cluster, 'marshallerCacheThreadPoolSize');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metrics group.
+$generatorXml.clusterMetrics = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cluster, 'metricsExpireTime');
+    $generatorXml.property(res, cluster, 'metricsHistorySize');
+    $generatorXml.property(res, cluster, 'metricsLogFrequency');
+    $generatorXml.property(res, cluster, 'metricsUpdateFrequency');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate PeerClassLoading group.
+$generatorXml.clusterP2p = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    var p2pEnabled = cluster.peerClassLoadingEnabled;
+
+    if ($commonUtils.isDefined(p2pEnabled)) {
+        $generatorXml.property(res, cluster, 'peerClassLoadingEnabled', null, false);
+
+        if (p2pEnabled) {
+            $generatorXml.property(res, cluster, 'peerClassLoadingMissedResourcesCacheSize');
+            $generatorXml.property(res, cluster, 'peerClassLoadingThreadPoolSize');
+            $generatorXml.listProperty(res, cluster, 'peerClassLoadingLocalClassPathExclude');
+        }
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate swap group.
+$generatorXml.clusterSwap = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cluster.swapSpaceSpi && cluster.swapSpaceSpi.kind == 'FileSwapSpaceSpi') {
+        $generatorXml.beanProperty(res, cluster.swapSpaceSpi.FileSwapSpaceSpi, 'swapSpaceSpi',
+            $generatorCommon.SWAP_SPACE_SPI, true);
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate time group.
+$generatorXml.clusterTime = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cluster, 'clockSyncSamples');
+    $generatorXml.property(res, cluster, 'clockSyncFrequency');
+    $generatorXml.property(res, cluster, 'timeServerPortBase');
+    $generatorXml.property(res, cluster, 'timeServerPortRange');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate thread pools group.
+$generatorXml.clusterPools = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cluster, 'publicThreadPoolSize');
+    $generatorXml.property(res, cluster, 'systemThreadPoolSize');
+    $generatorXml.property(res, cluster, 'managementThreadPoolSize');
+    $generatorXml.property(res, cluster, 'igfsThreadPoolSize');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate transactions group.
+$generatorXml.clusterTransactions = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.beanProperty(res, cluster.transactionConfiguration, 'transactionConfiguration', $generatorCommon.TRANSACTION_CONFIGURATION);
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+/**
+ * XML generator for cluster's SSL configuration.
+ *
+ * @param cluster Cluster to get SSL configuration.
+ * @param res Optional configuration presentation builder object.
+ * @returns Configuration presentation builder object
+ */
+$generatorXml.clusterSsl = function(cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cluster.sslEnabled && $commonUtils.isDefined(cluster.sslContextFactory)) {
+        cluster.sslContextFactory.keyStorePassword =
+            ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.keyStoreFilePath)) ? '${ssl.key.storage.password}' : undefined;
+
+        cluster.sslContextFactory.trustStorePassword =
+            ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustStoreFilePath)) ? '${ssl.trust.storage.password}' : undefined;
+
+        var propsDesc = $commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustManagers) ?
+            $generatorCommon.SSL_CONFIGURATION_TRUST_MANAGER_FACTORY :
+            $generatorCommon.SSL_CONFIGURATION_TRUST_FILE_FACTORY;
+
+        $generatorXml.beanProperty(res, cluster.sslContextFactory, 'sslContextFactory', propsDesc, false);
+    }
+
+    return res;
+};
+
+// Generate cache general group.
+$generatorXml.cacheGeneral = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cache, 'name');
+
+    $generatorXml.property(res, cache, 'cacheMode');
+    $generatorXml.property(res, cache, 'atomicityMode');
+
+    if (cache.cacheMode == 'PARTITIONED')
+        $generatorXml.property(res, cache, 'backups');
+
+    $generatorXml.property(res, cache, 'readFromBackup');
+    $generatorXml.property(res, cache, 'copyOnRead');
+    $generatorXml.property(res, cache, 'invalidate');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache memory group.
+$generatorXml.cacheMemory = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cache, 'memoryMode');
+    $generatorXml.property(res, cache, 'offHeapMaxMemory');
+
+    res.needEmptyLine = true;
+
+    $generatorXml.evictionPolicy(res, cache.evictionPolicy, 'evictionPolicy');
+
+    res.needEmptyLine = true;
+
+    $generatorXml.property(res, cache, 'swapEnabled');
+    $generatorXml.property(res, cache, 'startSize');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache query & indexing group.
+$generatorXml.cacheQuery = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cache, 'sqlOnheapRowCacheSize');
+    $generatorXml.property(res, cache, 'longQueryWarningTimeout');
+
+    if (cache.indexedTypes && cache.indexedTypes.length > 0) {
+        res.startBlock('<property name="indexedTypes">');
+        res.startBlock('<list>');
+
+        for (var i = 0; i < cache.indexedTypes.length; i++) {
+            var pair = cache.indexedTypes[i];
+
+            res.line('<value>' + $dataStructures.fullClassName(pair.keyClass) + '</value>');
+            res.line('<value>' + $dataStructures.fullClassName(pair.valueClass) + '</value>');
+        }
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+
+    $generatorXml.listProperty(res, cache, 'sqlFunctionClasses');
+
+    $generatorXml.property(res, cache, 'sqlEscapeAll');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache store group.
+$generatorXml.cacheStore = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) {
+        var storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind];
+
+        if (storeFactory) {
+            $generatorXml.beanProperty(res, storeFactory, 'cacheStoreFactory', $generatorCommon.STORE_FACTORIES[cache.cacheStoreFactory.kind], true);
+
+            if (storeFactory.dialect) {
+                if (_.findIndex(res.datasources, function (ds) {
+                        return ds.dataSourceBean == storeFactory.dataSourceBean;
+                    }) < 0) {
+                    res.datasources.push({
+                        dataSourceBean: storeFactory.dataSourceBean,
+                        className: $generatorCommon.DATA_SOURCES[storeFactory.dialect],
+                        dialect: storeFactory.dialect
+                    });
+                }
+            }
+
+            res.needEmptyLine = true;
+        }
+    }
+
+    $generatorXml.property(res, cache, 'loadPreviousValue');
+    $generatorXml.property(res, cache, 'readThrough');
+    $generatorXml.property(res, cache, 'writeThrough');
+
+    res.needEmptyLine = true;
+
+    $generatorXml.property(res, cache, 'writeBehindEnabled');
+    $generatorXml.property(res, cache, 'writeBehindBatchSize');
+    $generatorXml.property(res, cache, 'writeBehindFlushSize');
+    $generatorXml.property(res, cache, 'writeBehindFlushFrequency');
+    $generatorXml.property(res, cache, 'writeBehindFlushThreadCount');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache concurrency group.
+$generatorXml.cacheConcurrency = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cache, 'maxConcurrentAsyncOperations');
+    $generatorXml.property(res, cache, 'defaultLockTimeout');
+    $generatorXml.property(res, cache, 'atomicWriteOrderMode');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache rebalance group.
+$generatorXml.cacheRebalance = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cache.cacheMode != 'LOCAL') {
+        $generatorXml.property(res, cache, 'rebalanceMode');
+        $generatorXml.property(res, cache, 'rebalanceThreadPoolSize');
+        $generatorXml.property(res, cache, 'rebalanceBatchSize');
+        $generatorXml.property(res, cache, 'rebalanceOrder');
+        $generatorXml.property(res, cache, 'rebalanceDelay');
+        $generatorXml.property(res, cache, 'rebalanceTimeout');
+        $generatorXml.property(res, cache, 'rebalanceThrottle');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate cache server near cache group.
+$generatorXml.cacheServerNearCache = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cache.cacheMode == 'PARTITIONED' && cache.nearCacheEnabled) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="nearConfiguration">');
+        res.startBlock('<bean class="org.apache.ignite.configuration.NearCacheConfiguration">');
+
+        if (cache.nearConfiguration) {
+            if (cache.nearConfiguration.nearStartSize)
+                $generatorXml.property(res, cache.nearConfiguration, 'nearStartSize');
+
+
+            $generatorXml.evictionPolicy(res, cache.nearConfiguration.nearEvictionPolicy, 'nearEvictionPolicy');
+        }
+
+
+
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+    }
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache statistics group.
+$generatorXml.cacheStatistics = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, cache, 'statisticsEnabled');
+    $generatorXml.property(res, cache, 'managementEnabled');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metadata query fields.
+$generatorXml.metadataQueryFields = function (res, meta, fieldProp) {
+    var fields = meta[fieldProp];
+
+    if (fields && fields.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="' + fieldProp + '">');
+        res.startBlock('<map>');
+
+        _.forEach(fields, function (field) {
+            $generatorXml.element(res, 'entry', 'key', field.name.toUpperCase(), 'value', $dataStructures.fullClassName(field.className));
+        });
+
+        res.endBlock('</map>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+};
+
+// Generate metadata groups.
+$generatorXml.metadataGroups = function (res, meta) {
+    var groups = meta.groups;
+
+    if (groups && groups.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="groups">');
+        res.startBlock('<map>');
+
+        _.forEach(groups, function (group) {
+            var fields = group.fields;
+
+            if (fields && fields.length > 0) {
+                res.startBlock('<entry key="' + group.name + '">');
+                res.startBlock('<map>');
+
+                _.forEach(fields, function (field) {
+                    res.startBlock('<entry key="' + field.name + '">');
+
+                    res.startBlock('<bean class="org.apache.ignite.lang.IgniteBiTuple">');
+                    res.line('<constructor-arg value="' + $dataStructures.fullClassName(field.className) + '"/>');
+                    res.line('<constructor-arg value="' + field.direction + '"/>');
+                    res.endBlock('</bean>');
+
+                    res.endBlock('</entry>');
+                });
+
+                res.endBlock('</map>');
+                res.endBlock('</entry>');
+            }
+        });
+
+        res.endBlock('</map>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+};
+
+// Generate metadata db fields.
+$generatorXml.metadataDatabaseFields = function (res, meta, fieldProp) {
+    var fields = meta[fieldProp];
+
+    if (fields && fields.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="' + fieldProp + '">');
+
+        res.startBlock('<list>');
+
+        _.forEach(fields, function (field) {
+            res.startBlock('<bean class="org.apache.ignite.cache.CacheTypeFieldMetadata">');
+
+            $generatorXml.property(res, field, 'databaseName');
+
+            res.startBlock('<property name="databaseType">');
+            res.line('<util:constant static-field="java.sql.Types.' + field.databaseType + '"/>');
+            res.endBlock('</property>');
+
+            $generatorXml.property(res, field, 'javaName');
+
+            $generatorXml.classNameProperty(res, field, 'javaType');
+
+            res.endBlock('</bean>');
+        });
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+};
+
+// Generate metadata general group.
+$generatorXml.metadataGeneral = function(meta, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.classNameProperty(res, meta, 'keyType');
+    $generatorXml.property(res, meta, 'valueType');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metadata for query group.
+$generatorXml.metadataQuery = function(meta, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.metadataQueryFields(res, meta, 'queryFields');
+    $generatorXml.metadataQueryFields(res, meta, 'ascendingFields');
+    $generatorXml.metadataQueryFields(res, meta, 'descendingFields');
+
+    $generatorXml.listProperty(res, meta, 'textFields');
+
+    $generatorXml.metadataGroups(res, meta);
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metadata for store group.
+$generatorXml.metadataStore = function(meta, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorXml.property(res, meta, 'databaseSchema');
+    $generatorXml.property(res, meta, 'databaseTable');
+
+    res.needEmptyLine = true;
+
+    if (!$dataStructures.isJavaBuildInClass(meta.keyType))
+        $generatorXml.metadataDatabaseFields(res, meta, 'keyFields');
+
+    $generatorXml.metadataDatabaseFields(res, meta, 'valueFields');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache type metadata config.
+$generatorXml.cacheMetadata = function(meta, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    res.emptyLineIfNeeded();
+
+    res.startBlock('<bean class="org.apache.ignite.cache.CacheTypeMetadata">');
+
+    $generatorXml.metadataGeneral(meta, res);
+    $generatorXml.metadataQuery(meta, res);
+    $generatorXml.metadataStore(meta, res);
+
+    res.endBlock('</bean>');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache type metadata configs.
+$generatorXml.cacheMetadatas = function(metadatas, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (metadatas && metadatas.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="typeMetadata">');
+        res.startBlock('<list>');
+
+        _.forEach(metadatas, function (meta) {
+            $generatorXml.cacheMetadata(meta, res);
+        });
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate cache configs.
+$generatorXml.cache = function(cache, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    res.startBlock('<bean class="org.apache.ignite.configuration.CacheConfiguration">');
+
+    $generatorXml.cacheGeneral(cache, res);
+
+    $generatorXml.cacheMemory(cache, res);
+
+    $generatorXml.cacheQuery(cache, res);
+
+    $generatorXml.cacheStore(cache, res);
+
+    $generatorXml.cacheConcurrency(cache, res);
+
+    $generatorXml.cacheRebalance(cache, res);
+
+    $generatorXml.cacheServerNearCache(cache, res);
+
+    $generatorXml.cacheStatistics(cache, res);
+
+    $generatorXml.cacheMetadatas(cache.metadatas, res);
+
+    res.endBlock('</bean>');
+
+    return res;
+};
+
+// Generate caches configs.
+$generatorXml.clusterCaches = function(caches, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (caches && caches.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="cacheConfiguration">');
+        res.startBlock('<list>');
+
+        for (var i = 0; i < caches.length; i++) {
+            if (i > 0)
+                res.line();
+
+            $generatorXml.cache(caches[i], res);
+        }
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate cluster config.
+$generatorXml.cluster = function (cluster, clientNearCfg) {
+    if (cluster) {
+        var res = $generatorCommon.builder();
+
+        res.deep = 1;
+
+        if (clientNearCfg) {
+            res.startBlock('<bean id="nearCacheBean" class="org.apache.ignite.configuration.NearCacheConfiguration">');
+
+            if (clientNearCfg.nearStartSize)
+                $generatorXml.property(res, clientNearCfg, 'nearStartSize');
+
+            if (clientNearCfg.nearEvictionPolicy && clientNearCfg.nearEvictionPolicy.kind)
+                $generatorXml.evictionPolicy(res, clientNearCfg.nearEvictionPolicy, 'nearEvictionPolicy');
+
+            res.endBlock('</bean>');
+
+            res.line();
+        }
+
+        // Generate Ignite Configuration.
+        res.startBlock('<bean class="org.apache.ignite.configuration.IgniteConfiguration">');
+
+        if (clientNearCfg) {
+            res.line('<property name="clientMode" value="true" />');
+
+            res.line();
+        }
+
+        $generatorXml.clusterGeneral(cluster, res);
+
+        $generatorXml.clusterAtomics(cluster, res);
+
+        $generatorXml.clusterCommunication(cluster, res);
+
+        $generatorXml.clusterDeployment(cluster, res);
+
+        $generatorXml.clusterEvents(cluster, res);
+
+        $generatorXml.clusterMarshaller(cluster, res);
+
+        $generatorXml.clusterMetrics(cluster, res);
+
+        $generatorXml.clusterP2p(cluster, res);
+
+        $generatorXml.clusterSwap(cluster, res);
+
+        $generatorXml.clusterTime(cluster, res);
+
+        $generatorXml.clusterPools(cluster, res);
+
+        $generatorXml.clusterTransactions(cluster, res);
+
+        $generatorXml.clusterCaches(cluster.caches, res);
+
+        $generatorXml.clusterSsl(cluster, res);
+
+        res.endBlock('</bean>');
+
+        // Build final XML:
+        // 1. Add header.
+        var xml = '<?xml version="1.0" encoding="UTF-8"?>\n\n';
+
+        xml += '<!-- ' + $generatorCommon.mainComment() + ' -->\n';
+        xml += '<beans xmlns="http://www.springframework.org/schema/beans"\n';
+        xml += '       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n';
+        xml += '       xmlns:util="http://www.springframework.org/schema/util"\n';
+        xml += '       xsi:schemaLocation="http://www.springframework.org/schema/beans\n';
+        xml += '                           http://www.springframework.org/schema/beans/spring-beans.xsd\n';
+        xml += '                           http://www.springframework.org/schema/util\n';
+        xml += '                           http://www.springframework.org/schema/util/spring-util.xsd">\n';
+
+        // 2. Add external property file
+        if (res.datasources.length > 0
+            || (cluster.sslEnabled && (
+                $commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.keyStoreFilePath) ||
+                $commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustStoreFilePath)))) {
+            xml += '    <!-- Load external properties file. -->\n';
+            xml += '    <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">\n';
+            xml += '        <property name="location" value="classpath:secret.properties"/>\n';
+            xml += '    </bean>\n\n';
+        }
+
+        // 3. Add data sources.
+        if (res.datasources.length > 0) {
+            xml += '    <!-- Data source beans will be initialized from external properties file. -->\n';
+
+            _.forEach(res.datasources, function (item) {
+                var beanId = item.dataSourceBean;
+
+                xml += '    <bean id="' + beanId + '" class="' + item.className + '">\n';
+                switch (item.dialect) {
+                    case 'DB2':
+                        xml += '        <property name="serverName" value="${' + beanId + '.jdbc.server_name}" />\n';
+                        xml += '        <property name="portNumber" value="${' + beanId + '.jdbc.port_number}" />\n';
+                        xml += '        <property name="databaseName" value="${' + beanId + '.jdbc.database_name}" />\n';
+                        xml += '        <property name="driverType" value="${' + beanId + '.jdbc.driver_type}" />\n';
+                        break;
+
+                    default:
+                        xml += '        <property name="URL" value="${' + beanId + '.jdbc.url}" />\n';
+                }
+
+                xml += '        <property name="user" value="${' + beanId + '.jdbc.username}" />\n';
+                xml += '        <property name="password" value="${' + beanId + '.jdbc.password}" />\n';
+                xml += '    </bean>\n\n';
+            });
+        }
+
+        // 3. Add main content.
+        xml += res.asString();
+
+        // 4. Add footer.
+        xml += '\n</beans>';
+
+        return xml;
+    }
+
+    return '';
+};
+
+// For server side we should export XML generation entry point.
+if (typeof window === 'undefined') {
+    module.exports = $generatorXml;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/metadata.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/metadata.js b/modules/control-center-web/src/main/js/routes/metadata.js
new file mode 100644
index 0000000..b9d9445
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/metadata.js
@@ -0,0 +1,192 @@
+/*
+ *
+ *  * 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 async = require('async');
+var router = require('express').Router();
+var db = require('../db');
+
+/* GET metadata page. */
+router.get('/', function (req, res) {
+    res.render('configuration/metadata');
+});
+
+/* GET metadata load dialog. */
+router.get('/metadata-load', function (req, res) {
+    res.render('configuration/metadata-load');
+});
+
+/**
+ * Get spaces and metadata accessed for user account.
+ *
+ * @param req Request.
+ * @param res Response.
+ */
+router.post('/list', function (req, res) {
+    var user_id = req.currentUserId();
+
+    // Get owned space and all accessed space.
+    db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) {
+        if (db.processed(err, res)) {
+            var space_ids = spaces.map(function (value) {
+                return value._id;
+            });
+
+            // Get all caches for spaces.
+            db.Cache.find({space: {$in: space_ids}}).sort('name').exec(function (err, caches) {
+                if (db.processed(err, res)) {
+                    // Get all metadata for spaces.
+                    db.CacheTypeMetadata.find({space: {$in: space_ids}}).sort('valueType').exec(function (err, metadatas) {
+                        if (db.processed(err, res)) {
+                            // Remove deleted caches.
+                            _.forEach(metadatas, function (meta) {
+                                meta.caches = _.filter(meta.caches, function (cacheId) {
+                                    return _.findIndex(caches, function (cache) {
+                                            return cache._id.equals(cacheId);
+                                        }) >= 0;
+                                });
+                            });
+
+                            res.json({
+                                spaces: spaces,
+                                caches: caches.map(function (cache) {
+                                    return {value: cache._id, label: cache.name};
+                                }),
+                                metadatas: metadatas
+                            });
+                        }
+                    });
+                }
+            });
+        }
+    });
+});
+
+function _save(metas, res) {
+    var savedMetas = [];
+
+    if (metas && metas.length > 0)
+        async.forEachOf(metas, function(meta, idx, callback) {
+            var metaId = meta._id;
+            var caches = meta.caches;
+
+            if (metaId)
+                db.CacheTypeMetadata.update({_id: meta._id}, meta, {upsert: true}, function (err) {
+                    if (err)
+                        callback(err);
+                    else
+                        db.Cache.update({_id: {$in: caches}}, {$addToSet: {metadatas: metaId}}, {multi: true}, function (err) {
+                            if (err)
+                                callback(err);
+                            else
+                                db.Cache.update({_id: {$nin: caches}}, {$pull: {metadatas: metaId}}, {multi: true}, function (err) {
+                                    if (err)
+                                        callback(err);
+                                    else {
+                                        savedMetas.push(meta);
+
+                                        callback();
+                                    }
+                                });
+                        });
+                });
+            else {
+                db.CacheTypeMetadata.findOne({space: meta.space, valueType: meta.valueType}, function (err, metadata) {
+                    if (err)
+                        callback(err);
+                    else
+                        if (metadata)
+                            return callback('Cache type metadata with value type: "' + metadata.valueType + '" already exist.');
+
+                        (new db.CacheTypeMetadata(meta)).save(function (err, metadata) {
+                            if (err)
+                                callback(err);
+                            else {
+                                metaId = metadata._id;
+
+                                db.Cache.update({_id: {$in: caches}}, {$addToSet: {metadatas: metaId}}, {multi: true}, function (err) {
+                                    if (err)
+                                        callback(err);
+                                    else {
+                                        savedMetas.push(metadata);
+
+                                        callback();
+                                    }
+                                });
+                            }
+                        });
+                    });
+                }
+        }, function (err) {
+            if (err)
+                res.status(500).send(err);
+            else
+                res.send(savedMetas);
+        });
+    else
+        res.status(500).send('Nothing to save!');
+}
+
+/**
+ * Save metadata.
+ */
+router.post('/save', function (req, res) {
+    _save([req.body], res);
+});
+
+/**
+ * Batch save metadata .
+ */
+router.post('/save/batch', function (req, res) {
+    _save(req.body, res);
+});
+
+/**
+ * Remove metadata by ._id.
+ */
+router.post('/remove', function (req, res) {
+    db.CacheTypeMetadata.remove(req.body, function (err) {
+        if (db.processed(err, res))
+            res.sendStatus(200);
+    })
+});
+
+/**
+ * Remove all metadata.
+ */
+router.post('/remove/all', function (req, res) {
+    var user_id = req.currentUserId();
+
+    // Get owned space and all accessed space.
+    db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) {
+        if (db.processed(err, res)) {
+            var space_ids = spaces.map(function (value) {
+                return value._id;
+            });
+
+            db.CacheTypeMetadata.remove({space: {$in: space_ids}}, function (err) {
+                if (err)
+                    return res.status(500).send(err.message);
+
+                res.sendStatus(200);
+            })
+        }
+    });
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/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
new file mode 100644
index 0000000..70ccbcd
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/notebooks.js
@@ -0,0 +1,157 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+
+var db = require('../db');
+var utils = require('./../helpers/common-utils');
+
+router.get('/new', function (req, res) {
+    res.render('sql/notebook-new', {});
+});
+
+/**
+ * Get notebooks names accessed for user account.
+ *
+ * @param req Request.
+ * @param res Response.
+ */
+router.post('/list', function (req, res) {
+    var user_id = req.currentUserId();
+
+    // Get owned space and all accessed space.
+    db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        var space_ids = spaces.map(function (value) {
+            return value._id;
+        });
+
+        // Get all metadata for spaces.
+        db.Notebook.find({space: {$in: space_ids}}).select('_id name').sort('name').exec(function (err, notebooks) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            res.json(notebooks);
+        });
+    });
+});
+
+/**
+ * Get notebook accessed for user account.
+ *
+ * @param req Request.
+ * @param res Response.
+ */
+router.post('/get', function (req, res) {
+    var user_id = req.currentUserId();
+
+    // Get owned space and all accessed space.
+    db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        var space_ids = spaces.map(function (value) {
+            return value._id;
+        });
+
+        // Get all metadata for spaces.
+        db.Notebook.findOne({space: {$in: space_ids}, _id: req.body.noteId}).exec(function (err, notebook) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            res.json(notebook);
+        });
+    });
+});
+
+/**
+ * Save notebook accessed for user account.
+ *
+ * @param req Request.
+ * @param res Response.
+ */
+router.post('/save', function (req, res) {
+    var note = req.body;
+    var noteId = note._id;
+
+    if (noteId)
+        db.Notebook.update({_id: noteId}, note, {upsert: true}, function (err) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            res.send(noteId);
+        });
+    else
+        db.Notebook.findOne({space: note.space, name: note.name}, function (err, note) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            if (note)
+                return res.status(500).send('Notebook with name: "' + note.name + '" already exist.');
+
+            (new db.Notebook(req.body)).save(function (err, note) {
+                if (err)
+                    return res.status(500).send(err.message);
+
+                res.send(note._id);
+            });
+        });
+});
+
+/**
+ * Remove notebook by ._id.
+ *
+ * @param req Request.
+ * @param res Response.
+ */
+router.post('/remove', function (req, res) {
+    db.Notebook.remove(req.body, function (err) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        res.sendStatus(200);
+    });
+});
+
+/**
+ * Create new notebook for user account.
+ *
+ * @param req Request.
+ * @param res Response.
+ */
+router.post('/new', function (req, res) {
+    var user_id = req.currentUserId();
+
+    // Get owned space and all accessed space.
+    db.Space.findOne({owner: user_id}, function (err, space) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        (new db.Notebook({space: space.id, name: req.body.name, paragraphs: []})).save(function (err, note) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            return res.send(note._id);
+        });
+    });
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/presets.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/presets.js b/modules/control-center-web/src/main/js/routes/presets.js
new file mode 100644
index 0000000..76eb5dd
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/presets.js
@@ -0,0 +1,70 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+var db = require('../db');
+
+/**
+ * Get database presets.
+ *
+ * @param req Request.
+ * @param res Response.
+ */
+router.post('/list', function (req, res) {
+    var userId = req.currentUserId();
+
+    // Get owned space and all accessed space.
+    db.Space.find({$or: [{owner: userId}, {usedBy: {$elemMatch: {account: userId}}}]}, function (err, spaces) {
+        if (db.processed(err, res)) {
+            var spaceIds = spaces.map(function (value) {
+                return value._id;
+            });
+
+            // Get all presets for spaces.
+            db.DatabasePreset.find({space: {$in: spaceIds}}).exec(function (err, presets) {
+                if (db.processed(err, res))
+                    res.json({spaces: spaces, presets: presets});
+            });
+        }
+    });
+});
+
+/**
+ * Save database preset.
+ */
+router.post('/save', function (req, res) {
+    var params = req.body;
+
+    db.DatabasePreset.findOne({space: params.space, jdbcDriverJar: params.jdbcDriverJar}, function (err, preset) {
+        if (db.processed(err, res)) {
+            if (preset)
+                db.DatabasePreset.update({space: params.space, jdbcDriverJar: params.jdbcDriverJar}, params, {upsert: true}, function (err) {
+                    if (db.processed(err, res))
+                        return res.sendStatus(200);
+                });
+            else
+                (new db.DatabasePreset(params)).save(function (err) {
+                    if (db.processed(err, res))
+                        return res.sendStatus(200);
+                });
+        }
+    });
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/profile.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/profile.js b/modules/control-center-web/src/main/js/routes/profile.js
new file mode 100644
index 0000000..8f9d324
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/profile.js
@@ -0,0 +1,105 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+var db = require('../db');
+
+/**
+ * Get user profile page.
+ */
+router.get('/', function (req, res) {
+    var user_id = req.currentUserId();
+
+    db.Account.findById(user_id, function (err) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        res.render('settings/profile');
+    });
+});
+
+function _updateUser(res, user, params) {
+    if (params.userName)
+        user.username = params.userName;
+
+    if (params.email)
+        user.email = params.email;
+
+    if (params.token)
+        user.token = params.token;
+
+    if (params.userName || params.email || params.token || params.newPassword)
+        user.save(function (err) {
+            if (err)
+                // TODO IGNITE-843 Send error to admin.
+                return res.status(500).send('Failed to update profile!');
+
+            res.json(user);
+        });
+    else
+        res.status(200);
+}
+
+function _checkUserEmailAndUpdate(res, user, params) {
+    if (params.email && user.email != params.email) {
+        db.Account.findOne({email: params.email}, function(err, userForEmail) {
+            // TODO send error to admin
+            if (err)
+                return res.status(500).send('Failed to check e-mail!');
+
+            if (userForEmail && userForEmail._id != user._id)
+                return res.status(500).send('User with this e-mail already registered!');
+
+            _updateUser(res, user, params);
+        });
+    }
+    else
+        _updateUser(res, user, params);
+}
+
+/**
+ * Save user profile.
+ */
+router.post('/save', function (req, res) {
+    var params = req.body;
+
+    db.Account.findById(params._id, function (err, user) {
+        if (err)
+        // TODO IGNITE-843 Send error to admin
+            return res.status(500).send('Failed to find user!');
+
+        if (params.newPassword) {
+            var newPassword = params.newPassword;
+
+            if (!newPassword || newPassword.length == 0)
+                return res.status(500).send('Wrong value for new password!');
+
+            user.setPassword(newPassword, function (err, user) {
+                if (err)
+                    return res.status(500).send(err.message);
+
+                _checkUserEmailAndUpdate(res, user, params);
+            });
+        }
+        else
+            _checkUserEmailAndUpdate(res, user, params);
+    });
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/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
new file mode 100644
index 0000000..47b5d56
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/public.js
@@ -0,0 +1,266 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+var passport = require('passport');
+var nodemailer = require('nodemailer');
+
+var db = require('../db');
+var config = require('../helpers/configuration-loader.js');
+var $commonUtils = require('./../helpers/common-utils');
+
+// GET dropdown-menu template.
+router.get('/select', function (req, res) {
+    res.render('templates/select', {});
+});
+
+// GET dropdown-menu template.
+router.get('/validation-error', function (req, res) {
+    res.render('templates/validation-error', {});
+});
+
+// GET confirmation dialog.
+router.get('/message', function (req, res) {
+    res.render('templates/message', {});
+});
+
+// GET confirmation dialog.
+router.get('/confirm', function (req, res) {
+    res.render('templates/confirm', {});
+});
+
+// GET batch confirmation dialog.
+router.get('/confirm/batch', function (req, res) {
+    res.render('templates/batch-confirm', {});
+});
+
+// GET copy dialog.
+router.get('/clone', function (req, res) {
+    res.render('templates/clone', {});
+});
+
+/* GET login dialog. */
+router.get('/login', function (req, res) {
+    res.render('login');
+});
+
+/**
+ * Register new account.
+ */
+router.post('/register', function (req, res) {
+    db.Account.count(function (err, cnt) {
+        if (err)
+            return res.status(401).send(err.message);
+
+        req.body.admin = cnt == 0;
+
+        var account = new db.Account(req.body);
+
+        account.token = $commonUtils.randomString(20);
+
+        db.Account.register(account, req.body.password, function (err, account) {
+            if (err)
+                return res.status(401).send(err.message);
+
+            if (!account)
+                return res.status(500).send('Failed to create account.');
+
+            new db.Space({name: 'Personal space', owner: account._id}).save();
+
+            req.logIn(account, {}, function (err) {
+                if (err)
+                    return res.status(401).send(err.message);
+
+                return res.redirect('/configuration/clusters');
+            });
+        });
+    });
+});
+
+/**
+ * Login in exist account.
+ */
+router.post('/login', function (req, res, next) {
+    passport.authenticate('local', function (err, user) {
+        if (err)
+            return res.status(401).send(err.message);
+
+        if (!user)
+            return res.status(401).send('Invalid email or password');
+
+        req.logIn(user, {}, function (err) {
+            if (err)
+                return res.status(401).send(err.message);
+
+            res.redirect('/configuration/clusters');
+        });
+    })(req, res, next);
+});
+
+/**
+ * Logout.
+ */
+router.get('/logout', function (req, res) {
+    req.logout();
+
+    res.redirect('/');
+});
+
+/**
+ * Send e-mail to user with reset token.
+ */
+router.post('/password/forgot', function(req, res) {
+    var transporter = {
+        service: config.get('smtp:service'),
+        auth: {
+            user:config.get('smtp:username'),
+            pass: config.get('smtp:password')
+        }
+    };
+
+    if (transporter.service == '' || transporter.auth.user == '' || transporter.auth.pass == '')
+        return res.status(401).send('Can\'t send e-mail with instructions to reset password.<br />' +
+            'Please ask webmaster to setup smtp server!');
+
+    var token = $commonUtils.randomString(20);
+
+    db.Account.findOne({ email: req.body.email }, function(err, user) {
+        if (!user)
+            return res.status(401).send('No account with that email address exists!');
+
+        if (err)
+            // TODO IGNITE-843 Send email to admin
+            return res.status(401).send('Failed to reset password!');
+
+        user.resetPasswordToken = token;
+
+        user.save(function(err) {
+            if (err)
+            // TODO IGNITE-843 Send email to admin
+            return res.status(401).send('Failed to reset password!');
+
+            var mailer  = nodemailer.createTransport(transporter);
+
+            var mailOptions = {
+                from: transporter.auth.user,
+                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 + '/password/reset/' + token + '\n\n' +
+                'If you did not request this, please ignore this email and your password will remain unchanged.\n\n' +
+                '--------------\n' +
+                'Apache Ignite Web Console\n'
+            };
+
+            mailer.sendMail(mailOptions, function(err){
+                if (err)
+                    return res.status(401).send('Failed to send e-mail with reset link!<br />' + err);
+
+                return res.status(403).send('An e-mail has been sent with further instructions.');
+            });
+        });
+    });
+});
+
+/**
+ * Change password with given token.
+ */
+router.post('/password/reset', function(req, res) {
+    db.Account.findOne({ resetPasswordToken: req.body.token }, function(err, user) {
+        if (!user)
+            return res.status(500).send('Invalid token for password reset!');
+
+        if (err)
+            // TODO IGNITE-843 Send email to admin
+            return res.status(500).send('Failed to reset password!');
+
+        user.setPassword(req.body.password, function (err, updatedUser) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            updatedUser.resetPasswordToken = undefined;
+
+            updatedUser.save(function (err) {
+                if (err)
+                    return res.status(500).send(err.message);
+
+                var transporter = {
+                    service: config.get('smtp:service'),
+                    auth: {
+                        user: config.get('smtp:username'),
+                        pass: config.get('smtp:password')
+                    }
+                };
+
+                var mailer = nodemailer.createTransport(transporter);
+
+                var mailOptions = {
+                    from: transporter.auth.user,
+                    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\n' +
+                    'Now you can login: http://' + req.headers.host + '\n\n' +
+                    '--------------\n' +
+                    'Apache Ignite Web Console\n'
+                };
+
+                mailer.sendMail(mailOptions, function (err) {
+                    if (err)
+                        return res.status(503).send('Password was changed, but failed to send confirmation e-mail!<br />' + err);
+
+                    return res.status(200).send(user.email);
+                });
+            });
+        });
+    });
+});
+
+router.get('/password/reset', function (req, res) {
+    res.render('reset');
+});
+
+/* GET reset password page. */
+router.get('/password/reset/:token', function (req, res) {
+    var token = req.params.token;
+
+    var data = {token: token};
+
+    db.Account.findOne({resetPasswordToken: token}, function (err, user) {
+        if (!user)
+            data.error = 'Invalid token for password reset!';
+        else if (err)
+            data.error = err;
+        else
+            data.email = user.email;
+
+        res.render('reset', data);
+    });
+});
+
+/* GET home page. */
+router.get('/', function (req, res) {
+    if (req.isAuthenticated())
+        res.redirect('/configuration/clusters');
+    else
+        res.render('index');
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/sql.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/sql.js b/modules/control-center-web/src/main/js/routes/sql.js
new file mode 100644
index 0000000..306539b
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/sql.js
@@ -0,0 +1,39 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+var db = require('../db');
+
+router.get('/rate', function (req, res) {
+    res.render('sql/paragraph-rate', {});
+});
+
+router.get('/chart-settings', function (req, res) {
+    res.render('sql/chart-settings', {});
+});
+
+router.get('/cache-metadata', function (req, res) {
+    res.render('sql/cache-metadata', {});
+});
+
+router.get('/:noteId', function (req, res) {
+    res.render('sql/sql', {noteId: req.params.noteId});
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/summary.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/summary.js b/modules/control-center-web/src/main/js/routes/summary.js
new file mode 100644
index 0000000..911b495
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/summary.js
@@ -0,0 +1,104 @@
+/*
+ *
+ *  * 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 db = require('../db');
+
+var router = require('express').Router();
+
+var $generatorXml = require('./generator/generator-xml');
+var $generatorJava = require('./generator/generator-java');
+var $generatorDocker = require('./generator/generator-docker');
+var $generatorProperties = require('./generator/generator-properties');
+
+// GET template for summary tabs.
+router.get('/summary-tabs', function (req, res) {
+    res.render('configuration/summary-tabs', {});
+});
+
+/* GET summary page. */
+router.get('/', function (req, res) {
+    res.render('configuration/summary');
+});
+
+router.post('/download', function (req, res) {
+    // Get cluster with all inner objects (caches, metadata).
+    db.Cluster.findById(req.body._id).deepPopulate('caches caches.metadatas').exec(function (err, cluster) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        if (!cluster)
+            return res.sendStatus(404);
+
+        var clientNearConfiguration = req.body.clientNearConfiguration;
+
+        var archiver = require('archiver');
+
+        // Creating archive.
+        var zip = archiver('zip');
+
+        zip.on('error', function (err) {
+            res.status(500).send({error: err.message});
+        });
+
+        // On stream closed we can end the request.
+        res.on('close', function () {
+            return res.status(200).send('OK').end();
+        });
+
+        // Set the archive name.
+        res.attachment(cluster.name + (clientNearConfiguration ? '-client' : '-server') + '-configuration.zip');
+
+        // Send the file to the page output.
+        zip.pipe(res);
+
+        var builder = $generatorProperties.sslProperties(cluster);
+
+        if (!clientNearConfiguration) {
+            zip.append($generatorDocker.clusterDocker(cluster, req.body.os), {name: 'Dockerfile'});
+
+            builder = $generatorProperties.dataSourcesProperties(cluster, builder);
+        }
+
+        if (builder)
+            zip.append(builder.asString(), {name: 'secret.properties'});
+
+        zip.append($generatorXml.cluster(cluster, clientNearConfiguration), {name: cluster.name + '.xml'})
+            .append($generatorJava.cluster(cluster, false, clientNearConfiguration),
+                {name: cluster.name + '.snippet.java'})
+            .append($generatorJava.cluster(cluster, true, clientNearConfiguration),
+                {name: 'ConfigurationFactory.java'});
+
+        $generatorJava.pojos(cluster.caches, req.body.useConstructor, req.body.includeKeyFields);
+
+        var metadatas = $generatorJava.metadatas;
+
+        for (var metaIx = 0; metaIx < metadatas.length; metaIx ++) {
+            var meta = metadatas[metaIx];
+
+            if (meta.keyClass)
+                zip.append(meta.keyClass, {name: meta.keyType.replace(/\./g, '/') + '.java'});
+
+            zip.append(meta.valueClass, {name: meta.valueType.replace(/\./g, '/') + '.java'});
+        }
+
+        zip.finalize();
+    });
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/configuration/caches.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/caches.jade b/modules/control-center-web/src/main/js/views/configuration/caches.jade
new file mode 100644
index 0000000..3a8dbfc
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/caches.jade
@@ -0,0 +1,46 @@
+//-
+    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 sidebar
+
+append scripts
+    script(src='/caches-controller.js')
+
+include ../includes/controls
+
+block content
+    .docs-header
+        h1 Create and Configure Ignite Caches
+    .docs-body(ng-controller='cachesController')
+        div(dw-loading='loadingCachesScreen' dw-loading-options='{text: "Loading caches screen...", className: "page-loading-overlay"}')
+            div(ng-show='ui.ready')
+                +block-callout('{{screenTip.workflowTitle}}', 'screenTip.workflowContent', '{{screenTip.whatsNextTitle}}', 'screenTip.whatsNextContent')
+                hr
+                +main-table('Caches:', 'caches', 'cacheName', 'selectItem(row)', '{{$index + 1}}) {{row.name}}, {{row.cacheMode | displayValue:cacheModes:"Cache mode not set"}}, {{row.atomicityMode | displayValue:atomicities:"Cache atomicity not set"}}')
+                .padding-top-dflt(bs-affix)
+                    .panel-tip-container(data-placement='bottom' bs-tooltip data-title='Create new caches')
+                        button.btn.btn-primary(id='new-item' ng-click='createItem()') Add cache
+                    +save-remove-buttons('cache')
+                    hr
+                form.form-horizontal(name='ui.inputForm' ng-if='backupItem' novalidate unsaved-warning-form)
+                    .panel-group(bs-collapse ng-model='panels.activePanels' data-allow-multiple='true')
+                        +groups('general', 'backupItem')
+                        div(ng-show='ui.expanded')
+                            +advanced-options
+                            +groups('advanced', 'backupItem')
+                    +advanced-options
+                    .section(ng-if='ui.expanded')
+                        +save-remove-buttons('cache')

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/configuration/clusters.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/clusters.jade b/modules/control-center-web/src/main/js/views/configuration/clusters.jade
new file mode 100644
index 0000000..3ce51aa
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/clusters.jade
@@ -0,0 +1,46 @@
+//-
+    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 sidebar
+
+append scripts
+    script(src='/clusters-controller.js')
+
+include ../includes/controls
+
+block content
+    .docs-header
+        h1 Create and Configure Ignite Clusters
+    .docs-body(ng-controller='clustersController')
+        div(dw-loading='loadingClustersScreen' dw-loading-options='{text: "Loading clusters screen...", className: "page-loading-overlay"}')
+            div(ng-show='ui.ready')
+                +block-callout('{{screenTip.workflowTitle}}', 'screenTip.workflowContent', '{{screenTip.whatsNextTitle}}', 'screenTip.whatsNextContent')
+                hr
+                +main-table('Clusters:', 'clusters', 'clusterName', 'selectItem(row)', '{{$index + 1}}) {{row.name}}, {{row.discovery.kind | displayValue:discoveries:"Discovery not set"}}')
+                .padding-top-dflt(bs-affix)
+                    .panel-tip-container(data-placement='bottom' bs-tooltip data-title='Create new cluster')
+                        button.btn.btn-primary(id='new-item' ng-click='createItem()') Add cluster
+                    +save-remove-buttons('cluster')
+                    hr
+                form.form-horizontal(name='ui.inputForm' ng-if='backupItem' novalidate unsaved-warning-form)
+                    .panel-group(bs-collapse ng-model='panels.activePanels' data-allow-multiple='true' ng-click='triggerDigest = true')
+                        +groups('general', 'backupItem')
+                        div(ng-show='ui.expanded')
+                            +advanced-options
+                            +groups('advanced', 'backupItem')
+                    +advanced-options
+                    .section(ng-show='ui.expanded')
+                        +save-remove-buttons('cluster')

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade b/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade
new file mode 100644
index 0000000..42e7798
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade
@@ -0,0 +1,89 @@
+//-
+    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.
+
+include ../includes/controls
+
+mixin chk(mdl, change, tip)
+    input(type='checkbox' ng-model=mdl ng-change=change bs-tooltip='' data-title=tip data-placement='bottom')
+
+.modal.center(role='dialog')
+    .modal-dialog
+        .modal-content(dw-loading='loadingMetadataFromDb' dw-loading-options='{text: ""}')
+            #errors-container.modal-header.header
+                button.close(ng-click='$hide()' aria-hidden='true') &times;
+                h4.modal-title Load metadata from database
+            .metadata-content(ng-show='loadMeta.action == "connect"' style='margin-bottom: 60px')
+                form.form-horizontal(name='loadForm' novalidate)
+                    .settings-row(ng-repeat='field in metadataDb')
+                        +form-row-custom(['col-xs-4 col-sm-3 col-md-3'], ['col-xs-8 col-sm-9 col-md-9'], 'preset')
+            .metadata-content(ng-show='loadMeta.action == "schemas"')
+                table.table.metadata(st-table='loadMeta.displayedSchemas' st-safe-src='loadMeta.schemas')
+                    thead
+                        tr
+                            th.header(colspan='2')
+                                .col-sm-4.pull-right
+                                    input.form-control(type='text' st-search='' placeholder='Filter schemas...' ng-model='loadMeta.displayedSchemasFilter' ng-change='selectSchema()')
+                        tr
+                            th(width='50px')
+                                +chk('loadMeta.allSchemasSelected',  'selectAllSchemas()', 'Select all schemas')
+                            th
+                                label Schemas
+                        tbody
+                            tr
+                                td(colspan='2')
+                                    .scrollable-y(style='height: 184px')
+                                        table.table-modal-striped(id='metadataSchemaData')
+                                            tbody
+                                                tr(ng-repeat='schema in loadMeta.displayedSchemas')
+                                                    td(width='50px')
+                                                        input(type='checkbox' ng-model='schema.use' ng-change='selectSchema()')
+                                                    td
+                                                        label {{::schema.name}}
+            .metadata-content(ng-show='loadMeta.action == "tables"')
+                .metadata-package-name
+                    label.required Package:
+                    span
+                        input.form-control(id='metadataLoadPackage' type="text" ng-model='ui.packageName' placeholder='Package for POJOs generation' bs-tooltip='' data-title='Package that will be used for POJOs generation' data-placement='top' data-trigger='hover')
+                table.table.metadata(st-table='loadMeta.displayedTables' st-safe-src='loadMeta.tables')
+                    thead
+                        tr
+                            th.header(colspan='3')
+                                .col-sm-4.pull-right
+                                    input.form-control(type='text' st-search='' placeholder='Filter tables...' ng-model='loadMeta.displayedTablesFilter' ng-change='selectTable()')
+                        tr
+                            th(width='50px')
+                                +chk('loadMeta.allTablesSelected',  'selectAllTables()', 'Select all tables')
+                            th(width='200px')
+                                label Schemas
+                            th
+                                label Tables
+                    tbody
+                        tr
+                            td(colspan='3')
+                                .scrollable-y(style='height: 146px')
+                                    table.table-modal-striped(id='metadataTableData')
+                                        tbody
+                                            tr(ng-repeat='table in loadMeta.displayedTables')
+                                                td(width='50px')
+                                                    input(type='checkbox' ng-model='table.use' ng-change='selectTable()')
+                                                td(width='200px')
+                                                    label {{::table.schema}}
+                                                td
+                                                    label {{::table.tbl}}
+            .modal-footer
+                label.labelField {{loadMeta.info}}
+                button.btn.btn-primary(ng-show='loadMeta.action != "connect"' ng-click='loadMetadataPrev()') Prev
+                a.btn.btn-primary(ng-click='loadMetadataNext()' ng-disabled='!nextAvailable()' bs-tooltip data-title='{{nextTooltipText()}}' data-placement='bottom') {{loadMeta.button}}