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/30 11:37:00 UTC

[16/28] incubator-ignite git commit: # ignite-843 Rename

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/routes/generator/xml.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/xml.js b/modules/control-center-web/src/main/js/routes/generator/xml.js
new file mode 100644
index 0000000..079f268
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/generator/xml.js
@@ -0,0 +1,736 @@
+/*
+ * 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 _ = require('lodash');
+
+var generatorUtils = require("./common");
+var dataStructures = require("../../helpers/data-structures.js");
+
+exports.generateClusterConfiguration = function(cluster, clientNearConfiguration) {
+    var res = generatorUtils.builder();
+
+    res.datasources = [];
+    res.deep = 1;
+
+    if (clientNearConfiguration) {
+        res.startBlock('<bean id="nearCacheBean" class="org.apache.ignite.configuration.NearCacheConfiguration">');
+
+        if (clientNearConfiguration.nearStartSize)
+            addProperty(res, clientNearConfiguration, 'nearStartSize');
+
+        if (clientNearConfiguration.nearEvictionPolicy && clientNearConfiguration.nearEvictionPolicy.kind)
+            createEvictionPolicy(res, clientNearConfiguration.nearEvictionPolicy, 'nearEvictionPolicy');
+
+        res.endBlock('</bean>');
+
+        res.line();
+    }
+
+    // Generate Ignite Configuration.
+    res.startBlock('<bean class="org.apache.ignite.configuration.IgniteConfiguration">');
+
+    if (clientNearConfiguration) {
+        res.line('<property name="clientMode" value="true" />');
+
+        res.line();
+    }
+
+    // Generate discovery.
+    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">');
+
+                addProperty(res, d.Multicast, 'multicastGroup');
+                addProperty(res, d.Multicast, 'multicastPort');
+                addProperty(res, d.Multicast, 'responseWaitTime');
+                addProperty(res, d.Multicast, 'addressRequestAttempts');
+                addProperty(res, d.Multicast, 'localAddress');
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'Vm':
+                if (d.Vm.addresses.length > 0) {
+                    res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">');
+
+                    addListProperty(res, d.Vm, 'addresses');
+
+                    res.endBlock('</bean>');
+                }
+                else {
+                    res.line('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder"/>');
+                }
+
+                break;
+
+            case 'S3':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.s3.TcpDiscoveryS3IpFinder">');
+
+                if (d.S3 && d.S3.bucketName)
+                    res.line('<property name="bucketName" value="' + escapeAttr(d.S3.bucketName) + '" />');
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'Cloud':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.cloud.TcpDiscoveryCloudIpFinder">');
+
+                addProperty(res, d.Cloud, 'credential');
+                addProperty(res, d.Cloud, 'credentialPath');
+                addProperty(res, d.Cloud, 'identity');
+                addProperty(res, d.Cloud, 'provider');
+                addListProperty(res, d.Cloud, 'regions');
+                addListProperty(res, d.Cloud, 'zones');
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'GoogleStorage':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.gce.TcpDiscoveryGoogleStorageIpFinder">');
+
+                addProperty(res, d.GoogleStorage, 'projectName');
+                addProperty(res, d.GoogleStorage, 'bucketName');
+                addProperty(res, d.GoogleStorage, 'serviceAccountP12FilePath');
+
+                //if (d.GoogleStorage.addrReqAttempts) todo ????
+                //    res.line('<property name="serviceAccountP12FilePath" value="' + escapeAttr(d.GoogleStorage.addrReqAttempts) + '"/>');
+
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'Jdbc':
+                res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinder">');
+                res.line('<property name="initSchema" value="' + (d.Jdbc.initSchema != null || d.Jdbc.initSchema) + '"/>');
+                res.endBlock('</bean>');
+
+                break;
+
+            case 'SharedFs':
+                if (d.SharedFs.path) {
+                    res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinder">');
+                    addProperty(res, d.SharedFs, 'path');
+                    res.endBlock('</bean>');
+                }
+                else {
+                    res.line('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinder"/>');
+                }
+
+                break;
+
+            default:
+                throw "Unknown discovery kind: " + d.kind;
+        }
+
+        res.endBlock('</property>');
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true
+    }
+
+    // Generate atomics group.
+    addBeanWithProperties(res, cluster.atomicConfiguration, 'atomicConfiguration',
+        generatorUtils.atomicConfiguration.className, generatorUtils.atomicConfiguration.fields);
+    res.needEmptyLine = true;
+
+    // Generate communication group.
+    addProperty(res, cluster, 'networkTimeout');
+    addProperty(res, cluster, 'networkSendRetryDelay');
+    addProperty(res, cluster, 'networkSendRetryCount');
+    addProperty(res, cluster, 'segmentCheckFrequency');
+    addProperty(res, cluster, 'waitForSegmentOnStart');
+    addProperty(res, cluster, 'discoveryStartupDelay');
+    res.needEmptyLine = true;
+
+    // Generate deployment group.
+    addProperty(res, cluster, 'deploymentMode');
+    res.needEmptyLine = true;
+
+    // Generate events group.
+    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('<array>');
+
+            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.eventGroups[eventGroup];
+
+                for (var k = 0; k < eventList.length; k++) {
+                    res.line('<util:constant static-field="org.apache.ignite.events.EventType.' + eventList[k] + '"/>')
+                }
+            }
+
+            res.endBlock('</array>');
+        }
+        
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+
+    // Generate marshaller group.
+    var marshaller = cluster.marshaller;
+
+    if (marshaller && marshaller.kind) {
+        var marshallerDesc = generatorUtils.marshallers[marshaller.kind];
+
+        addBeanWithProperties(res, marshaller[marshaller.kind], 'marshaller', marshallerDesc.className, marshallerDesc.fields, true);
+        res.needEmptyLine = true;
+    }
+
+    addProperty(res, cluster, 'marshalLocalJobs');
+    addProperty(res, cluster, 'marshallerCacheKeepAliveTime');
+    addProperty(res, cluster, 'marshallerCacheThreadPoolSize');
+    res.needEmptyLine = true;
+
+    // Generate metrics group.
+    addProperty(res, cluster, 'metricsExpireTime');
+    addProperty(res, cluster, 'metricsHistorySize');
+    addProperty(res, cluster, 'metricsLogFrequency');
+    addProperty(res, cluster, 'metricsUpdateFrequency');
+    res.needEmptyLine = true;
+
+    // Generate PeerClassLoading group.
+    addProperty(res, cluster, 'peerClassLoadingEnabled');
+    addListProperty(res, cluster, 'peerClassLoadingLocalClassPathExclude');
+    addProperty(res, cluster, 'peerClassLoadingMissedResourcesCacheSize');
+    addProperty(res, cluster, 'peerClassLoadingThreadPoolSize');
+    res.needEmptyLine = true;
+
+    // Generate swap group.
+    if (cluster.swapSpaceSpi && cluster.swapSpaceSpi.kind == 'FileSwapSpaceSpi') {
+        addBeanWithProperties(res, cluster.swapSpaceSpi.FileSwapSpaceSpi, 'swapSpaceSpi',
+            generatorUtils.swapSpaceSpi.className, generatorUtils.swapSpaceSpi.fields, true);
+
+        res.needEmptyLine = true;
+    }
+
+    // Generate time group.
+    addProperty(res, cluster, 'clockSyncSamples');
+    addProperty(res, cluster, 'clockSyncFrequency');
+    addProperty(res, cluster, 'timeServerPortBase');
+    addProperty(res, cluster, 'timeServerPortRange');
+    res.needEmptyLine = true;
+
+    // Generate thread pools group.
+    addProperty(res, cluster, 'publicThreadPoolSize');
+    addProperty(res, cluster, 'systemThreadPoolSize');
+    addProperty(res, cluster, 'managementThreadPoolSize');
+    addProperty(res, cluster, 'igfsThreadPoolSize');
+    res.needEmptyLine = true;
+
+    // Generate transactions group.
+    addBeanWithProperties(res, cluster.transactionConfiguration, 'transactionConfiguration',
+        generatorUtils.transactionConfiguration.className, generatorUtils.transactionConfiguration.fields);
+    res.needEmptyLine = true;
+
+    // Generate caches configs.
+    if (cluster.caches && cluster.caches.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="cacheConfiguration">');
+        res.startBlock('<list>');
+
+        for (var i = 0; i < cluster.caches.length; i++) {
+            if (i > 0)
+                res.line();
+
+            var cache = cluster.caches[i];
+
+            generateCacheConfiguration(res, cache);
+        }
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+
+        res.needEmptyLine = true;
+    }
+
+    res.endBlock('</bean>');
+
+    // Build final XML:
+    // 1. Add header.
+    var xml = '<?xml version="1.0" encoding="UTF-8"?>\n\n';
+
+    xml += '<!-- ' + generatorUtils.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 and all data sources.
+    if (res.datasources.length > 0) {
+        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';
+
+        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';
+            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.join('');
+
+    // 4. Add footer.
+    xml += '</beans>\n';
+
+    return xml;
+};
+
+function createEvictionPolicy(res, evictionPolicy, propertyName) {
+    if (evictionPolicy && evictionPolicy.kind) {
+        var e = generatorUtils.evictionPolicies[evictionPolicy.kind];
+
+        var obj = evictionPolicy[evictionPolicy.kind.toUpperCase()];
+
+        addBeanWithProperties(res, obj, propertyName, e.className, e.fields, true);
+    }
+}
+
+function addCacheTypeMetadataDatabaseFields(res, meta, fieldsProperty) {
+    var fields = meta[fieldsProperty];
+
+    if (fields && fields.length > 0) {
+        res.startBlock('<property name="' + fieldsProperty + '">');
+
+        res.startBlock('<list>');
+
+        _.forEach(fields, function (field) {
+            res.startBlock('<bean class="org.apache.ignite.cache.CacheTypeFieldMetadata">');
+
+            addProperty(res, field, 'databaseName');
+
+            res.startBlock('<property name="databaseType">');
+            res.line('<util:constant static-field="java.sql.Types.' + field.databaseType + '"/>');
+            res.endBlock('</property>');
+
+            addProperty(res, field, 'javaName');
+
+            addElement(res, 'property', 'name', 'javaType', 'value', generatorUtils.javaBuildInClass(field.javaType));
+
+            res.endBlock('</bean>');
+        });
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+    }
+}
+
+function addCacheTypeMetadataQueryFields(res, meta, fieldsProperty) {
+    var fields = meta[fieldsProperty];
+
+    if (fields && fields.length > 0) {
+        res.startBlock('<property name="' + fieldsProperty + '">');
+
+        res.startBlock('<map>');
+
+        _.forEach(fields, function (field) {
+            addElement(res, 'entry', 'key', field.name, 'value', generatorUtils.javaBuildInClass(field.className));
+        });
+
+        res.endBlock('</map>');
+
+        res.endBlock('</property>');
+    }
+}
+
+function addCacheTypeMetadataGroups(res, meta) {
+    var groups = meta.groups;
+
+    if (groups && groups.length > 0) {
+        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="' + generatorUtils.javaBuildInClass(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>');
+    }
+}
+
+function generateCacheTypeMetadataConfiguration(res, meta) {
+    if (!res)
+        res = generatorUtils.builder();
+
+    res.startBlock('<bean class="org.apache.ignite.cache.CacheTypeMetadata">');
+
+    addProperty(res, meta, 'databaseSchema');
+    addProperty(res, meta, 'databaseTable');
+
+    addProperty(res, meta, 'keyType');
+    addProperty(res, meta, 'valueType');
+
+    addCacheTypeMetadataDatabaseFields(res, meta, 'keyFields');
+    addCacheTypeMetadataDatabaseFields(res, meta, 'valueFields');
+
+    addCacheTypeMetadataQueryFields(res, meta, 'queryFields');
+    addCacheTypeMetadataQueryFields(res, meta, 'ascendingFields');
+    addCacheTypeMetadataQueryFields(res, meta, 'descendingFields');
+
+    addListProperty(res, meta, 'textFields');
+
+    addCacheTypeMetadataGroups(res, meta);
+
+    res.endBlock('</bean>');
+
+    return res;
+}
+
+function generateCacheConfiguration(res, cacheCfg) {
+    if (!res)
+        res = generatorUtils.builder();
+
+    res.startBlock('<bean class="org.apache.ignite.configuration.CacheConfiguration">');
+
+    addProperty(res, cacheCfg, 'name');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'mode', 'cacheMode');
+
+    addProperty(res, cacheCfg, 'atomicityMode');
+    addProperty(res, cacheCfg, 'backups');
+    addProperty(res, cacheCfg, 'startSize');
+    addProperty(res, cacheCfg, 'readFromBackup');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'memoryMode');
+    addProperty(res, cacheCfg, 'offHeapMaxMemory');
+    addProperty(res, cacheCfg, 'swapEnabled');
+    addProperty(res, cacheCfg, 'copyOnRead');
+
+    res.needEmptyLine = true;
+
+    createEvictionPolicy(res, cacheCfg.evictionPolicy, 'evictionPolicy');
+
+    res.needEmptyLine = true;
+
+    if (cacheCfg.nearCacheEnabled) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="nearConfiguration">');
+        res.startBlock('<bean class="org.apache.ignite.configuration.NearCacheConfiguration">');
+
+        if (cacheCfg.nearConfiguration && cacheCfg.nearConfiguration.nearStartSize)
+            addProperty(res, cacheCfg.nearConfiguration, 'nearStartSize');
+
+        if (cacheCfg.nearConfiguration && cacheCfg.nearConfiguration.nearEvictionPolicy.kind)
+            createEvictionPolicy(res, cacheCfg.nearConfiguration.nearEvictionPolicy, 'nearEvictionPolicy');
+
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+    }
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'sqlEscapeAll');
+    addProperty(res, cacheCfg, 'sqlOnheapRowCacheSize');
+    addProperty(res, cacheCfg, 'longQueryWarningTimeout');
+
+    if (cacheCfg.indexedTypes && cacheCfg.indexedTypes.length > 0) {
+        res.startBlock('<property name="indexedTypes">');
+        res.startBlock('<list>');
+
+        for (var i = 0; i < cacheCfg.indexedTypes.length; i++) {
+            var pair = cacheCfg.indexedTypes[i];
+
+            res.line('<value>' + generatorUtils.javaBuildInClass(pair.keyClass) + '</value>');
+            res.line('<value>' + generatorUtils.javaBuildInClass(pair.valueClass) + '</value>');
+        }
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+    }
+
+    addListProperty(res, cacheCfg, 'sqlFunctionClasses', 'array');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'rebalanceMode');
+    addProperty(res, cacheCfg, 'rebalanceThreadPoolSize');
+    addProperty(res, cacheCfg, 'rebalanceBatchSize');
+    addProperty(res, cacheCfg, 'rebalanceOrder');
+    addProperty(res, cacheCfg, 'rebalanceDelay');
+    addProperty(res, cacheCfg, 'rebalanceTimeout');
+    addProperty(res, cacheCfg, 'rebalanceThrottle');
+
+    res.needEmptyLine = true;
+
+    if (cacheCfg.cacheStoreFactory && cacheCfg.cacheStoreFactory.kind) {
+        var storeFactory = cacheCfg.cacheStoreFactory[cacheCfg.cacheStoreFactory.kind];
+        var data = generatorUtils.storeFactories[cacheCfg.cacheStoreFactory.kind];
+
+        addBeanWithProperties(res, storeFactory, 'cacheStoreFactory', data.className, data.fields, true);
+
+        if (storeFactory.dialect) {
+            if (_.findIndex(res.datasources, function (ds) {
+                    return ds.dataSourceBean == storeFactory.dataSourceBean;
+                }) < 0) {
+                res.datasources.push({
+                    dataSourceBean: storeFactory.dataSourceBean,
+                    className: generatorUtils.dataSources[storeFactory.dialect]
+                });
+            }
+        }
+    }
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'loadPreviousValue');
+    addProperty(res, cacheCfg, 'readThrough');
+    addProperty(res, cacheCfg, 'writeThrough');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'invalidate');
+    addProperty(res, cacheCfg, 'defaultLockTimeout');
+    addProperty(res, cacheCfg, 'transactionManagerLookupClassName');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'writeBehindEnabled');
+    addProperty(res, cacheCfg, 'writeBehindBatchSize');
+    addProperty(res, cacheCfg, 'writeBehindFlushSize');
+    addProperty(res, cacheCfg, 'writeBehindFlushFrequency');
+    addProperty(res, cacheCfg, 'writeBehindFlushThreadCount');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'statisticsEnabled');
+    addProperty(res, cacheCfg, 'managementEnabled');
+
+    res.needEmptyLine = true;
+
+    addProperty(res, cacheCfg, 'maxConcurrentAsyncOperations');
+
+    // Generate cache type metadata configs.
+    if ((cacheCfg.queryMetadata && cacheCfg.queryMetadata.length > 0) ||
+        (cacheCfg.storeMetadata && cacheCfg.storeMetadata.length > 0)) {
+        res.emptyLineIfNeeded();
+
+        res.startBlock('<property name="typeMetadata">');
+        res.startBlock('<list>');
+
+        var metaNames = [];
+
+        if (cacheCfg.queryMetadata && cacheCfg.queryMetadata.length > 0) {
+            _.forEach(cacheCfg.queryMetadata, function (meta) {
+                if (!_.contains(metaNames, meta.name)) {
+                    metaNames.push(meta.name);
+
+                    generateCacheTypeMetadataConfiguration(res, meta);
+                }
+            });
+        }
+
+        if (cacheCfg.storeMetadata && cacheCfg.storeMetadata.length > 0) {
+            _.forEach(cacheCfg.storeMetadata, function (meta) {
+                if (!_.contains(metaNames, meta.name)) {
+                    metaNames.push(meta.name);
+
+                    generateCacheTypeMetadataConfiguration(res, meta);
+                }
+            });
+        }
+
+        res.endBlock('</list>');
+        res.endBlock('</property>');
+    }
+
+    res.endBlock('</bean>');
+
+    return res;
+}
+
+function addElement(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);
+}
+
+function addProperty(res, obj, propName, setterName) {
+    var val = obj[propName];
+
+    if (generatorUtils.isDefined(val))
+        addElement(res, 'property', 'name', setterName ? setterName : propName, 'value', escapeAttr(val));
+}
+
+function addBeanWithProperties(res, bean, beanPropName, beanClass, props, createBeanAlthoughNoProps) {
+    if (bean && generatorUtils.hasProperty(bean, props)) {
+        res.emptyLineIfNeeded();
+        res.startBlock('<property name="' + beanPropName + '">');
+        res.startBlock('<bean class="' + beanClass + '">');
+
+        for (var propName in props) {
+            if (props.hasOwnProperty(propName)) {
+                var descr = props[propName];
+
+                if (descr) {
+                    if (descr.type == 'list') {
+                        addListProperty(res, bean, propName, descr.setterName);
+                    }
+                    else if (descr.type == 'className') {
+                        if (bean[propName]) {
+                            res.startBlock('<property name="' + propName + '">');
+                            res.line('<bean class="' + generatorUtils.knownClasses[bean[propName]].className + '"/>');
+                            res.endBlock('</property>');
+                        }
+                    }
+                    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="' + escapeAttr(nameAndValue.substring(0, eqIndex)) + '">' +
+                                            + escape(nameAndValue.substr(eqIndex + 1)) + '</prop>');
+                                }
+                            }
+
+                            res.endBlock('</props>');
+                            res.endBlock('</property>');
+                        }
+                    }
+                    else
+                        addProperty(res, bean, propName, descr.setterName);
+                }
+                else
+                    addProperty(res, bean, propName);
+            }
+        }
+
+        res.endBlock('</bean>');
+        res.endBlock('</property>');
+    }
+    else if (createBeanAlthoughNoProps) {
+        res.emptyLineIfNeeded();
+        res.line('<property name="' + beanPropName + '">');
+        res.line('    <bean class="' + beanClass + '"/>');
+        res.line('</property>');
+    }
+}
+function addListProperty(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>' + 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>');
+    }
+}
+
+function escapeAttr(s) {
+    if (typeof(s) != 'string')
+        return s;
+
+    return s.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+}
+
+function escape(s) {
+    if (typeof(s) != 'string')
+        return s;
+
+    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/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..0af624e
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/metadata.js
@@ -0,0 +1,95 @@
+/*
+ * 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 metadata page. */
+router.get('/', function (req, res) {
+    res.render('configuration/metadata');
+});
+
+/**
+ * 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 (err)
+            return res.status(500).send(err.message);
+
+        var space_ids = spaces.map(function (value) {
+            return value._id;
+        });
+
+        // Get all metadata for spaces.
+        db.CacheTypeMetadata.find({space: {$in: space_ids}}).sort('name').exec(function (err, metadatas) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            res.json({spaces: spaces, metadatas: metadatas});
+        });
+    });
+});
+
+/**
+ * Save metadata.
+ */
+router.post('/save', function (req, res) {
+    if (req.body._id)
+        db.CacheTypeMetadata.update({_id: req.body._id}, req.body, {upsert: true}, function (err) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            res.send(req.body._id);
+        });
+    else {
+        db.CacheTypeMetadata.findOne({space: req.body.space, name: req.body.name}, function (err, metadata) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            if (metadata)
+                return res.status(500).send('Cache type metadata with name: "' + metadata.name + '" already exist.');
+
+            (new db.CacheTypeMetadata(req.body)).save(function (err, metadata) {
+                if (err)
+                    return res.status(500).send(err.message);
+
+                res.send(metadata._id);
+            });
+        });
+    }
+});
+
+/**
+ * Remove metadata by ._id.
+ */
+router.post('/remove', function (req, res) {
+    db.CacheTypeMetadata.remove(req.body, function (err) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        res.sendStatus(200);
+    })
+});
+
+module.exports = router;
\ 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/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..0269e7d
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/profile.js
@@ -0,0 +1,97 @@
+/*
+ * 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.all('/profile/*', function (req, res, next) {
+    var userId = req.body._id;
+
+    if (userId != req.currentUserId() && userId != req.user._id)
+        return res.sendStatus(403);
+    else
+        next();
+});
+
+/**
+ * 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');
+    });
+});
+
+/**
+ * Save user profile.
+ */
+router.post('/saveUser', function (req, res) {
+    var params = req.body;
+
+    if (params.newPassword) {
+        var newPassword = params.newPassword;
+
+        if (!newPassword || newPassword.length == 0)
+            return res.status(500).send('Wrong value for new password');
+
+        db.Account.findById(params._id, function (err, user) {
+            if (err)
+                return res.status(500).send(err);
+
+            user.setPassword(newPassword, function (err, updatedUser) {
+                if (err)
+                    return res.status(500).send(err.message);
+
+                if (params.userName)
+                    updatedUser.username = params.userName;
+
+                if (params.email)
+                    updatedUser.email = params.email;
+
+                updatedUser.save(function (err) {
+                    if (err)
+                        return res.status(500).send(err.message);
+
+                    res.json(user);
+                });
+            });
+        });
+    }
+    else if (params.userName || params.email) {
+        var upd = {};
+
+        if (params.userName)
+            upd.username = params.userName;
+
+        if (params.email)
+            upd.email = params.email;
+
+        db.Account.findByIdAndUpdate(params._id, upd, {new: true}, function (err, val) {
+            if (err)
+                return res.status(500).send(err.message);
+
+            res.json(val);
+        })
+    }
+});
+
+module.exports = router;
\ 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/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..b3cb983
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/public.js
@@ -0,0 +1,123 @@
+/*
+ * 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 db = require('../db');
+
+// GET dropdown-menu template.
+router.get('/select', function (req, res) {
+    res.render('templates/select', {});
+});
+
+// GET dynamic tabs template.
+router.get('/tab', function (req, res) {
+    res.render('templates/tab', {});
+});
+
+// GET confirmation dialog.
+router.get('/confirm', function (req, res) {
+    res.render('templates/confirm', {});
+});
+
+// GET copy dialog.
+router.get('/copy', function (req, res) {
+    res.render('templates/copy', {});
+});
+
+/* GET login page. */
+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;
+
+        db.Account.register(new db.Account(req.body), 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('/');
+});
+
+/* GET home page. */
+router.get('/', function (req, res) {
+    if (req.isAuthenticated())
+        res.redirect('/configuration/clusters');
+    else
+        res.render('index');
+});
+
+///* GET sql page. */
+//router.get('/sql', function(req, res) {
+//    res.render('sql', { user: req.user });
+//});
+//
+///* GET clients page. */
+//router.get('/clients', function(req, res) {
+//    res.render('clients', { user: req.user });
+//});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/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..ce4565d
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/sql.js
@@ -0,0 +1,24 @@
+/*
+ * 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('/', function(req, res) {
+    res.render('sql/sql');
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/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..9f8df2a
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/summary.js
@@ -0,0 +1,108 @@
+/*
+ * 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/xml');
+var generatorJava = require('./generator/java');
+var generatorDocker = require('./generator/docker');
+
+/* GET summary page. */
+router.get('/', function (req, res) {
+    res.render('configuration/summary');
+});
+
+router.post('/generator', function (req, res) {
+    // Get cluster.
+    db.Cluster.findById(req.body._id).deepPopulate('caches caches.queryMetadata caches.storeMetadata').exec(function (err, cluster) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        if (!cluster)
+            return res.sendStatus(404);
+
+        var clientCache = req.body.clientNearConfiguration;
+
+        if (clientCache)
+            return res.send({
+                xmlClient: generatorXml.generateClusterConfiguration(cluster, clientCache),
+                javaClient: generatorJava.generateClusterConfiguration(cluster, req.body.javaClass, clientCache)
+            });
+
+        return res.send({
+            xmlServer: generatorXml.generateClusterConfiguration(cluster),
+            javaSnippetServer: generatorJava.generateClusterConfiguration(cluster, false),
+            javaClassServer: generatorJava.generateClusterConfiguration(cluster, true),
+            docker: generatorDocker.generateClusterConfiguration(cluster, '%OS%')
+        });
+    });
+});
+
+router.post('/download', function (req, res) {
+    // Get cluster.
+    db.Cluster.findById(req.body._id).populate('caches').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' : '') + '-configuration.zip');
+
+        var generatorCommon = require('./generator/common');
+
+        // Send the file to the page output.
+        zip.pipe(res);
+
+        var javaClass = req.body.javaClass;
+
+        if (!clientNearConfiguration) {
+            zip.append(generatorDocker.generateClusterConfiguration(cluster, req.body.os), {name: "Dockerfile"});
+
+            var props = generatorCommon.generateProperties(cluster);
+
+            if (props)
+                zip.append(props, {name: "secret.properties"});
+        }
+
+        zip.append(generatorXml.generateClusterConfiguration(cluster, clientNearConfiguration), {name: cluster.name + ".xml"})
+            .append(generatorJava.generateClusterConfiguration(cluster, javaClass, clientNearConfiguration),
+                {name: javaClass ? 'ConfigurationFactory.java' : cluster.name + '.snipplet.java'})
+            .finalize();
+    });
+});
+
+module.exports = router;
\ 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/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..15d8f40
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/caches.jade
@@ -0,0 +1,74 @@
+//-
+    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
+        hr
+    .docs-body(ng-controller='cachesController')
+        +block-callout('{{screenTip.workflowTitle}}', 'joinTip(screenTip.workflowContent)', '{{screenTip.whatsNextTitle}}', 'joinTip(screenTip.whatsNextContent)')
+        div(ng-hide='caches.length == 0')
+            .padding-dflt
+                lable.labelHeader Caches:
+                table.links(st-table='caches')
+                    tbody
+                        tr(ng-repeat='row in caches track by row._id')
+                            td.col-sm-6(ng-class='{active: row._id == selectedItem._id}')
+                                a(event-focus='click' event-focus-id='defaultFocusId' ng-click='selectItem(row)') {{$index + 1}}) {{row.name}}, {{row.mode | displayValue:modes:'Cache mode not set'}}, {{row.atomicityMode | displayValue:atomicities:'Cache atomicity not set'}}
+        .padding-top-dflt
+            button.btn.btn-primary(event-focus='click' event-focus-id='defaultFocusId' ng-click='createItem()') Add cache
+        hr
+        form.form-horizontal(name='inputForm' ng-if='backupItem' novalidate)
+            div(bs-collapse data-start-collapsed='false')
+                .panel.panel-default
+                    .panel-heading
+                        h3
+                            a(bs-collapse-toggle) General
+                    .panel-collapse(bs-collapse-target)
+                        .panel-body
+                            .settings-row(ng-repeat='field in general')
+                                +form-row(['col-sm-3'], ['col-sm-3'])
+            .panel-group(bs-collapse data-allow-multiple='true')
+                div(bs-collapse data-start-collapsed='true')
+                    .panel-title(ng-show='expanded')
+                        h3
+                            a(bs-collapse-toggle='0' ng-click='expanded = !expanded;') {{expanded ? 'Hide advanced settings...' : 'Show advanced settings...'}}
+                    .panel-collapse(bs-collapse-target)
+                        .span(bs-collapse data-start-collapsed='true' data-allow-multiple='true')
+                            .panel.panel-default(ng-repeat='group in advanced')
+                                .panel-heading
+                                    h3
+                                        a(bs-collapse-toggle) {{group.label}}
+                                        i.tipLabel.fa.fa-question-circle(ng-if='group.tip' bs-tooltip='joinTip(group.tip)' type='button')
+                                        i.tipLabel.fa.fa-question-circle.blank(ng-if='!group.tip')
+                                .panel-collapse(bs-collapse-target)
+                                    .panel-body
+                                        .settings-row(ng-repeat='field in group.fields')
+                                            +form-row
+                    .panel-title
+                        h3
+                            a(bs-collapse-toggle='0' ng-click='expanded = !expanded;') {{expanded ? 'Hide advanced settings...' : 'Show advanced settings...'}}
+            div
+                button.btn.btn-primary(ng-disabled='inputForm.$invalid' ng-click='saveItem()') Save
+                button.btn.btn-primary(ng-show='backupItem._id' ng-disabled='inputForm.$invalid' ng-click='saveItemAs()') Copy
+                button.btn.btn-primary.btn-second(ng-show='backupItem._id' ng-click='removeItem()') Remove

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/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..239a31f
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/clusters.jade
@@ -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.
+
+extends sidebar
+
+append scripts
+    script(src='/clusters-controller.js')
+
+include ../includes/controls
+
+block content
+    .docs-header
+        h1 Create and Configure Ignite Clusters
+        hr
+    .docs-body(ng-controller='clustersController')
+        +block-callout('{{screenTip.workflowTitle}}', 'joinTip(screenTip.workflowContent)', '{{screenTip.whatsNextTitle}}', 'joinTip(screenTip.whatsNextContent)')
+        div(ng-hide='clusters.length == 0')
+            .padding-dflt
+                lable.labelHeader Clusters:
+                table.links(st-table='clusters')
+                    tbody
+                        tr(ng-repeat='row in clusters track by row._id')
+                            td.col-sm-6(ng-class='{active: row._id == selectedItem._id}')
+                                a(event-focus='click' event-focus-id='defaultFocusId' ng-click='selectItem(row)') {{$index + 1}}) {{row.name}}, {{row.discovery.kind | displayValue:discoveries:'Discovery not set'}}
+        .padding-top-dflt
+            button.btn.btn-primary(event-focus='click' event-focus-id='defaultFocusId' ng-click='createItem()') &nbspAdd cluster
+            label(style='margin-left: 10px; margin-right: 10px') Use template:
+            button.btn.btn-default.base-control(ng-init='create.template = templates[0].value' ng-model='create.template' data-template='/select' data-placeholder='Choose cluster template' bs-options='item.value as item.label for item in templates' bs-select)
+            i.tiplabel.fa.fa-question-circle(bs-tooltip data-title='{{joinTip(templateTip)}}' type='button')
+        hr
+        form.form-horizontal(name='inputForm' ng-if='backupItem' novalidate)
+            div(bs-collapse data-start-collapsed='false')
+                .panel.panel-default
+                    .panel-heading
+                        h3
+                            a(bs-collapse-toggle) General
+                    .panel-collapse(bs-collapse-target)
+                        .panel-body
+                            .settings-row(ng-repeat='field in general')
+                                +form-row
+            .panel-group(bs-collapse data-allow-multiple='true')
+                div(bs-collapse data-start-collapsed='true')
+                    .panel-title(ng-show='expanded')
+                        h3
+                            a(bs-collapse-toggle='0' ng-click='expanded = !expanded;') {{expanded ? 'Hide advanced settings...' : 'Show advanced settings...'}}
+                    .panel-collapse(bs-collapse-target)
+                        .span(bs-collapse data-start-collapsed='true' data-allow-multiple='true')
+                            .panel.panel-default(ng-repeat='group in advanced')
+                                .panel-heading
+                                    h3
+                                        a(bs-collapse-toggle) {{group.label}}
+                                        i.tipLabel.fa.fa-question-circle(ng-if='group.tip' bs-tooltip='joinTip(group.tip)' type='button')
+                                        i.tipLabel.fa.fa-question-circle.blank(ng-if='!group.tip')
+                                .panel-collapse(bs-collapse-target)
+                                    .panel-body
+                                        .settings-row(ng-repeat='field in group.fields')
+                                            +form-row
+                    .panel-title
+                        h3
+                            a(bs-collapse-toggle='0' ng-click='expanded = !expanded;') {{expanded ? 'Hide advanced settings...' : 'Show advanced settings...'}}
+            div
+                button.btn.btn-primary(ng-disabled='inputForm.$invalid' ng-click='saveItem()') Save
+                button.btn.btn-primary(ng-show='backupItem._id' ng-disabled='inputForm.$invalid' ng-click='saveItemAs()') Copy
+                button.btn.btn-primary(ng-show='backupItem._id' ng-click='removeItem()') Remove
\ 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/views/configuration/metadata.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/metadata.jade b/modules/control-center-web/src/main/js/views/configuration/metadata.jade
new file mode 100644
index 0000000..e0cc76f
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/metadata.jade
@@ -0,0 +1,121 @@
+//-
+    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='/metadata-controller.js')
+
+include ../includes/controls
+
+block content
+    .docs-header
+        h1 Create and Configure Cache Type Metadata
+        hr
+    .docs-body(ng-controller='metadataController')
+        +block-callout('{{screenTip.workflowTitle}}', 'joinTip(screenTip.workflowContent)', '{{screenTip.whatsNextTitle}}', 'joinTip(screenTip.whatsNextContent)')
+        div(ng-hide='metadatas.length == 0')
+            .padding-dflt
+                lable.labelHeader Types metadata:
+                table.links(st-table='metadatas')
+                    tbody
+                        tr(ng-repeat='row in metadatas track by row._id')
+                            td.col-sm-6(ng-class='{active: row._id == selectedItem._id}')
+                                a(event-focus='click' event-focus-id='defaultFocusId' ng-click='selectItem(row)') {{$index + 1}}) {{row.name}}
+        .padding-top-dflt
+            button.btn.btn-primary(event-focus='click' event-focus-id='defaultFocusId' ng-click='panels.activePanel = [0]; createItem()') &nbspAdd metadata
+            label(style='margin-left: 6px; margin-right: 10px') For:
+            button.btn.btn-default(ng-model='template' data-template='/select' data-placeholder='Choose metadata type' bs-options='item.value as item.label for item in templates' bs-select)
+            i.tiplabel.fa.fa-question-circle(bs-tooltip data-title='{{joinTip(templateTip)}}' type='button')
+        hr
+        .panel-group(bs-collapse ng-model='panels.activePanel' data-allow-multiple='false')
+            .panel.panel-default(ng-show='selectedItem || backupItem')
+                .panel-heading
+                    h3
+                        a(bs-collapse-toggle) Manual
+                .panel-collapse(role='tabpanel' bs-collapse-target)
+                    .panel-body
+                        form.form-horizontal(name='manualForm' ng-if='backupItem' novalidate)
+                            .settings-row(ng-repeat='field in metadataManual')
+                                +form-row
+                            button.btn.btn-primary(ng-disabled='manualForm.$invalid' ng-click='saveItem()') Save
+                            button.btn.btn-primary(ng-show='backupItem._id' ng-disabled='inputForm.$invalid' ng-click='saveItemAs()') Copy
+                            button.btn.btn-primary.btn-second(ng-show='backupItem._id' ng-click='removeItem()') Remove
+            .panel.panel-default
+                .panel-heading
+                    h3
+                        a(bs-collapse-toggle) Load from database
+                .panel-collapse(bs-collapse-target)
+                    .panel-body
+                        form.form-horizontal(name='dbForm' novalidate)
+                            .settings-row(ng-repeat='field in metadataDb')
+                                +form-row
+                        div(ng-hide='data.tables.length == 0')
+                            table.table-bordered.table-condensed.links-edit-small-padding.col-sm-12(st-table='data.tables')
+                                thead
+                                    tr
+                                        th.col-sm-3 Schema/Table
+                                        th Key class
+                                        th Value class
+                                tbody
+                                    tr(ng-repeat='row in data.tables')
+                                        td(colspan='{{row.tableName ? 1 : 3}}')
+                                            div.checkbox(ng-if='!row.tableName')
+                                                label(ng-click='selectSchema($index)')
+                                                    input(type='checkbox' ng-checked='row.use')
+                                                    | {{row.schemaName}}
+                                            div.checkbox(ng-if='row.tableName')
+                                                label(style='padding-left: 30px' ng-click='selectTable($index)')
+                                                    input(type='checkbox' ng-checked = 'row.use')
+                                                    | {{row.tableName}}
+                                        td(ng-if='row.tableName')
+                                            a(ng-show='data.curTableIdx != $index' ng-click='selectTable($index)') {{row.keyClass}}
+                                            input.form-control(type='text' ng-show='data.curTableIdx == $index' ng-model='data.curKeyClass' placeholder='Key class full name')
+                                        td(ng-if='row.tableName')
+                                            a(ng-show='data.curTableIdx != $index' ng-click='selectTable($index)') {{row.valueClass}}
+                                            input.form-control(type='text' ng-show='data.curTableIdx == $index' ng-model='data.curValueClass' placeholder='Value class full name')
+                        //div(ng-hide='data.curTableIdx < 0')
+                        //    table.table-bordered.table-condensed.links-edit-small-padding.col-sm-12(st-table='data.tables[data.curTableIdx].fields')
+                        //        thead
+                        //            tr
+                        //                th(style='width:45px') Use
+                        //                th(style='width:45px') Key
+                        //                th(style='width:45px') Ak
+                        //                th DB Name
+                        //                th DB Type
+                        //                th Java Name
+                        //                th Java Type
+                        //        tbody
+                        //            tr(ng-repeat='row in data.tables[data.curTableIdx].fields')
+                        //                td
+                        //                    +dbcheck('row.use')
+                        //                td
+                        //                    +dbcheck('row.key')
+                        //                td
+                        //                    +dbcheck('row.ak')
+                        //                td
+                        //                    label {{row.databaseName}}
+                        //                td
+                        //                    label {{row.databaseType}}
+                        //                td
+                        //                    a(ng-show='data.curFieldIdx != $index' ng-click='selectField($index)') {{row.javaName}}
+                        //                    input.form-control(type='text' ng-show='data.curFieldIdx == $index' ng-model='data.curJavaName' placeholder='Field Java name')
+                        //                td
+                        //                    a(ng-show='data.curFieldIdx != $index' ng-click='selectField($index)') {{row.javaType}}
+                        //                    input.form-control(type='text' ng-show='data.curFieldIdx == $index' ng-model='data.curJavaType' placeholder='Field Java type')
+                        button.btn.btn-primary(ng-disabled='dbForm.$invalid' ng-click='saveItem()') Save
+                        button.btn.btn-primary.btn-second(ng-show='backupItem._id' ng-click='removeItem()') Remove
+                        button.btn.btn-primary.btn-second(ng-click='reloadMetadata()') Reload
\ 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/views/configuration/sidebar.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/sidebar.jade b/modules/control-center-web/src/main/js/views/configuration/sidebar.jade
new file mode 100644
index 0000000..7289f3e
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/sidebar.jade
@@ -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.
+
+extends ../templates/layout
+
+mixin sidebar-item(ref, num, txt)
+    li
+        a(ng-class='{active: isActive("#{ref}")}' href='#{ref}')
+            span.fa-stack
+                i.fa.fa-circle-thin.fa-stack-2x
+                i.fa.fa-stack-1x #{num}
+            | #{txt}
+
+block container
+    .row
+        .col-sm-2.border-right.section-left.greedy
+            .sidebar-nav(bs-affix)
+                ul.menu(ng-controller='activeLink')
+                    +sidebar-item('/configuration/clusters', 1, 'Clusters')
+                    +sidebar-item('/configuration/metadata', 2, 'Metadata')
+                    +sidebar-item('/configuration/caches', 3, 'Caches')
+                    +sidebar-item('/configuration/summary', 4, 'Summary')
+
+        .col-sm-10.border-left.section-right
+            .docs-content
+                block content
\ 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/views/configuration/summary.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/summary.jade b/modules/control-center-web/src/main/js/views/configuration/summary.jade
new file mode 100644
index 0000000..ba63343
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/summary.jade
@@ -0,0 +1,113 @@
+//-
+    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='/summary-controller.js')
+
+    script(src='//cdn.jsdelivr.net/angularjs/1.3.15/angular-animate.min.js')
+    script(src='//cdn.jsdelivr.net/angularjs/1.3.15/angular-sanitize.min.js')
+
+    script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/theme-chrome.js')
+    script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-xml.js')
+    script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-java.js')
+    script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-dockerfile.js')
+
+append css
+
+include ../includes/controls
+
+mixin hard-link(ref, txt)
+    a(style='color:#ec1c24' href=ref target='_blank') #{txt}
+
+block content
+    .docs-header
+        h1 Configurations Summary
+        hr
+    .docs-body(ng-controller='summaryController')
+        +block-callout('{{screenTip.workflowTitle}}', 'joinTip(screenTip.workflowContent)', '{{screenTip.whatsNextTitle}}', 'joinTip(screenTip.whatsNextContent)')
+        .padding-dflt(ng-if='clusters.length == 0')
+            | No cluster configured. You can&nbsp;
+            a(href='clusters') configure
+            | &nbsp;it.
+        .padding-dflt(ng-if='clusters.length > 0')
+            lable.labelHeader Clusters:
+            table.links(st-table='clusters')
+                tbody
+                    tr(ng-repeat='row in clusters track by row._id')
+                        td.col-sm-6(ng-class='{active: row._id == selectedItem._id}')
+                            a(ng-click='selectItem(row)') {{$index + 1}}) {{row.name}}
+        div(ng-show='selectedItem' role='tab' method='post' action='summary/download')
+            .padding-dflt(bs-collapse data-start-collapsed='false')
+                .panel.panel-default
+                    form.panel-heading(role='tab' method='post' action='summary/download')
+                        input(type='hidden' name='_id' value='{{selectedItem._id}}')
+                        input(type='hidden' name='os' value='{{os}}')
+                        input(type='hidden' name='javaClass' value='{{javaClassServer}}')
+                        h3
+                            a(bs-collapse-toggle) Server
+                            button.btn.btn-primary.pull-right(type='submit' style='margin-top: -5px') Download
+                    .panel-collapse(role='tabpanel' bs-collapse-target)
+                        div(ng-show='selectedItem' bs-tabs style='margin-top: 0.65em')
+                            div(title='<img src="/images/xml.png" width="16px" height="16px"/> XML' bs-pane)
+                                div(ui-ace='{ onLoad: aceInit, mode: "xml" }' ng-model='xmlServer')
+                            div(title='<img src="/images/java.png" width="16px" height="16px"/> Java' bs-pane)
+                                .details-row
+                                    .col-sm-1
+                                        label Generate:
+                                    .col-sm-3
+                                        button.form-control(type='button' ng-model='configServer.javaClassServer'  bs-select data-placeholder='{{detail.placeholder}}' bs-options='item.value as item.label for item in javaClassItems' data-sort='false')
+                                div(ui-ace='{ onLoad: aceInit, mode: "java" }' ng-model='javaServer')
+                            div(title='<img src="/images/docker.png" width="16px" height="16px"/> Dockerfile' bs-pane)
+                                .details-row
+                                    p
+                                        +hard-link('https://docs.docker.com/reference/builder', 'Docker')
+                                        | &nbsp;file is a text file with instructions to create Docker image.<br/>
+                                        | To build image you have to store following Docker file with your Ignite XML configuration to the same directory.<br>
+                                        | Also you could use predefined&nbsp;
+                                        +hard-link('https://ignite.incubator.apache.org/download.html#docker', 'Apache Ignite docker image')
+                                        | . For more information about using Ignite with Docker please read&nbsp;
+                                        +hard-link('http://apacheignite.readme.io/docs/docker-deployment', 'documentation')
+                                        |.
+                                    .col-sm-2
+                                        label(for='os') Operation System:
+                                    .col-sm-4
+                                        input#os.form-control(type='text' ng-model='configServer.os' placeholder='debian:8' data-min-length='0' data-html='1' data-auto-select='true' data-animation='am-flip-x' bs-typeahead bs-options='os for os in oss')
+                                div(ui-ace='{ onLoad: aceInit, mode: "dockerfile" }' ng-model='dockerServer')
+            .padding-dflt(bs-collapse data-start-collapsed='false')
+                .panel.panel-default
+                    form.panel-heading(role='tab' method='post' action='summary/download')
+                        input(type='hidden' name='_id' value='{{selectedItem._id}}')
+                        input(type='hidden' name='javaClass' value='{{javaClassClient}}')
+                        input(type='hidden' name='clientNearConfiguration' value='{{backupItem}}')
+                        h3
+                            a(bs-collapse-toggle) Client
+                            button.btn.btn-primary.pull-right(type='submit' style='margin-top: -5px') Download
+                    .panel-collapse(role='tabpanel' bs-collapse-target)
+                        div(ng-show='selectedItem')
+                            .details-row(ng-repeat='field in clientFields')
+                                +form-row-custom(['col-sm-3'], ['col-sm-3'])
+                            div(bs-tabs style='margin-top: 0.65em')
+                                div(title='<img src="/images/xml.png" width="16px" height="16px"/> XML' bs-pane)
+                                    div(ui-ace='{ onLoad: aceInit, mode: "xml" }' ng-model='xmlClient')
+                                div(title='<img src="/images/java.png" width="16px" height="16px"/> Java' bs-pane)
+                                    .details-row
+                                        .col-sm-1
+                                            label Generate:
+                                        .col-sm-4
+                                            button.form-control(type='button' ng-model='backupItem.javaClassClient' bs-select data-placeholder='{{detail.placeholder}}' bs-options='item.value as item.label for item in javaClassItems' data-sort='false')
+                                    div(ui-ace='{ onLoad: aceInit, mode: "java" }' ng-model='javaClient')

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/96522874/modules/control-center-web/src/main/js/views/error.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/error.jade b/modules/control-center-web/src/main/js/views/error.jade
new file mode 100644
index 0000000..b458fb7
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/error.jade
@@ -0,0 +1,22 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+extends templates/layout
+
+block container
+  h1= message
+  h2= error.status
+  pre #{error.stack}