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:02 UTC

[01/18] ignite git commit: ignite-843 Agent initial commit

Repository: ignite
Updated Branches:
  refs/heads/ignite-843-rc1 [created] 433160528


http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java
new file mode 100644
index 0000000..d920a1b
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java
@@ -0,0 +1,414 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.agent.AgentConfiguration;
+import org.apache.ignite.agent.testdrive.model.Car;
+import org.apache.ignite.agent.testdrive.model.CarKey;
+import org.apache.ignite.agent.testdrive.model.Country;
+import org.apache.ignite.agent.testdrive.model.CountryKey;
+import org.apache.ignite.agent.testdrive.model.Department;
+import org.apache.ignite.agent.testdrive.model.DepartmentKey;
+import org.apache.ignite.agent.testdrive.model.Employee;
+import org.apache.ignite.agent.testdrive.model.EmployeeKey;
+import org.apache.ignite.agent.testdrive.model.Parking;
+import org.apache.ignite.agent.testdrive.model.ParkingKey;
+import org.apache.ignite.cache.CacheTypeMetadata;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteNodeAttributes;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.logger.NullLogger;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+
+/**
+ * Test drive for SQL.
+ *
+ * Cache will be created and populated with data to query.
+ */
+public class AgentSqlTestDrive {
+    /** */
+    private static final Logger log = Logger.getLogger(AgentMetadataTestDrive.class.getName());
+
+    /** */
+    private static final AtomicBoolean initLatch = new AtomicBoolean();
+
+    /** */
+    private static final String EMPLOYEE_CACHE_NAME = "test-drive-employee";
+
+    /** */
+    private static final String CAR_CACHE_NAME = "test-drive-car";
+
+    /** */
+    private static final Random rnd = new Random();
+
+    /** Countries count. */
+    private static final int CNTR_CNT = 10;
+
+    /** Departments count */
+    private static final int DEP_CNT = 100;
+
+    /** Employees count. */
+    private static final int EMPL_CNT = 1000;
+
+    /** Countries count. */
+    private static final int CAR_CNT = 100;
+
+    /** Departments count */
+    private static final int PARK_CNT = 10;
+
+    /**
+     * Configure cacheEmployee.
+     *
+     * @param name Cache name.
+     */
+    private static <K, V> CacheConfiguration<K, V> cacheEmployee(String name) {
+        CacheConfiguration<K, V> ccfg = new CacheConfiguration<>(name);
+
+        // Configure cacheEmployee types.
+        Collection<CacheTypeMetadata> meta = new ArrayList<>();
+
+        // COUNTRY.
+        CacheTypeMetadata type = new CacheTypeMetadata();
+
+        meta.add(type);
+
+        type.setKeyType(CountryKey.class.getName());
+        type.setValueType(Country.class.getName());
+
+        // Query fields for COUNTRY.
+        Map<String, Class<?>> qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("id", int.class);
+        qryFlds.put("countryName", String.class);
+
+        type.setQueryFields(qryFlds);
+
+        // Ascending fields for COUNTRY.
+        Map<String, Class<?>> ascFlds = new LinkedHashMap<>();
+
+        ascFlds.put("id", int.class);
+
+        type.setAscendingFields(ascFlds);
+
+        ccfg.setTypeMetadata(meta);
+
+        // DEPARTMENT.
+        type = new CacheTypeMetadata();
+
+        meta.add(type);
+
+        type.setKeyType(DepartmentKey.class.getName());
+        type.setValueType(Department.class.getName());
+
+        // Query fields for DEPARTMENT.
+        qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("departmentId", int.class);
+        qryFlds.put("departmentName", String.class);
+        qryFlds.put("countryId", Integer.class);
+        qryFlds.put("managerId", Integer.class);
+
+        type.setQueryFields(qryFlds);
+
+        // Ascending fields for DEPARTMENT.
+        ascFlds = new LinkedHashMap<>();
+
+        ascFlds.put("departmentId", int.class);
+
+        type.setAscendingFields(ascFlds);
+
+        ccfg.setTypeMetadata(meta);
+
+        // EMPLOYEE.
+        type = new CacheTypeMetadata();
+
+        meta.add(type);
+
+        type.setKeyType(EmployeeKey.class.getName());
+        type.setValueType(Employee.class.getName());
+
+        // Query fields for EMPLOYEE.
+        qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("employeeId", int.class);
+        qryFlds.put("firstName", String.class);
+        qryFlds.put("lastName", String.class);
+        qryFlds.put("email", String.class);
+        qryFlds.put("phoneNumber", String.class);
+        qryFlds.put("hireDate", java.sql.Date.class);
+        qryFlds.put("job", String.class);
+        qryFlds.put("salary", Double.class);
+        qryFlds.put("managerId", Integer.class);
+        qryFlds.put("departmentId", Integer.class);
+
+        type.setQueryFields(qryFlds);
+
+        // Ascending fields for EMPLOYEE.
+        ascFlds = new LinkedHashMap<>();
+
+        ascFlds.put("employeeId", int.class);
+
+        type.setAscendingFields(ascFlds);
+
+        // Desc fields for EMPLOYEE.
+        Map<String, Class<?>> descFlds = new LinkedHashMap<>();
+
+        descFlds.put("salary", Double.class);
+
+        type.setDescendingFields(descFlds);
+
+        // Groups for EMPLOYEE.
+        Map<String, LinkedHashMap<String, IgniteBiTuple<Class<?>, Boolean>>> grps = new LinkedHashMap<>();
+
+        LinkedHashMap<String, IgniteBiTuple<Class<?>, Boolean>> grpItems = new LinkedHashMap<>();
+
+        grpItems.put("firstName", new IgniteBiTuple<Class<?>, Boolean>(String.class, false));
+        grpItems.put("lastName", new IgniteBiTuple<Class<?>, Boolean>(String.class, true));
+
+        grps.put("EMP_NAMES", grpItems);
+
+        type.setGroups(grps);
+
+        ccfg.setTypeMetadata(meta);
+
+        return ccfg;
+    }
+
+    /**
+     * Configure cacheEmployee.
+     *
+     * @param name Cache name.
+     */
+    private static <K, V> CacheConfiguration<K, V> cacheCar(String name) {
+        CacheConfiguration<K, V> ccfg = new CacheConfiguration<>(name);
+
+        // Configure cacheEmployee types.
+        Collection<CacheTypeMetadata> meta = new ArrayList<>();
+
+        // CAR.
+        CacheTypeMetadata type = new CacheTypeMetadata();
+
+        meta.add(type);
+
+        type.setKeyType(CarKey.class.getName());
+        type.setValueType(Car.class.getName());
+
+        // Query fields for CAR.
+        Map<String, Class<?>> qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("carId", int.class);
+        qryFlds.put("parkingId", int.class);
+        qryFlds.put("carName", String.class);
+
+        type.setQueryFields(qryFlds);
+
+        // Ascending fields for CAR.
+        Map<String, Class<?>> ascFlds = new LinkedHashMap<>();
+
+        ascFlds.put("carId", int.class);
+
+        type.setAscendingFields(ascFlds);
+
+        ccfg.setTypeMetadata(meta);
+
+        // PARKING.
+        type = new CacheTypeMetadata();
+
+        meta.add(type);
+
+        type.setKeyType(ParkingKey.class.getName());
+        type.setValueType(Parking.class.getName());
+
+        // Query fields for PARKING.
+        qryFlds = new LinkedHashMap<>();
+
+        qryFlds.put("parkingId", int.class);
+        qryFlds.put("parkingName", String.class);
+
+        type.setQueryFields(qryFlds);
+
+        // Ascending fields for PARKING.
+        ascFlds = new LinkedHashMap<>();
+
+        ascFlds.put("parkingId", int.class);
+
+        type.setAscendingFields(ascFlds);
+
+        ccfg.setTypeMetadata(meta);
+
+        return ccfg;
+    }
+
+    /**
+     * @param val Value to round.
+     * @param places Numbers after point.
+     * @return Rounded value;
+     */
+    private static double round(double val, int places) {
+        if (places < 0)
+            throw new IllegalArgumentException();
+
+        long factor = (long) Math.pow(10, places);
+
+        val *= factor;
+
+        long tmp = Math.round(val);
+
+        return (double) tmp / factor;
+    }
+
+    /**
+     * @param ignite Ignite.
+     * @param name Cache name.
+     */
+    private static void populateCacheEmployee(Ignite ignite, String name) {
+        log.log(Level.FINE, "TEST-DRIVE-SQL: Start population cache: '" + name + "' with data...");
+
+        IgniteCache<CountryKey, Country> cacheCountry = ignite.cache(name);
+
+        for (int i = 0; i < CNTR_CNT; i++)
+            cacheCountry.put(new CountryKey(i), new Country(i, "State " + (i + 1)));
+
+        IgniteCache<DepartmentKey, Department> cacheDepartment = ignite.cache(name);
+
+        for (int i = 0; i < DEP_CNT; i++) {
+            Integer mgrId = (i == 0 || rnd.nextBoolean()) ? null : rnd.nextInt(i);
+
+            cacheDepartment.put(new DepartmentKey(i),
+                new Department(i, "Department " + (i + 1), rnd.nextInt(CNTR_CNT), mgrId));
+        }
+
+        IgniteCache<EmployeeKey, Employee> cacheEmployee = ignite.cache(name);
+
+        long off = java.sql.Date.valueOf("2007-01-01").getTime();
+
+        long end = java.sql.Date.valueOf("2016-01-01").getTime();
+
+        long diff = end - off + 1;
+
+        for (int i = 0; i < EMPL_CNT; i++) {
+            Integer mgrId = (i == 0 || rnd.nextBoolean()) ? null : rnd.nextInt(i);
+
+            double r = rnd.nextDouble();
+
+            cacheEmployee.put(new EmployeeKey(i),
+                new Employee(i, "first name " + (i + 1), "last name " + (i + 1), "email " + (i + 1),
+                    "phone number " + (i + 1), new java.sql.Date(off + (long)(r * diff)), "job " + (i + 1),
+                    round(r * 5000, 2) , mgrId, rnd.nextInt(DEP_CNT)));
+        }
+
+        log.log(Level.FINE, "TEST-DRIVE-SQL: Finished population cache: '" + name + "' with data.");
+    }
+
+    /**
+     * @param ignite Ignite.
+     * @param name Cache name.
+     */
+    private static void populateCacheCar(Ignite ignite, String name) {
+        log.log(Level.FINE, "TEST-DRIVE-SQL: Start population cache: '" + name + "' with data...");
+
+        IgniteCache<ParkingKey, Parking> cacheParking = ignite.cache(name);
+
+        for (int i = 0; i < PARK_CNT; i++)
+            cacheParking.put(new ParkingKey(i), new Parking(i, "Parking " + (i + 1)));
+
+        IgniteCache<CarKey, Car> cacheDepartment = ignite.cache(name);
+
+        for (int i = 0; i < CAR_CNT; i++)
+            cacheDepartment.put(new CarKey(i), new Car(i, rnd.nextInt(PARK_CNT), "Car " + (i + 1)));
+
+
+        log.log(Level.FINE, "TEST-DRIVE-SQL: Finished population cache: '" + name + "' with data.");
+    }
+
+    /**
+     * Start ignite node with cacheEmployee and populate it with data.
+     */
+    public static void testDrive(AgentConfiguration acfg) {
+        if (initLatch.compareAndSet(false, true)) {
+            log.log(Level.INFO, "TEST-DRIVE-SQL: Starting embedded node for sql test-drive...");
+
+            try {
+                IgniteConfiguration cfg = new IgniteConfiguration();
+
+                cfg.setLocalHost("127.0.0.1");
+
+                cfg.setMetricsLogFrequency(0);
+
+                cfg.setGridLogger(new NullLogger());
+
+                // Configure discovery SPI.
+                TcpDiscoverySpi discoSpi = new TcpDiscoverySpi();
+
+                TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+                ipFinder.setAddresses(Collections.singleton("127.0.0.1:47500..47501"));
+
+                discoSpi.setIpFinder(ipFinder);
+
+                cfg.setDiscoverySpi(discoSpi);
+
+                cfg.setCacheConfiguration(cacheEmployee(EMPLOYEE_CACHE_NAME), cacheCar(CAR_CACHE_NAME));
+
+                log.log(Level.FINE, "TEST-DRIVE-SQL: Start embedded node with indexed enabled caches...");
+
+                IgniteEx ignite = (IgniteEx)Ignition.start(cfg);
+
+                String host = ((Collection<String>)
+                    ignite.localNode().attribute(IgniteNodeAttributes.ATTR_REST_JETTY_ADDRS)).iterator().next();
+
+                Integer port = ignite.localNode().attribute(IgniteNodeAttributes.ATTR_REST_JETTY_PORT);
+
+                if (F.isEmpty(host) || port == null) {
+                    log.log(Level.SEVERE, "TEST-DRIVE-SQL: Failed to start embedded node with rest!");
+
+                    return;
+                }
+
+                acfg.nodeUri(String.format("http://%s:%d", "0.0.0.0".equals(host) ? "127.0.0.1" : host, port));
+
+                log.log(Level.INFO, "TEST-DRIVE-SQL: Embedded node for sql test-drive successfully started");
+
+                populateCacheEmployee(ignite, EMPLOYEE_CACHE_NAME);
+
+                populateCacheCar(ignite, CAR_CACHE_NAME);
+            }
+            catch (Exception e) {
+                log.log(Level.SEVERE, "TEST-DRIVE-SQL: Failed to start embedded node for sql test-drive!", e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java
new file mode 100644
index 0000000..04e3ee6
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java
@@ -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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * Car definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class Car implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for carId. */
+    private int carId;
+
+    /** Value for parkingId. */
+    private int parkingId;
+
+    /** Value for carName. */
+    private String carName;
+
+    /**
+     * Empty constructor.
+     */
+    public Car() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Car(
+        int carId,
+        int parkingId,
+        String carName
+    ) {
+        this.carId = carId;
+        this.parkingId = parkingId;
+        this.carName = carName;
+    }
+
+    /**
+     * Gets carId.
+     *
+     * @return Value for carId.
+     */
+    public int getCarId() {
+        return carId;
+    }
+
+    /**
+     * Sets carId.
+     *
+     * @param carId New value for carId.
+     */
+    public void setCarId(int carId) {
+        this.carId = carId;
+    }
+
+    /**
+     * Gets parkingId.
+     *
+     * @return Value for parkingId.
+     */
+    public int getParkingId() {
+        return parkingId;
+    }
+
+    /**
+     * Sets parkingId.
+     *
+     * @param parkingId New value for parkingId.
+     */
+    public void setParkingId(int parkingId) {
+        this.parkingId = parkingId;
+    }
+
+    /**
+     * Gets carName.
+     *
+     * @return Value for carName.
+     */
+    public String getCarName() {
+        return carName;
+    }
+
+    /**
+     * Sets carName.
+     *
+     * @param carName New value for carName.
+     */
+    public void setCarName(String carName) {
+        this.carName = carName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof Car))
+            return false;
+
+        Car that = (Car)o;
+
+        if (carId != that.carId)
+            return false;
+
+        if (parkingId != that.parkingId)
+            return false;
+
+        if (carName != null ? !carName.equals(that.carName) : that.carName != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = carId;
+
+        res = 31 * res + parkingId;
+
+        res = 31 * res + (carName != null ? carName.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Car [carId=" + carId +
+            ", parkingId=" + parkingId +
+            ", carName=" + carName +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java
new file mode 100644
index 0000000..063e6c2
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java
@@ -0,0 +1,99 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * CarKey definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class CarKey implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for carId. */
+    private int carId;
+
+    /**
+     * Empty constructor.
+     */
+    public CarKey() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public CarKey(
+        int carId
+    ) {
+        this.carId = carId;
+    }
+
+    /**
+     * Gets carId.
+     *
+     * @return Value for carId.
+     */
+    public int getCarId() {
+        return carId;
+    }
+
+    /**
+     * Sets carId.
+     *
+     * @param carId New value for carId.
+     */
+    public void setCarId(int carId) {
+        this.carId = carId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof CarKey))
+            return false;
+
+        CarKey that = (CarKey)o;
+
+        if (carId != that.carId)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = carId;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "CarKey [carId=" + carId +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java
new file mode 100644
index 0000000..edead2f
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java
@@ -0,0 +1,128 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * Country definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class Country implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for id. */
+    private int id;
+
+    /** Value for countryName. */
+    private String countryName;
+
+    /**
+     * Empty constructor.
+     */
+    public Country() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Country(
+        int id,
+        String countryName
+    ) {
+        this.id = id;
+        this.countryName = countryName;
+    }
+
+    /**
+     * Gets id.
+     *
+     * @return Value for id.
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Sets id.
+     *
+     * @param id New value for id.
+     */
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets countryName.
+     *
+     * @return Value for countryName.
+     */
+    public String getCountryName() {
+        return countryName;
+    }
+
+    /**
+     * Sets countryName.
+     *
+     * @param countryName New value for countryName.
+     */
+    public void setCountryName(String countryName) {
+        this.countryName = countryName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof Country))
+            return false;
+
+        Country that = (Country)o;
+
+        if (id != that.id)
+            return false;
+
+        if (countryName != null ? !countryName.equals(that.countryName) : that.countryName != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = id;
+
+        res = 31 * res + (countryName != null ? countryName.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Country [id=" + id +
+            ", countryName=" + countryName +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java
new file mode 100644
index 0000000..f3f3761
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java
@@ -0,0 +1,99 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * CountryKey definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class CountryKey implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for id. */
+    private int id;
+
+    /**
+     * Empty constructor.
+     */
+    public CountryKey() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public CountryKey(
+        int id
+    ) {
+        this.id = id;
+    }
+
+    /**
+     * Gets id.
+     *
+     * @return Value for id.
+     */
+    public int getId() {
+        return id;
+    }
+
+    /**
+     * Sets id.
+     *
+     * @param id New value for id.
+     */
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof CountryKey))
+            return false;
+
+        CountryKey that = (CountryKey)o;
+
+        if (id != that.id)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = id;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "CountryKey [id=" + id +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java
new file mode 100644
index 0000000..fb87d72
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java
@@ -0,0 +1,186 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * Department definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class Department implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for departmentId. */
+    private int departmentId;
+
+    /** Value for departmentName. */
+    private String departmentName;
+
+    /** Value for countryId. */
+    private Integer countryId;
+
+    /** Value for managerId. */
+    private Integer managerId;
+
+    /**
+     * Empty constructor.
+     */
+    public Department() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Department(
+        int departmentId,
+        String departmentName,
+        Integer countryId,
+        Integer managerId
+    ) {
+        this.departmentId = departmentId;
+        this.departmentName = departmentName;
+        this.countryId = countryId;
+        this.managerId = managerId;
+    }
+
+    /**
+     * Gets departmentId.
+     *
+     * @return Value for departmentId.
+     */
+    public int getDepartmentId() {
+        return departmentId;
+    }
+
+    /**
+     * Sets departmentId.
+     *
+     * @param departmentId New value for departmentId.
+     */
+    public void setDepartmentId(int departmentId) {
+        this.departmentId = departmentId;
+    }
+
+    /**
+     * Gets departmentName.
+     *
+     * @return Value for departmentName.
+     */
+    public String getDepartmentName() {
+        return departmentName;
+    }
+
+    /**
+     * Sets departmentName.
+     *
+     * @param departmentName New value for departmentName.
+     */
+    public void setDepartmentName(String departmentName) {
+        this.departmentName = departmentName;
+    }
+
+    /**
+     * Gets countryId.
+     *
+     * @return Value for countryId.
+     */
+    public Integer getCountryId() {
+        return countryId;
+    }
+
+    /**
+     * Sets countryId.
+     *
+     * @param countryId New value for countryId.
+     */
+    public void setCountryId(Integer countryId) {
+        this.countryId = countryId;
+    }
+
+    /**
+     * Gets managerId.
+     *
+     * @return Value for managerId.
+     */
+    public Integer getManagerId() {
+        return managerId;
+    }
+
+    /**
+     * Sets managerId.
+     *
+     * @param managerId New value for managerId.
+     */
+    public void setManagerId(Integer managerId) {
+        this.managerId = managerId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof Department))
+            return false;
+
+        Department that = (Department)o;
+
+        if (departmentId != that.departmentId)
+            return false;
+
+        if (departmentName != null ? !departmentName.equals(that.departmentName) : that.departmentName != null)
+            return false;
+
+        if (countryId != null ? !countryId.equals(that.countryId) : that.countryId != null)
+            return false;
+
+        if (managerId != null ? !managerId.equals(that.managerId) : that.managerId != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = departmentId;
+
+        res = 31 * res + (departmentName != null ? departmentName.hashCode() : 0);
+
+        res = 31 * res + (countryId != null ? countryId.hashCode() : 0);
+
+        res = 31 * res + (managerId != null ? managerId.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Department [departmentId=" + departmentId +
+            ", departmentName=" + departmentName +
+            ", countryId=" + countryId +
+            ", managerId=" + managerId +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java
new file mode 100644
index 0000000..a2d33a4
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java
@@ -0,0 +1,99 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * DepartmentKey definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class DepartmentKey implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for departmentId. */
+    private int departmentId;
+
+    /**
+     * Empty constructor.
+     */
+    public DepartmentKey() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public DepartmentKey(
+        int departmentId
+    ) {
+        this.departmentId = departmentId;
+    }
+
+    /**
+     * Gets departmentId.
+     *
+     * @return Value for departmentId.
+     */
+    public int getDepartmentId() {
+        return departmentId;
+    }
+
+    /**
+     * Sets departmentId.
+     *
+     * @param departmentId New value for departmentId.
+     */
+    public void setDepartmentId(int departmentId) {
+        this.departmentId = departmentId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof DepartmentKey))
+            return false;
+
+        DepartmentKey that = (DepartmentKey)o;
+
+        if (departmentId != that.departmentId)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = departmentId;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "DepartmentKey [departmentId=" + departmentId +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java
new file mode 100644
index 0000000..39fc2b3
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java
@@ -0,0 +1,360 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * Employee definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class Employee implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for employeeId. */
+    private int employeeId;
+
+    /** Value for firstName. */
+    private String firstName;
+
+    /** Value for lastName. */
+    private String lastName;
+
+    /** Value for email. */
+    private String email;
+
+    /** Value for phoneNumber. */
+    private String phoneNumber;
+
+    /** Value for hireDate. */
+    private java.sql.Date hireDate;
+
+    /** Value for job. */
+    private String job;
+
+    /** Value for salary. */
+    private Double salary;
+
+    /** Value for managerId. */
+    private Integer managerId;
+
+    /** Value for departmentId. */
+    private Integer departmentId;
+
+    /**
+     * Empty constructor.
+     */
+    public Employee() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Employee(
+        int employeeId,
+        String firstName,
+        String lastName,
+        String email,
+        String phoneNumber,
+        java.sql.Date hireDate,
+        String job,
+        Double salary,
+        Integer managerId,
+        Integer departmentId
+    ) {
+        this.employeeId = employeeId;
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.email = email;
+        this.phoneNumber = phoneNumber;
+        this.hireDate = hireDate;
+        this.job = job;
+        this.salary = salary;
+        this.managerId = managerId;
+        this.departmentId = departmentId;
+    }
+
+    /**
+     * Gets employeeId.
+     *
+     * @return Value for employeeId.
+     */
+    public int getEmployeeId() {
+        return employeeId;
+    }
+
+    /**
+     * Sets employeeId.
+     *
+     * @param employeeId New value for employeeId.
+     */
+    public void setEmployeeId(int employeeId) {
+        this.employeeId = employeeId;
+    }
+
+    /**
+     * Gets firstName.
+     *
+     * @return Value for firstName.
+     */
+    public String getFirstName() {
+        return firstName;
+    }
+
+    /**
+     * Sets firstName.
+     *
+     * @param firstName New value for firstName.
+     */
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    /**
+     * Gets lastName.
+     *
+     * @return Value for lastName.
+     */
+    public String getLastName() {
+        return lastName;
+    }
+
+    /**
+     * Sets lastName.
+     *
+     * @param lastName New value for lastName.
+     */
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    /**
+     * Gets email.
+     *
+     * @return Value for email.
+     */
+    public String getEmail() {
+        return email;
+    }
+
+    /**
+     * Sets email.
+     *
+     * @param email New value for email.
+     */
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    /**
+     * Gets phoneNumber.
+     *
+     * @return Value for phoneNumber.
+     */
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    /**
+     * Sets phoneNumber.
+     *
+     * @param phoneNumber New value for phoneNumber.
+     */
+    public void setPhoneNumber(String phoneNumber) {
+        this.phoneNumber = phoneNumber;
+    }
+
+    /**
+     * Gets hireDate.
+     *
+     * @return Value for hireDate.
+     */
+    public java.sql.Date getHireDate() {
+        return hireDate;
+    }
+
+    /**
+     * Sets hireDate.
+     *
+     * @param hireDate New value for hireDate.
+     */
+    public void setHireDate(java.sql.Date hireDate) {
+        this.hireDate = hireDate;
+    }
+
+    /**
+     * Gets job.
+     *
+     * @return Value for job.
+     */
+    public String getJob() {
+        return job;
+    }
+
+    /**
+     * Sets job.
+     *
+     * @param job New value for job.
+     */
+    public void setJob(String job) {
+        this.job = job;
+    }
+
+    /**
+     * Gets salary.
+     *
+     * @return Value for salary.
+     */
+    public Double getSalary() {
+        return salary;
+    }
+
+    /**
+     * Sets salary.
+     *
+     * @param salary New value for salary.
+     */
+    public void setSalary(Double salary) {
+        this.salary = salary;
+    }
+
+    /**
+     * Gets managerId.
+     *
+     * @return Value for managerId.
+     */
+    public Integer getManagerId() {
+        return managerId;
+    }
+
+    /**
+     * Sets managerId.
+     *
+     * @param managerId New value for managerId.
+     */
+    public void setManagerId(Integer managerId) {
+        this.managerId = managerId;
+    }
+
+    /**
+     * Gets departmentId.
+     *
+     * @return Value for departmentId.
+     */
+    public Integer getDepartmentId() {
+        return departmentId;
+    }
+
+    /**
+     * Sets departmentId.
+     *
+     * @param departmentId New value for departmentId.
+     */
+    public void setDepartmentId(Integer departmentId) {
+        this.departmentId = departmentId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof Employee))
+            return false;
+
+        Employee that = (Employee)o;
+
+        if (employeeId != that.employeeId)
+            return false;
+
+        if (firstName != null ? !firstName.equals(that.firstName) : that.firstName != null)
+            return false;
+
+        if (lastName != null ? !lastName.equals(that.lastName) : that.lastName != null)
+            return false;
+
+        if (email != null ? !email.equals(that.email) : that.email != null)
+            return false;
+
+        if (phoneNumber != null ? !phoneNumber.equals(that.phoneNumber) : that.phoneNumber != null)
+            return false;
+
+        if (hireDate != null ? !hireDate.equals(that.hireDate) : that.hireDate != null)
+            return false;
+
+        if (job != null ? !job.equals(that.job) : that.job != null)
+            return false;
+
+        if (salary != null ? !salary.equals(that.salary) : that.salary != null)
+            return false;
+
+        if (managerId != null ? !managerId.equals(that.managerId) : that.managerId != null)
+            return false;
+
+        if (departmentId != null ? !departmentId.equals(that.departmentId) : that.departmentId != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = employeeId;
+
+        res = 31 * res + (firstName != null ? firstName.hashCode() : 0);
+
+        res = 31 * res + (lastName != null ? lastName.hashCode() : 0);
+
+        res = 31 * res + (email != null ? email.hashCode() : 0);
+
+        res = 31 * res + (phoneNumber != null ? phoneNumber.hashCode() : 0);
+
+        res = 31 * res + (hireDate != null ? hireDate.hashCode() : 0);
+
+        res = 31 * res + (job != null ? job.hashCode() : 0);
+
+        res = 31 * res + (salary != null ? salary.hashCode() : 0);
+
+        res = 31 * res + (managerId != null ? managerId.hashCode() : 0);
+
+        res = 31 * res + (departmentId != null ? departmentId.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Employee [employeeId=" + employeeId +
+            ", firstName=" + firstName +
+            ", lastName=" + lastName +
+            ", email=" + email +
+            ", phoneNumber=" + phoneNumber +
+            ", hireDate=" + hireDate +
+            ", job=" + job +
+            ", salary=" + salary +
+            ", managerId=" + managerId +
+            ", departmentId=" + departmentId +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java
new file mode 100644
index 0000000..160606a
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java
@@ -0,0 +1,99 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * EmployeeKey definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class EmployeeKey implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for employeeId. */
+    private int employeeId;
+
+    /**
+     * Empty constructor.
+     */
+    public EmployeeKey() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public EmployeeKey(
+        int employeeId
+    ) {
+        this.employeeId = employeeId;
+    }
+
+    /**
+     * Gets employeeId.
+     *
+     * @return Value for employeeId.
+     */
+    public int getEmployeeId() {
+        return employeeId;
+    }
+
+    /**
+     * Sets employeeId.
+     *
+     * @param employeeId New value for employeeId.
+     */
+    public void setEmployeeId(int employeeId) {
+        this.employeeId = employeeId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof EmployeeKey))
+            return false;
+
+        EmployeeKey that = (EmployeeKey)o;
+
+        if (employeeId != that.employeeId)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = employeeId;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "EmployeeKey [employeeId=" + employeeId +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java
new file mode 100644
index 0000000..145cb6d
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java
@@ -0,0 +1,128 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * Parking definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class Parking implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for parkingId. */
+    private int parkingId;
+
+    /** Value for parkingName. */
+    private String parkingName;
+
+    /**
+     * Empty constructor.
+     */
+    public Parking() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public Parking(
+        int parkingId,
+        String parkingName
+    ) {
+        this.parkingId = parkingId;
+        this.parkingName = parkingName;
+    }
+
+    /**
+     * Gets parkingId.
+     *
+     * @return Value for parkingId.
+     */
+    public int getParkingId() {
+        return parkingId;
+    }
+
+    /**
+     * Sets parkingId.
+     *
+     * @param parkingId New value for parkingId.
+     */
+    public void setParkingId(int parkingId) {
+        this.parkingId = parkingId;
+    }
+
+    /**
+     * Gets parkingName.
+     *
+     * @return Value for parkingName.
+     */
+    public String getParkingName() {
+        return parkingName;
+    }
+
+    /**
+     * Sets parkingName.
+     *
+     * @param parkingName New value for parkingName.
+     */
+    public void setParkingName(String parkingName) {
+        this.parkingName = parkingName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof Parking))
+            return false;
+
+        Parking that = (Parking)o;
+
+        if (parkingId != that.parkingId)
+            return false;
+
+        if (parkingName != null ? !parkingName.equals(that.parkingName) : that.parkingName != null)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = parkingId;
+
+        res = 31 * res + (parkingName != null ? parkingName.hashCode() : 0);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Parking [parkingId=" + parkingId +
+            ", parkingName=" + parkingName +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java
new file mode 100644
index 0000000..5875cd1
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java
@@ -0,0 +1,99 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive.model;
+
+import java.io.Serializable;
+
+/**
+ * ParkingKey definition.
+ *
+ * Code generated by Apache Ignite Schema Import utility: 08/24/2015.
+ */
+public class ParkingKey implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Value for parkingId. */
+    private int parkingId;
+
+    /**
+     * Empty constructor.
+     */
+    public ParkingKey() {
+        // No-op.
+    }
+
+    /**
+     * Full constructor.
+     */
+    public ParkingKey(
+        int parkingId
+    ) {
+        this.parkingId = parkingId;
+    }
+
+    /**
+     * Gets parkingId.
+     *
+     * @return Value for parkingId.
+     */
+    public int getParkingId() {
+        return parkingId;
+    }
+
+    /**
+     * Sets parkingId.
+     *
+     * @param parkingId New value for parkingId.
+     */
+    public void setParkingId(int parkingId) {
+        this.parkingId = parkingId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (!(o instanceof ParkingKey))
+            return false;
+
+        ParkingKey that = (ParkingKey)o;
+
+        if (parkingId != that.parkingId)
+            return false;
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = parkingId;
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "ParkingKey [parkingId=" + parkingId +
+            "]";
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/resources/logging.properties
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/resources/logging.properties b/modules/control-center-agent/src/main/resources/logging.properties
new file mode 100644
index 0000000..44115f8
--- /dev/null
+++ b/modules/control-center-agent/src/main/resources/logging.properties
@@ -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.
+#  */
+#
+
+handlers =java.util.logging.ConsoleHandler
+.level=FINE
+java.util.logging.ConsoleHandler.level=INFO
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format=%1$tl:%1$tM:%1$tS %1$Tp %4$s: %5$s%n

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/test-drive/README.txt
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/test-drive/README.txt b/modules/control-center-agent/test-drive/README.txt
new file mode 100644
index 0000000..723ee3a
--- /dev/null
+++ b/modules/control-center-agent/test-drive/README.txt
@@ -0,0 +1,4 @@
+Ignite Web Agent
+======================================
+
+This is folder for test-drive files.

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/test-drive/test-drive.sql
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/test-drive/test-drive.sql b/modules/control-center-agent/test-drive/test-drive.sql
new file mode 100644
index 0000000..a7deafa
--- /dev/null
+++ b/modules/control-center-agent/test-drive/test-drive.sql
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+CREATE TABLE COUNTRY (
+    ID           INTEGER NOT NULL PRIMARY KEY,
+    COUNTRY_NAME VARCHAR(100)
+);
+
+CREATE TABLE DEPARTMENT (
+    DEPARTMENT_ID   INTEGER     NOT NULL PRIMARY KEY,
+    DEPARTMENT_NAME VARCHAR(50) NOT NULL,
+    COUNTRY_ID      INTEGER,
+    MANAGER_ID      INTEGER
+);
+
+CREATE TABLE EMPLOYEE (
+    EMPLOYEE_ID   INTEGER     NOT NULL PRIMARY KEY,
+    FIRST_NAME    VARCHAR(20) NOT NULL,
+    LAST_NAME     VARCHAR(30) NOT NULL,
+    EMAIL         VARCHAR(25) NOT NULL,
+    PHONE_NUMBER  VARCHAR(20),
+    HIRE_DATE     DATE        NOT NULL,
+    JOB           VARCHAR(50) NOT NULL,
+    SALARY        DOUBLE,
+    MANAGER_ID    INTEGER,
+    DEPARTMENT_ID INTEGER
+);
+
+CREATE INDEX EMP_SALARY_A ON EMPLOYEE (SALARY ASC);
+CREATE INDEX EMP_SALARY_B ON EMPLOYEE (SALARY DESC);
+CREATE INDEX EMP_NAMES ON EMPLOYEE (FIRST_NAME ASC, LAST_NAME ASC);
+
+CREATE SCHEMA CARS;
+
+CREATE TABLE CARS.PARKING (
+    PARKING_ID   INTEGER     NOT NULL PRIMARY KEY,
+    PARKING_NAME VARCHAR(50) NOT NULL
+);
+
+CREATE TABLE CARS.CAR (
+    CAR_ID     INTEGER     NOT NULL PRIMARY KEY,
+    PARKING_ID INTEGER     NOT NULL,
+    CAR_NAME   VARCHAR(50) NOT NULL
+);


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

Posted by an...@apache.org.
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}}


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/common-module.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/common-module.js b/modules/control-center-web/src/main/js/controllers/common-module.js
new file mode 100644
index 0000000..5454c29
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/common-module.js
@@ -0,0 +1,2017 @@
+/*
+ *
+ *  * 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 consoleModule = angular.module('ignite-web-console',
+    ['ngAnimate', 'ngSanitize', 'mgcrea.ngStrap', 'smart-table', 'ui.ace', 'treeControl', 'darthwade.loading', 'agGrid', 'nvd3', 'dndLists']);
+
+// Modal popup configuration.
+consoleModule.config(function ($modalProvider) {
+    angular.extend($modalProvider.defaults, {
+        html: true
+    });
+});
+
+// Comboboxes configuration.
+consoleModule.config(function ($popoverProvider) {
+    angular.extend($popoverProvider.defaults, {
+        trigger: 'manual',
+        placement: 'right',
+        container: 'body',
+        templateUrl: '/validation-error'
+    });
+});
+
+// Tooltips configuration.
+consoleModule.config(function ($tooltipProvider) {
+    angular.extend($tooltipProvider.defaults, {
+        container: 'body',
+        delay: 150,
+        placement: 'right',
+        html: 'true',
+        trigger: 'click hover'
+    });
+});
+
+// Comboboxes configuration.
+consoleModule.config(function ($selectProvider) {
+    angular.extend($selectProvider.defaults, {
+        container: 'body',
+        maxLength: '5',
+        allText: 'Select All',
+        noneText: 'Clear All',
+        templateUrl: '/select',
+        iconCheckmark: 'fa fa-check',
+        caretHtml: ''
+    });
+});
+
+// Alerts configuration.
+consoleModule.config(function ($alertProvider) {
+    angular.extend($alertProvider.defaults, {
+        container: 'body',
+        placement: 'top-right',
+        duration: '5',
+        type: 'danger'
+    });
+});
+
+// Modals configuration.
+consoleModule.config(function($modalProvider) {
+    angular.extend($modalProvider.defaults, {
+        animation: 'am-fade-and-scale'
+    });
+});
+
+// Common functions to be used in controllers.
+consoleModule.service('$common', [
+    '$alert', '$popover', '$timeout', '$focus', '$window', function ($alert, $popover, $timeout, $focus, $window) {
+        function isDefined(v) {
+            return !(v === undefined || v === null);
+        }
+
+        function isEmptyArray(arr) {
+            if (isDefined(arr))
+                return arr.length == 0;
+
+            return true;
+        }
+
+        function isEmptyString(s) {
+            if (isDefined(s))
+                return s.trim().length == 0;
+
+            return true;
+        }
+
+        var msgModal = undefined;
+
+        function errorMessage(errMsg) {
+            if (errMsg) {
+                if (errMsg.hasOwnProperty('message'))
+                    return errMsg.message;
+
+                return errMsg;
+            }
+
+            return 'Internal server error.';
+        }
+
+        function showError(msg, placement, container, persistent) {
+            if (msgModal)
+                msgModal.hide();
+
+            msgModal = $alert({
+                title: errorMessage(msg),
+                placement: placement ? placement : 'top-right',
+                container: container ? container : 'body'
+            });
+
+            if (persistent)
+                msgModal.$options.duration = false;
+
+            return false;
+        }
+
+        var javaBuildInClasses = [
+            'BigDecimal', 'Boolean', 'Byte', 'Date', 'Double', 'Float', 'Integer', 'Long', 'Short', 'String', 'Time', 'Timestamp', 'UUID'
+        ];
+
+        var javaBuildInFullNameClasses = [
+            'java.math.BigDecimal', 'java.lang.Boolean', 'java.lang.Byte', 'java.sql.Date', 'java.lang.Double',
+            'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short', 'java.lang.String',
+            'java.sql.Time', 'java.sql.Timestamp', 'java.util.UUID'
+        ];
+
+        function isJavaBuildInClass(cls) {
+            if (isEmptyString(cls))
+                return false;
+
+            return _.contains(javaBuildInClasses, cls) || _.contains(javaBuildInFullNameClasses, cls);
+        }
+
+        var SUPPORTED_JDBC_TYPES = [
+            'BIGINT',
+            'BIT',
+            'BOOLEAN',
+            'CHAR',
+            'DATE',
+            'DECIMAL',
+            'DOUBLE',
+            'FLOAT',
+            'INTEGER',
+            'LONGNVARCHAR',
+            'LONGVARCHAR',
+            'NCHAR',
+            'NUMERIC',
+            'NVARCHAR',
+            'REAL',
+            'SMALLINT',
+            'TIME',
+            'TIMESTAMP',
+            'TINYINT',
+            'VARCHAR'
+        ];
+
+        var ALL_JDBC_TYPES = [
+            {dbName: 'BIT', dbType: -7, javaType: 'Boolean'},
+            {dbName: 'TINYINT', dbType: -6, javaType: 'Byte'},
+            {dbName: 'SMALLINT', dbType:  5, javaType: 'Short'},
+            {dbName: 'INTEGER', dbType: 4, javaType: 'Integer'},
+            {dbName: 'BIGINT', dbType: -5, javaType: 'Long'},
+            {dbName: 'FLOAT', dbType: 6, javaType: 'Float'},
+            {dbName: 'REAL', dbType: 7, javaType: 'Double'},
+            {dbName: 'DOUBLE', dbType: 8, javaType: 'Double'},
+            {dbName: 'NUMERIC', dbType: 2, javaType: 'BigDecimal'},
+            {dbName: 'DECIMAL', dbType: 3, javaType: 'BigDecimal'},
+            {dbName: 'CHAR', dbType: 1, javaType: 'String'},
+            {dbName: 'VARCHAR', dbType: 12, javaType: 'String'},
+            {dbName: 'LONGVARCHAR', dbType: -1, javaType: 'String'},
+            {dbName: 'DATE', dbType: 91, javaType: 'Date'},
+            {dbName: 'TIME', dbType: 92, javaType: 'Time'},
+            {dbName: 'TIMESTAMP', dbType: 93, javaType: 'Timestamp'},
+            {dbName: 'BINARY', dbType: -2, javaType: 'Object'},
+            {dbName: 'VARBINARY', dbType: -3, javaType: 'Object'},
+            {dbName: 'LONGVARBINARY', dbType: -4, javaType: 'Object'},
+            {dbName: 'NULL', dbType: 0, javaType: 'Object'},
+            {dbName: 'OTHER', dbType: 1111, javaType: 'Object'},
+            {dbName: 'JAVA_OBJECT', dbType: 2000, javaType: 'Object'},
+            {dbName: 'DISTINCT', dbType: 2001, javaType: 'Object'},
+            {dbName: 'STRUCT', dbType: 2002, javaType: 'Object'},
+            {dbName: 'ARRAY', dbType: 2003, javaType: 'Object'},
+            {dbName: 'BLOB', dbType: 2004, javaType: 'Object'},
+            {dbName: 'CLOB', dbType: 2005, javaType: 'String'},
+            {dbName: 'REF', dbType: 2006, javaType: 'Object'},
+            {dbName: 'DATALINK', dbType: 70, javaType: 'Object'},
+            {dbName: 'BOOLEAN', dbType: 16, javaType: 'Boolean'},
+            {dbName: 'ROWID', dbType: -8, javaType: 'Object'},
+            {dbName: 'NCHAR', dbType: -15, javaType: 'String'},
+            {dbName: 'NVARCHAR', dbType: -9, javaType: 'String'},
+            {dbName: 'LONGNVARCHAR', dbType: -16, javaType: 'String'},
+            {dbName: 'NCLOB', dbType: 2011, javaType: 'String'},
+            {dbName: 'SQLXML', dbType: 2009, javaType: 'Object'}
+        ];
+
+        var JAVA_KEYWORDS = [
+            'abstract',     'assert',        'boolean',      'break',           'byte',
+            'case',         'catch',         'char',         'class',           'const',
+            'continue',     'default',       'do',           'double',          'else',
+            'enum',         'extends',       'false',        'final',           'finally',
+            'float',        'for',           'goto',         'if',              'implements',
+            'import',       'instanceof',    'int',          'interface',       'long',
+            'native',       'new',           'null',         'package',         'private',
+            'protected',    'public',        'return',       'short',           'static',
+            'strictfp',     'super',         'switch',       'synchronized',    'this',
+            'throw',        'throws',        'transient',    'true',            'try',
+            'void',         'volatile',      'while'
+        ];
+
+        var VALID_JAVA_IDENTIFIER = new RegExp('^[a-zA-Z_$][a-zA-Z\d_$]*');
+
+        function isValidJavaIdentifier(msg, ident, elemId, panels, panelId) {
+            if (isEmptyString(ident))
+                return showPopoverMessage(panels, panelId, elemId, msg + ' is invalid!');
+
+            if (_.contains(JAVA_KEYWORDS, ident))
+                return showPopoverMessage(panels, panelId, elemId, msg + ' could not contains reserved java keyword: "' + ident + '"!');
+
+            if (!VALID_JAVA_IDENTIFIER.test(ident))
+                return showPopoverMessage(panels, panelId, elemId, msg + ' contains invalid identifier: "' + ident + '"!');
+
+            return true;
+        }
+
+        var context = null;
+
+        /**
+         * Calculate width of specified text in body's font.
+         *
+         * @param text Text to calculate width.
+         * @returns {Number} Width of text in pixels.
+         */
+        function measureText(text) {
+            if (!context) {
+                var canvas = document.createElement('canvas');
+
+                context = canvas.getContext('2d');
+
+                var style = window.getComputedStyle(document.getElementsByTagName('body')[0]);
+
+                context.font = style.fontSize + ' ' + style.fontFamily;
+            }
+
+            return context.measureText(text).width;
+        }
+
+        /**
+         * Compact java full class name by max number of characters.
+         *
+         * @param names Array of class names to compact.
+         * @param nameLength Max available width in characters for simple name.
+         * @returns {*} Array of compacted class names.
+         */
+        function compactByMaxCharts(names, nameLength) {
+            for (var nameIx = 0; nameIx < names.length; nameIx ++) {
+                var s = names[nameIx];
+
+                if (s.length > nameLength) {
+                    var totalLength = s.length;
+
+                    var packages = s.split('.');
+
+                    var packageCnt = packages.length - 1;
+
+                    for (var i = 0; i < packageCnt && totalLength > nameLength; i++) {
+                        if (packages[i].length > 0) {
+                            totalLength -= packages[i].length - 1;
+
+                            packages[i] = packages[i][0];
+                        }
+                    }
+
+                    if (totalLength > nameLength) {
+                        var className = packages[packageCnt];
+
+                        var classNameLen = className.length;
+
+                        var remains = Math.min(nameLength - totalLength + classNameLen, classNameLen);
+
+                        if (remains < 3)
+                            remains = Math.min(3, classNameLen);
+
+                        packages[packageCnt] = className.substring(0, remains) + '...';
+                    }
+
+                    var result = packages[0];
+
+                    for (i = 1; i < packages.length; i++)
+                        result += '.' + packages[i];
+
+                    names[nameIx] = result;
+                }
+            }
+
+            return names
+        }
+
+        /**
+         * Compact java full class name by max number of pixels.
+         *
+         * @param names Array of class names to compact.
+         * @param nameLength Max available width in characters for simple name. Used for calculation optimization.
+         * @param nameWidth Maximum available width in pixels for simple name.
+         * @returns {*} Array of compacted class names.
+         */
+        function compactByMaxPixels(names, nameLength, nameWidth) {
+            if (nameWidth <= 0)
+                return names;
+
+            var fitted = [];
+
+            var widthByName = [];
+
+            var len = names.length;
+
+            var divideTo = len;
+
+            for (var nameIx = 0; nameIx < len; nameIx ++) {
+                fitted[nameIx] = false;
+
+                widthByName[nameIx] = nameWidth;
+            }
+
+            // Try to distribute space from short class names to long class names.
+            do {
+                var remains = 0;
+
+                for (nameIx = 0; nameIx < len; nameIx++) {
+                    if (!fitted[nameIx]) {
+                        var curNameWidth = measureText(names[nameIx]);
+
+                        if (widthByName[nameIx] > curNameWidth) {
+                            fitted[nameIx] = true;
+
+                            remains += widthByName[nameIx] - curNameWidth;
+
+                            divideTo -= 1;
+
+                            widthByName[nameIx] = curNameWidth;
+                        }
+                    }
+                }
+
+                var remainsByName = remains / divideTo;
+
+                for (nameIx = 0; nameIx < len; nameIx++) {
+                    if (!fitted[nameIx]) {
+                        widthByName[nameIx] += remainsByName;
+                    }
+                }
+            }
+            while(remains > 0);
+
+            // Compact class names to available for each space.
+            for (nameIx = 0; nameIx < len; nameIx ++) {
+                var s = names[nameIx];
+
+                if (s.length > (nameLength / 2) | 0) {
+                    var totalWidth = measureText(s);
+
+                    if (totalWidth > widthByName[nameIx]) {
+                        var packages = s.split('.');
+
+                        var packageCnt = packages.length - 1;
+
+                        for (var i = 0; i < packageCnt && totalWidth > widthByName[nameIx]; i++) {
+                            if (packages[i].length > 1) {
+                                totalWidth -= measureText(packages[i].substring(1, packages[i].length));
+
+                                packages[i] = packages[i][0];
+                            }
+                        }
+
+                        var shortPackage = '';
+
+                        for (i = 0; i < packageCnt; i++)
+                            shortPackage += packages[i] + '.';
+
+                        var className = packages[packageCnt];
+
+                        var classLen = className.length;
+
+                        var minLen = Math.min(classLen, 3);
+
+                        totalWidth = measureText(shortPackage + className);
+
+                        // Compact class name if shorten package path is very long.
+                        if (totalWidth > widthByName[nameIx]) {
+                            var maxLen = classLen;
+
+                            var middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+
+                            var minLenPx = measureText(shortPackage + className.substr(0, minLen) + '...');
+                            var maxLenPx = totalWidth;
+
+                            while (middleLen != minLen && middleLen != maxLen) {
+                                var middleLenPx = measureText(shortPackage + className.substr(0, middleLen) + '...');
+
+                                if (middleLenPx > widthByName[nameIx]) {
+                                    maxLen = middleLen;
+                                    maxLenPx = middleLenPx;
+                                }
+                                else {
+                                    minLen = middleLen;
+                                    minLenPx = middleLenPx;
+                                }
+
+                                middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+                            }
+
+                            names[nameIx] = shortPackage + className.substring(0, middleLen) + '...';
+                        }
+                        else
+                            names[nameIx] = shortPackage + className;
+                    }
+                }
+            }
+
+            return names;
+        }
+
+        /**
+         * Calculate available width for text in link to edit element.
+         *
+         * @param index Showed index of element for calcuraion maximal width in pixel.
+         * @param id Id of contains link table.
+         * @returns {*[]} First element is length of class for single value, second element is length for pair vlaue.
+         */
+        function availableWidth(index, id) {
+            var divs = $($('#' + id).find('tr')[index - 1]).find('div');
+
+            var div = null;
+
+            var width = 0;
+
+            for (var divIx = 0; divIx < divs.length; divIx ++)
+                if (divs[divIx].className.length == 0 && (div == null ||  divs[divIx].childNodes.length > div.childNodes.length))
+                    div = divs[divIx];
+
+            if (div != null) {
+                width = div.clientWidth;
+
+                if (width > 0) {
+                    var children = div.childNodes;
+
+                    for (var i = 1; i < children.length; i++) {
+                        var child = children[i];
+
+                        if ('offsetWidth' in child)
+                            width -= $(children[i]).outerWidth(true);
+                    }
+                }
+            }
+
+            return width | 0;
+        }
+
+        var popover = null;
+
+        function ensureActivePanel(panels, id, focusId) {
+            if (panels) {
+                var idx = _.findIndex($('div.panel-collapse'), function(pnl) {
+                    return pnl.id == id;
+                });
+
+                if (idx >= 0) {
+                    var activePanels = panels.activePanels;
+
+                    if (!activePanels || activePanels.length < 1)
+                        panels.activePanels = [idx];
+                    else if (!_.contains(activePanels, idx)) {
+                        var newActivePanels = activePanels.slice();
+
+                        newActivePanels.push(idx);
+
+                        panels.activePanels = newActivePanels;
+                    }
+                }
+
+                if (isDefined(focusId))
+                    $focus(focusId)
+            }
+        }
+
+        function showPopoverMessage(panels, panelId, id, message) {
+            popoverShown = false;
+
+            ensureActivePanel(panels, panelId, id);
+
+            var el = $('body').find('#' + id);
+
+            if (popover)
+                popover.hide();
+
+            var newPopover = $popover(el, {content: message});
+
+            popover = newPopover;
+
+            $timeout(function () { newPopover.$promise.then(newPopover.show); });
+
+            $timeout(function () { newPopover.hide(); }, 5000);
+
+            return false;
+        }
+
+        function getModel(obj, field) {
+            var path = field.path;
+
+            if (!isDefined(path))
+                return obj;
+
+            path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
+            path = path.replace(/^\./, '');           // strip a leading dot
+
+            var segs = path.split('.');
+            var root = obj;
+
+            while (segs.length > 0) {
+                var pathStep = segs.shift();
+
+                if (typeof root[pathStep] === 'undefined')
+                    root[pathStep] = {};
+
+                root = root[pathStep];
+            }
+
+            return root;
+        }
+
+        function checkGroupDirty(group, curItem, srcItem) {
+            function _compareField(field) {
+                var curModel = getModel(curItem, field);
+                var srcModel = getModel(srcItem, field);
+
+                if (field.model == 'kind' && isDefined(curModel.kind)) {
+                    if (curModel.kind != srcModel.kind)
+                        return true;
+
+                    if (_compareFields(field.details[curModel.kind].fields))
+                        return true;
+                }
+
+                var curValue = curModel[field.model];
+                var srcValue = srcModel[field.model];
+
+                if ((_.isArray(curValue) || _.isString(curValue)) && (curValue.length == 0) && (srcValue === undefined))
+                    curValue = undefined;
+
+                if (_.isBoolean(curValue) && !curValue && srcValue === undefined)
+                    curValue = undefined;
+
+                var isCur = isDefined(curValue);
+                var isSrc = isDefined(srcValue);
+
+                return !!((isCur && !isSrc) || (!isCur && isSrc) || (isCur && isSrc && !angular.equals(curValue, srcValue)));
+            }
+
+            function _compareFields(fields) {
+                for (var fldIx = 0; fldIx < fields.length; fldIx++) {
+                    var field = fields[fldIx];
+
+                    if (_compareField(field))
+                        return true;
+                }
+
+                return false;
+            }
+
+            group.dirty = _compareFields(group.fields);
+
+            return group.dirty;
+        }
+
+        return {
+            getModel: getModel,
+            joinTip: function (arr) {
+                if (!arr) {
+                    return arr;
+                }
+
+                var lines = arr.map(function (line) {
+                    var rtrimmed = line.replace(/\s+$/g, '');
+
+                    if (rtrimmed.indexOf('>', this.length - 1) == -1) {
+                        rtrimmed = rtrimmed + '<br/>';
+                    }
+
+                    return rtrimmed;
+                });
+
+                return lines.join('');
+            },
+            mkOptions: function (options) {
+                return _.map(options, function (option) {
+                    return {value: option, label: isDefined(option) ? option : 'Not set'};
+                });
+            },
+            isDefined: isDefined,
+            hasProperty: function (obj, props) {
+                for (var propName in props) {
+                    if (props.hasOwnProperty(propName)) {
+                        if (obj[propName])
+                            return true;
+                    }
+                }
+
+                return false;
+            },
+            isEmptyArray: isEmptyArray,
+            isEmptyString: isEmptyString,
+            errorMessage: errorMessage,
+            hideAlert: function () {
+                if (msgModal)
+                    msgModal.hide();
+            },
+            showError: showError,
+            showInfo: function (msg) {
+                if (msgModal)
+                    msgModal.hide();
+
+                msgModal = $alert({
+                    type: 'success',
+                    title: msg,
+                    duration: 2
+                });
+            },
+            SUPPORTED_JDBC_TYPES: SUPPORTED_JDBC_TYPES,
+            findJdbcType: function (jdbcType) {
+                var res =  _.find(ALL_JDBC_TYPES, function (item) {
+                    return item.dbType == jdbcType;
+                });
+
+                return res ? res : {dbName: 'Unknown', javaType: 'Unknown'}
+            },
+            javaBuildInClasses: javaBuildInClasses,
+            isJavaBuildInClass: isJavaBuildInClass,
+            isValidJavaIdentifier: isValidJavaIdentifier,
+            isValidJavaClass: function (msg, ident, allowBuildInClass, elemId, packageOnly, panels, panelId) {
+                if (isEmptyString(ident))
+                    return showPopoverMessage(panels, panelId, elemId, msg + ' could not be empty!');
+
+                var parts = ident.split('.');
+
+                var len = parts.length;
+
+                if (!allowBuildInClass && isJavaBuildInClass(ident))
+                    return showPopoverMessage(panels, panelId, elemId, msg + ' should not be the Java build-in class!');
+
+                if (len < 2 && !isJavaBuildInClass(ident) && !packageOnly)
+                    return showPopoverMessage(panels, panelId, elemId, msg + ' does not have package specified!');
+
+                for (var i = 0; i < parts.length; i++) {
+                    var part = parts[i];
+
+                    if (!isValidJavaIdentifier(msg, part, elemId, panels, panelId))
+                        return false;
+                }
+
+                return true;
+            },
+            metadataForQueryConfigured: function (meta) {
+                var isEmpty = !isDefined(meta) || (isEmptyArray(meta.queryFields)
+                    && isEmptyArray(meta.ascendingFields)
+                    && isEmptyArray(meta.descendingFields)
+                    && isEmptyArray(meta.textFields)
+                    && isEmptyArray(meta.groups));
+
+                return !isEmpty;
+            },
+            metadataForStoreConfigured: function (meta) {
+                var isEmpty = !isDefined(meta) || (isEmptyString(meta.databaseSchema)
+                    && isEmptyString(meta.databaseTable)
+                    && isEmptyArray(meta.keyFields)
+                    && isEmptyArray(meta.valueFields));
+
+                return !isEmpty;
+            },
+            /**
+             * Cut class name by width in pixel or width in symbol count.
+             *
+             * @param id Id of contains link table.
+             * @param index Showed index of element.
+             * @param maxLength Maximum length in symbols for all names.
+             * @param names Array of class names to compact.
+             * @returns {*} Array of compacted class names.
+             */
+            compactJavaName: function (id, index, maxLength, names) {
+                var prefix = index + ') ';
+
+                var nameCnt = names.length;
+
+                var nameLength = ((maxLength - 3 * (nameCnt - 1)) / nameCnt) | 0;
+
+                try {
+                    var nameWidth = (availableWidth(index, id) - measureText(prefix) - (nameCnt - 1) * measureText(' / ')) /
+                        nameCnt | 0;
+
+                    // HTML5 calculation of showed message width.
+                    names = compactByMaxPixels(names, nameLength, nameWidth);
+                }
+                catch (err) {
+                    names = compactByMaxCharts(names, nameLength);
+                }
+
+                var result = prefix + names[0];
+
+                for (var nameIx = 1; nameIx < names.length; nameIx ++)
+                    result += ' / ' + names[nameIx];
+
+                return result;
+            },
+            ensureActivePanel: function (panels, id, focusId) {
+                ensureActivePanel(panels, id, focusId);
+            },
+            showPopoverMessage: function (panels, panelId, id, message) {
+                return showPopoverMessage(panels, panelId, id, message)
+            },
+            hidePopover: function () {
+                if (popover)
+                    popover.hide();
+            },
+            confirmUnsavedChanges: function(dirty, selectFunc) {
+                if (dirty) {
+                    if ($window.confirm('You have unsaved changes.\n\nAre you sure you want to discard them?'))
+                        selectFunc();
+                }
+                else
+                    selectFunc();
+
+            },
+            saveBtnTipText: function (dirty, objectName) {
+                if (dirty)
+                    return 'Save ' + objectName;
+
+                return 'Nothing to save';
+            },
+            download: function (type, name, data) {
+                var file = document.createElement('a');
+
+                file.setAttribute('href', 'data:' + type +';charset=utf-8,' + data);
+                file.setAttribute('download', name);
+                file.setAttribute('target', '_self');
+
+                file.style.display = 'none';
+
+                document.body.appendChild(file);
+
+                file.click();
+
+                document.body.removeChild(file);
+            },
+            resetItem: function (backupItem, selectedItem, groups, group) {
+                function restoreFields(fields) {
+                    // Reset fields by one.
+                    for (var fldIx = 0; fldIx < fields.length; fldIx ++) {
+                        var field = fields[fldIx];
+
+                        var destMdl = getModel(backupItem, field);
+
+                        if (isDefined(destMdl)) {
+                            if (isDefined(selectedItem)) {
+                                var srcMdl = getModel(selectedItem, field);
+
+                                if (isDefined(srcMdl)) {
+                                    // For array create copy.
+                                    if ($.isArray(srcMdl[field.model]))
+                                        destMdl[field.model] = srcMdl[field.model].slice();
+                                    else
+                                        destMdl[field.model] = srcMdl[field.model];
+                                }
+                                else
+                                    destMdl[field.model] = undefined;
+                            }
+                            else
+                                destMdl[field.model] = undefined;
+
+                            // For kind field restore kind value and all configured kinds.
+                            if (field.model == 'kind') {
+                                var kind = getModel(backupItem, field)[field.model];
+
+                                var details = field.details;
+
+                                var keys = Object.keys(details);
+
+                                for (var detIx = 0; detIx < keys.length; detIx++)
+                                    restoreFields(details[keys[detIx]].fields);
+                            }
+                        }
+                    }
+                }
+
+                // Find group metadata to reset group values.
+                for (var grpIx = 0; grpIx < groups.length; grpIx ++) {
+                    if (groups[grpIx].group == group) {
+                        var fields = groups[grpIx].fields;
+
+                        restoreFields(fields);
+
+                        break;
+                    }
+                }
+            },
+            formUI: function () {
+                return {
+                    ready: false,
+                    expanded: false,
+                    groups: [],
+                    addGroups: function (general, advanced) {
+                        if (general)
+                            $.merge(this.groups, general);
+
+                        if (advanced)
+                            $.merge(this.groups, advanced);
+                    },
+                    isDirty: function () {
+                        return _.findIndex(this.groups, function (group) {
+                            return group.dirty;
+                        }) >= 0;
+                    },
+                    markPristine: function () {
+                        this.groups.forEach(function (group) {
+                            group.dirty = false;
+                        })
+                    },
+                    checkDirty: function(curItem, srcItem) {
+                        this.groups.forEach(function(group) {
+                            if (checkGroupDirty(group, curItem, srcItem))
+                                dirty = true;
+                        });
+                    }
+                };
+            },
+            getQueryVariable: function(name) {
+                var attrs = window.location.search.substring(1).split("&");
+
+                var attr = _.find(attrs, function(attr) {
+                    return attr == name || (attr.indexOf('=') >= 0 && attr.substr(0, attr.indexOf('=')) == name)
+                });
+
+                if (!isDefined(attr))
+                    return undefined;
+
+                if (attr == name)
+                    return true;
+
+                return attr.substr(attr.indexOf('=') + 1);
+            }
+        }
+    }]);
+
+// Confirm popup service.
+consoleModule.service('$confirm', function ($modal, $rootScope, $q) {
+    var scope = $rootScope.$new();
+
+    var deferred;
+
+    var confirmModal = $modal({templateUrl: '/confirm', scope: scope, placement: 'center', show: false});
+
+    scope.confirmOk = function () {
+        deferred.resolve(true);
+
+        confirmModal.hide();
+    };
+
+    scope.confirmCancel = function () {
+        deferred.reject('cancelled');
+
+        confirmModal.hide();
+    };
+
+    confirmModal.confirm = function (content) {
+        scope.content = content || 'Confirm?';
+
+        deferred = $q.defer();
+
+        confirmModal.show();
+
+        return deferred.promise;
+    };
+
+    return confirmModal;
+});
+
+// Show modal message service.
+consoleModule.service('$message', function ($modal, $rootScope) {
+    var scope = $rootScope.$new();
+
+    var messageModal = $modal({templateUrl: '/message', scope: scope, placement: 'center', show: false});
+
+    messageModal.message = function (title, content) {
+        scope.title = title || 'Message';
+        scope.content = content.join('<br/>') || '...';
+
+        messageModal.show();
+    };
+
+    return messageModal;
+});
+
+// Confirm change location.
+consoleModule.service('$unsavedChangesGuard', function () {
+    return {
+        install: function ($scope) {
+            $scope.$on("$destroy", function() {
+                window.onbeforeunload = null;
+            });
+
+            window.onbeforeunload = function(){
+                return $scope.ui && $scope.ui.isDirty()
+                    ? 'You have unsaved changes.\n\nAre you sure you want to discard them?'
+                    : undefined;
+            };
+        }
+    }
+});
+
+
+// Service for confirm or skip several steps.
+consoleModule.service('$confirmBatch', function ($rootScope, $modal,  $q) {
+    var scope = $rootScope.$new();
+
+    var contentGenerator = function () {
+        return 'No content';
+    };
+
+    var deferred;
+
+    var stepConfirmModal = $modal({templateUrl: '/confirm/batch', scope: scope, placement: 'center', show: false});
+
+    function _done(cancel) {
+        if (cancel)
+            deferred.reject('cancelled');
+        else
+            deferred.resolve();
+
+        stepConfirmModal.hide();
+    }
+
+    var items = [];
+    var curIx = 0;
+
+    function _nextElement(skip) {
+        items[curIx].skip = skip;
+
+        curIx++;
+
+        if (curIx < items.length)
+            scope.batchConfirm.content = contentGenerator(items[curIx]);
+        else
+            _done();
+    }
+
+    scope.batchConfirm = {
+        applyToAll: false,
+        cancel: function () {
+            _done(true);
+        },
+        skip: function () {
+            if (this.applyToAll) {
+                for (var i = curIx; i < items.length; i++)
+                    items[i].skip = true;
+
+                _done();
+            }
+            else
+                _nextElement(true);
+        },
+        overwrite: function () {
+            if (this.applyToAll)
+                _done();
+            else
+                _nextElement(false);
+        },
+        reset: function (itemsToConfirm) {
+            items = itemsToConfirm;
+            curIx = 0;
+            this.applyToAll = false;
+            this.content = (items && items.length > 0) ? contentGenerator(items[0]) : undefined;
+        }
+    };
+
+    /**
+     * Show confirm all dialog.
+     *
+     * @param confirmMessageFx Function to generate a confirm message.
+     * @param itemsToConfirm Array of element to process by confirm.
+     */
+    stepConfirmModal.confirm = function (confirmMessageFx, itemsToConfirm) {
+        contentGenerator = confirmMessageFx;
+
+        scope.batchConfirm.reset(itemsToConfirm);
+
+        deferred = $q.defer();
+
+        stepConfirmModal.show();
+
+        return deferred.promise;
+    };
+
+    return stepConfirmModal;
+});
+
+// 'Clone' popup service.
+consoleModule.service('$clone', function ($modal, $rootScope, $q) {
+    var scope = $rootScope.$new();
+
+    var deferred;
+
+    scope.ok = function (newName) {
+        deferred.resolve(newName);
+
+        copyModal.hide();
+    };
+
+    var copyModal = $modal({templateUrl: '/clone', scope: scope, placement: 'center', show: false});
+
+    copyModal.confirm = function (oldName) {
+        scope.newName = oldName + '(1)';
+
+        deferred = $q.defer();
+
+        copyModal.show();
+
+        return deferred.promise;
+    };
+
+    return copyModal;
+});
+
+// Tables support service.
+consoleModule.service('$table', ['$common', '$focus', function ($common, $focus) {
+    function _swapSimpleItems(a, ix1, ix2) {
+        var tmp = a[ix1];
+
+        a[ix1] = a[ix2];
+        a[ix2] = tmp;
+    }
+
+    function _model(item, field) {
+        return $common.getModel(item, field);
+    }
+
+    var table = {name: 'none', editIndex: -1};
+
+    function _tableReset() {
+        table.name = 'none';
+        table.editIndex = -1;
+
+        $common.hidePopover();
+    }
+
+    function _tableState(name, editIndex) {
+        table.name = name;
+        table.editIndex = editIndex;
+    }
+
+    function _tableUI(field) {
+        var ui = field.ui;
+
+        return ui ? ui : field.type;
+    }
+
+    function _tableFocus(focusId, index) {
+        $focus((index < 0 ? 'new' : 'cur') + focusId);
+    }
+
+    function _tableSimpleValue(filed, index) {
+        return index < 0 ? filed.newValue : filed.curValue;
+    }
+
+    function _tablePairValue(filed, index) {
+        return index < 0 ? {key: filed.newKey, value: filed.newValue} : {key: filed.curKey, value: filed.curValue};
+    }
+
+    function _tableStartEdit(item, field, index) {
+        _tableState(field.model, index);
+
+        var val = _model(item, field)[field.model][index];
+
+        var ui = _tableUI(field);
+
+        if (ui == 'table-simple') {
+            field.curValue = val;
+
+            _tableFocus(field.focusId, index);
+        }
+        else if (ui == 'table-pair') {
+            field.curKey = val[field.keyName];
+            field.curValue = val[field.valueName];
+
+            _tableFocus('Key' + field.focusId, index);
+        }
+        else if (ui == 'table-db-fields') {
+            field.curDatabaseName = val.databaseName;
+            field.curDatabaseType = val.databaseType;
+            field.curJavaName = val.javaName;
+            field.curJavaType = val.javaType;
+
+            _tableFocus('DatabaseName' + field.focusId, index);
+        }
+        else if (ui == 'table-query-groups') {
+            field.curGroupName = val.name;
+            field.curFields = val.fields;
+
+            _tableFocus('GroupName', index);
+        }
+    }
+
+    function _tableNewItem(field) {
+        _tableState(field.model, -1);
+
+        var ui = _tableUI(field);
+
+        if (ui == 'table-simple') {
+            field.newValue = null;
+
+            _tableFocus(field.focusId, -1);
+        }
+        else if (ui == 'table-pair') {
+            field.newKey = null;
+            field.newValue = null;
+
+            _tableFocus('Key' + field.focusId, -1);
+        }
+        else if (ui == 'table-db-fields') {
+            field.newDatabaseName = null;
+            field.newDatabaseType = null;
+            field.newJavaName = null;
+            field.newJavaType = null;
+
+            _tableFocus('DatabaseName' + field.focusId, -1);
+        }
+        else if (ui == 'table-query-groups') {
+            field.newGroupName = null;
+            field.newFields = null;
+
+            _tableFocus('GroupName', -1);
+        }
+        else if (ui == 'table-query-group-fields') {
+            _tableFocus('FieldName', -1);
+        }
+    }
+
+    return {
+        tableState: function (name, editIndex) {
+            _tableState(name, editIndex);
+        },
+        tableReset: function () {
+            _tableReset();
+        },
+        tableNewItem: _tableNewItem,
+        tableNewItemActive: function (field) {
+            return table.name == field.model && table.editIndex < 0;
+        },
+        tableEditing: function (field, index) {
+            return table.name == field.model && table.editIndex == index;
+        },
+        tableStartEdit: _tableStartEdit,
+        tableRemove: function (item, field, index) {
+            _tableReset();
+
+            _model(item, field)[field.model].splice(index, 1);
+        },
+        tableSimpleSave: function (valueValid, item, field, index) {
+            var simpleValue = _tableSimpleValue(field, index);
+
+            if (valueValid(item, field, simpleValue, index)) {
+                _tableReset();
+
+                if (index < 0) {
+                    if (_model(item, field)[field.model])
+                        _model(item, field)[field.model].push(simpleValue);
+                    else
+                        _model(item, field)[field.model] = [simpleValue];
+
+                    _tableNewItem(field);
+                }
+                else {
+                    var arr = _model(item, field)[field.model];
+
+                    arr[index] = simpleValue;
+
+                    if (index < arr.length - 1)
+                        _tableStartEdit(item, field, index + 1);
+                    else
+                        _tableNewItem(field);
+                }
+            }
+        },
+        tableSimpleSaveVisible: function (field, index) {
+            return !$common.isEmptyString(_tableSimpleValue(field, index));
+        },
+        tableSimpleUp: function (item, field, index) {
+            _tableReset();
+
+            _swapSimpleItems(_model(item, field)[field.model], index, index - 1);
+        },
+        tableSimpleDown: function (item, field, index) {
+            _tableReset();
+
+            _swapSimpleItems(_model(item, field)[field.model], index, index + 1);
+        },
+        tableSimpleDownVisible: function (item, field, index) {
+            return index < _model(item, field)[field.model].length - 1;
+        },
+        tablePairValue: _tablePairValue,
+        tablePairSave: function (pairValid, item, field, index) {
+            if (pairValid(item, field, index)) {
+                var pairValue = _tablePairValue(field, index);
+
+                var pairModel = {};
+
+                if (index < 0) {
+                    pairModel[field.keyName] = pairValue.key;
+                    pairModel[field.valueName] = pairValue.value;
+
+                    if (item[field.model])
+                        item[field.model].push(pairModel);
+                    else
+                        item[field.model] = [pairModel];
+
+                    _tableNewItem(field);
+                }
+                else {
+                    pairModel = item[field.model][index];
+
+                    pairModel[field.keyName] = pairValue.key;
+                    pairModel[field.valueName] = pairValue.value;
+
+                    if (index < item[field.model].length - 1)
+                        _tableStartEdit(item, field, index + 1);
+                    else
+                        _tableNewItem(field);
+                }
+            }
+        },
+        tablePairSaveVisible: function (field, index) {
+            var pairValue = _tablePairValue(field, index);
+
+            return !$common.isEmptyString(pairValue.key) && !$common.isEmptyString(pairValue.value);
+        },
+        tableFocusInvalidField: function (index, id) {
+            _tableFocus(id, index);
+
+            return false;
+        },
+        tableFieldId: function (index, id) {
+            return (index < 0 ? 'new' : 'cur') + id;
+        }
+    }
+}]);
+
+// Preview support service.
+consoleModule.service('$preview', ['$timeout', '$interval', function ($timeout, $interval) {
+    var Range = require('ace/range').Range;
+
+    var prevContent = [];
+
+    var animation = {editor: null, stage: 0, start: 0, stop: 0};
+
+    function _clearSelection(editor) {
+        _.forEach(editor.session.getMarkers(false), function (marker) {
+            editor.session.removeMarker(marker.id);
+        });
+    }
+
+    /**
+     * Switch to next stage of animation.
+     */
+    function _animate() {
+        animation.stage += animation.step;
+
+        var stage = animation.stage;
+
+        var editor = animation.editor;
+
+        _clearSelection(editor);
+
+        animation.selections.forEach(function (selection) {
+            editor.session.addMarker(new Range(selection.start, 0, selection.stop, 0),
+                'preview-highlight-' + stage, 'line', false);
+        });
+
+        if (stage == animation.finalStage) {
+            editor.animatePromise = null;
+
+            if (animation.clearOnFinal)
+                _clearSelection(editor);
+        }
+    }
+
+    /**
+     * Show selections with animation.
+     *
+     * @param editor Editor to show selection.
+     * @param selections Array of selection intervals.
+     */
+    function _fadeIn(editor, selections) {
+        _fade(editor, selections, 1, 0, 10, false);
+    }
+
+    /**
+     * Hide selections with animation.
+     *
+     * @param editor Editor to show selection.
+     * @param selections Array of selection intervals.
+     */
+    function _fadeOut(editor, selections) {
+        _fade(editor, selections, -1, 10, 0, true);
+    }
+
+    /**
+     * Selection with animation.
+     *
+     * @param editor Editor to show selection animation.
+     * @param selections Array of selection intervals.
+     * @param step Step of animation (1 or -1).
+     * @param startStage Start stage of animaiton.
+     * @param finalStage Final stage of animation.
+     * @param clearOnFinal Boolean flat to clear selection on animation finish.
+     */
+    function _fade(editor, selections, step, startStage, finalStage, clearOnFinal) {
+        var promise = editor.animatePromise;
+
+        if (promise) {
+            $interval.cancel(promise);
+
+            _clearSelection(editor);
+        }
+
+        animation = {editor: editor, step: step, stage: startStage, finalStage: finalStage, clearOnFinal: clearOnFinal, selections: selections};
+
+        editor.animatePromise = $interval(_animate, 100, 10, false);
+    }
+
+    function previewChanged (ace) {
+        var content = ace[0];
+
+        var editor = ace[1];
+
+        var clearPromise = editor.clearPromise;
+
+        var newContent = content.lines;
+
+        if (content.action == 'remove')
+            prevContent = content.lines;
+        else if (prevContent.length > 0 && newContent.length > 0 && editor.attractAttention) {
+            if (clearPromise) {
+                $timeout.cancel(clearPromise);
+
+                _clearSelection(editor);
+            }
+
+            var selections = [];
+
+            var newIx = 0;
+            var prevIx = 0;
+
+            var prevLen = prevContent.length - (prevContent[prevContent.length - 1] == '' ? 1 : 0);
+            var newLen = newContent.length - (newContent[newContent.length - 1] == '' ? 1 : 0);
+
+            var removed = newLen < prevLen;
+
+            var skipEnd = 0;
+
+            var selected = false;
+            var scrollTo = -1;
+
+            while (newContent[newLen - 1] == prevContent[prevLen - 1] && newLen > 0 && prevLen > 0) {
+                prevLen -= 1;
+                newLen -= 1;
+
+                skipEnd += 1;
+            }
+
+            while (newIx < newLen || prevIx < prevLen) {
+                var start = -1;
+                var end = -1;
+
+                // Find an index of a first line with different text.
+                for (; (newIx < newLen || prevIx < prevLen) && start < 0; newIx++, prevIx++) {
+                    if (newIx >= newLen || prevIx >= prevLen || newContent[newIx] != prevContent[prevIx]) {
+                        start = newIx;
+
+                        break;
+                    }
+                }
+
+                if (start >= 0) {
+                    // Find an index of a last line with different text by checking last string of old and new content in reverse order.
+                    for (var i = start; i < newLen && end < 0; i ++) {
+                        for (var j = prevIx; j < prevLen && end < 0; j ++) {
+                            if (newContent[i] == prevContent[j] && newContent[i] != '') {
+                                end = i;
+
+                                newIx = i;
+                                prevIx = j;
+
+                                break;
+                            }
+                        }
+                    }
+
+                    if (end < 0) {
+                        end = newLen;
+
+                        newIx = newLen;
+                        prevIx = prevLen;
+                    }
+
+                    if (start == end) {
+                        if (removed)
+                            start = Math.max(0, start - 1);
+
+                        end = Math.min(newLen + skipEnd, end + 1)
+                    }
+
+                    if (start <= end) {
+                        selections.push({start: start, stop: end});
+
+                        if (!selected)
+                            scrollTo = start;
+
+                        selected = true;
+                    }
+                }
+            }
+
+            // Run clear selection one time.
+            if (selected) {
+                _fadeIn(editor, selections);
+
+                editor.clearPromise = $timeout(function () {
+                    _fadeOut(editor, selections);
+
+                    editor.clearPromise = null;
+                }, 2000);
+
+                editor.scrollToRow(scrollTo)
+            }
+
+            prevContent = [];
+        }
+        else
+            editor.attractAttention = true;
+
+    }
+
+    return {
+        previewInit: function (preview) {
+            preview.setReadOnly(true);
+            preview.setOption('highlightActiveLine', false);
+            preview.setAutoScrollEditorIntoView(true);
+            preview.$blockScrolling = Infinity;
+            preview.attractAttention = true;
+
+            var renderer = preview.renderer;
+
+            renderer.setHighlightGutterLine(false);
+            renderer.setShowPrintMargin(false);
+            renderer.setOption('fontSize', '10px');
+            renderer.setOption('maxLines', '50');
+
+            preview.setTheme('ace/theme/chrome');
+        },
+        previewChanged: previewChanged
+    }
+}]);
+
+consoleModule.service('ngCopy', ['$window', '$common', function ($window, $common) {
+    var body = angular.element($window.document.body);
+
+    var textArea = angular.element('<textarea/>');
+
+    textArea.css({
+        position: 'fixed',
+        opacity: '0'
+    });
+
+    return function (toCopy) {
+        textArea.val(toCopy);
+
+        body.append(textArea);
+
+        textArea[0].select();
+
+        try {
+            if (document.execCommand('copy'))
+                $common.showInfo('Value copied to clipboard');
+            else
+                window.prompt("Copy to clipboard: Ctrl+C, Enter", toCopy);
+        } catch (err) {
+            window.prompt("Copy to clipboard: Ctrl+C, Enter", toCopy);
+        }
+
+        textArea.remove();
+    }
+}]).directive('ngClickCopy', ['ngCopy', function (ngCopy) {
+    return {
+        restrict: 'A',
+        link: function (scope, element, attrs) {
+            element.bind('click', function () {
+                ngCopy(attrs.ngClickCopy);
+            });
+        }
+    }
+}]);
+
+// Filter to decode name using map(value, label).
+consoleModule.filter('displayValue', function () {
+    return function (v, m, dflt) {
+        var i = _.findIndex(m, function (item) {
+            return item.value == v;
+        });
+
+        if (i >= 0) {
+            return m[i].label;
+        }
+
+        if (dflt) {
+            return dflt;
+        }
+
+        return 'Unknown value';
+    }
+});
+
+consoleModule.filter('clustersSearch', function() {
+    var discoveries = {
+        'Vm': 'static ips',
+        'Multicast': 'multicast',
+        'S3': 'aws s3',
+        'Cloud': 'apache jclouds',
+        'GoogleStorage': 'google cloud storage',
+        'Jdbc': 'jdbc',
+        'SharedFs': 'shared filesystem'
+    };
+
+    return function(array, query) {
+        if (!angular.isUndefined(array) && !angular.isUndefined(query) && !angular.isUndefined(query.$)) {
+            var filtredArray = [];
+
+            var matchString = query.$.toLowerCase();
+
+            angular.forEach(array, function (row) {
+                var label = (row.name + ', ' + discoveries[row.discovery.kind]).toLowerCase();
+
+                if (label.indexOf(matchString) >= 0)
+                    filtredArray.push(row);
+            });
+
+            return filtredArray;
+        } else
+            return array;
+    }
+});
+
+consoleModule.filter('cachesSearch', function() {
+    return function(array, query) {
+        if (!angular.isUndefined(array) && !angular.isUndefined(query) && !angular.isUndefined(query.$)) {
+            var filtredArray = [];
+
+            var matchString = query.$.toLowerCase();
+
+            angular.forEach(array, function (row) {
+                var label = (row.name + ', ' + row.cacheMode + ', ' + atomicityMode).toLowerCase();
+
+                if (label.indexOf(matchString) >= 0)
+                    filtredArray.push(row);
+            });
+
+            return filtredArray;
+        } else
+            return array;
+    }
+});
+
+consoleModule.filter('metadatasSearch', function() {
+    return function(array, query) {
+        if (!angular.isUndefined(array) && !angular.isUndefined(query) && !angular.isUndefined(query.$)) {
+            var filtredArray = [];
+
+            var matchString = query.$.toLowerCase();
+
+            angular.forEach(array, function (row) {
+                if (row.valueType.toLowerCase().indexOf(matchString) >= 0)
+                    filtredArray.push(row);
+            });
+
+            return filtredArray;
+        } else
+            return array;
+    }
+});
+
+// Filter metadata with key fields configuration.
+consoleModule.filter('metadatasValidation', ['$common', function ($common) {
+    return function(metadatas, valid, invalid) {
+        if (valid && invalid)
+            return metadatas;
+
+        var out = [];
+
+        _.forEach(metadatas, function (meta) {
+            var _valid = !$common.metadataForStoreConfigured(meta) || $common.isJavaBuildInClass(meta.keyType) || !$common.isEmptyArray(meta.keyFields);
+
+            if (valid && _valid || invalid && !_valid)
+                out.push(meta);
+        });
+
+        return out;
+    }
+}]);
+
+// Directive to enable validation for IP addresses.
+consoleModule.directive('ipaddress', function () {
+    const ip = '(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])';
+    const port = '([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])';
+    const portRange = '(:' + port + '(..' + port + ')?)?';
+    const host = '(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])';
+
+    return {
+        require: 'ngModel',
+        link: function (scope, elem, attrs, ctrl) {
+            ctrl.$validators.ipaddress = function (modelValue, viewValue) {
+                if (ctrl.$isEmpty(modelValue) || !attrs['ipaddress'])
+                    return true;
+
+                return viewValue.match(new RegExp('(^' + ip + portRange + '$)|(^' + host + portRange + '$)')) != null;
+            }
+        }
+    }
+});
+
+// Directive to enable validation to match specified value.
+consoleModule.directive('match', function ($parse) {
+    return {
+        require: 'ngModel',
+        link: function (scope, elem, attrs, ctrl) {
+            scope.$watch(function () {
+                return $parse(attrs.match)(scope) === ctrl.$modelValue;
+            }, function (currentValue) {
+                ctrl.$setValidity('mismatch', currentValue);
+            });
+        }
+    };
+});
+
+// Directive to bind ENTER key press with some user action.
+consoleModule.directive('onEnter', function ($timeout) {
+    return function (scope, elem, attrs) {
+        elem.on('keydown keypress', function (event) {
+            if (event.which === 13) {
+                scope.$apply(function () {
+                    $timeout(function () {
+                        scope.$eval(attrs.onEnter)
+                    });
+                });
+
+                event.preventDefault();
+            }
+        });
+
+        // Removes bound events in the element itself when the scope is destroyed
+        scope.$on('$destroy', function () {
+            elem.off('keydown keypress');
+        });
+    };
+});
+
+// Directive to bind ESC key press with some user action.
+consoleModule.directive('onEscape', function () {
+    return function (scope, elem, attrs) {
+        elem.on('keydown keypress', function (event) {
+            if (event.which === 27) {
+                scope.$apply(function () {
+                    scope.$eval(attrs.onEscape);
+                });
+
+                event.preventDefault();
+            }
+        });
+
+        // Removes bound events in the element itself when the scope is destroyed
+        scope.$on('$destroy', function () {
+            elem.off('keydown keypress');
+        });
+    };
+});
+
+// Directive to retain selection. To fix angular-strap typeahead bug with setting cursor to the end of text.
+consoleModule.directive('retainSelection', function ($timeout) {
+    var promise;
+
+    return function (scope, elem, attr) {
+        elem.on('keydown', function (evt) {
+            var key = evt.which;
+            var ctrlDown = evt.ctrlKey || evt.metaKey;
+            var input = this;
+            var start = input.selectionStart;
+
+            if (promise)
+                $timeout.cancel(promise);
+
+            promise = $timeout(function () {
+                var setCursor = false;
+
+                // Handle Backspace[8].
+                if (key == 8 && start > 0) {
+                    start -= 1;
+
+                    setCursor = true;
+                }
+                // Handle Del[46].
+                else if (key == 46)
+                    setCursor = true;
+                // Handle: Caps Lock[20], Tab[9], Shift[16], Ctrl[17], Alt[18], Esc[27], Enter[13], Arrows[37..40], Home[36], End[35], Ins[45], PgUp[33], PgDown[34], F1..F12[111..124], Num Lock[], Scroll Lock[145].
+                else if (!(key == 8 || key == 9 || key == 13 || (key > 15 && key < 20) || key == 27 ||
+                    (key > 32 && key < 41) || key == 45 || (key > 111 && key < 124) || key == 144 || key == 145)) {
+                    // Handle: Ctrl + [A[65], C[67], V[86]].
+                    if (!(ctrlDown && (key = 65 || key == 67 || key == 86))) {
+                        start += 1;
+
+                        setCursor = true;
+                    }
+                }
+
+                if (setCursor)
+                    input.setSelectionRange(start, start);
+
+                promise = undefined;
+            });
+        });
+
+        // Removes bound events in the element itself when the scope is destroyed
+        scope.$on('$destroy', function () {
+            elem.off('keydown');
+        });
+    };
+});
+
+// Factory function to focus element.
+consoleModule.factory('$focus', function ($timeout) {
+    return function (id) {
+        // Timeout makes sure that is invoked after any other event has been triggered.
+        // E.g. click events that need to run before the focus or inputs elements that are
+        // in a disabled state but are enabled when those events are triggered.
+        $timeout(function () {
+            var elem = $('#' + id);
+
+            if (elem.length > 0) {
+                var offset = elem.offset();
+
+                var elemOffset = offset.top;
+
+                var winOffset = window.pageYOffset;
+
+                var topHeight = $('.padding-top-dflt.affix').outerHeight();
+
+                if(elemOffset - 20 - topHeight < winOffset
+                    || elemOffset + elem.outerHeight(true) + 20 > winOffset + window.innerHeight)
+                    $('html, body').animate({
+                        scrollTop: elemOffset - 20 - topHeight
+                    }, 10);
+
+                elem[0].focus();
+            }
+        });
+    };
+});
+
+// Directive to auto-focus element.
+consoleModule.directive('autoFocus', function($timeout) {
+    return {
+        restrict: 'AC',
+        link: function(scope, element) {
+            $timeout(function(){
+                element[0].focus();
+            });
+        }
+    };
+});
+
+// Directive to focus next element on ENTER key.
+consoleModule.directive('enterFocusNext', function ($focus) {
+    return function (scope, elem, attrs) {
+        elem.on('keydown keypress', function (event) {
+            if (event.which === 13) {
+                event.preventDefault();
+
+                $focus(attrs.enterFocusNext);
+            }
+        });
+    };
+});
+
+// Directive to mark elements to focus.
+consoleModule.directive('onClickFocus', function ($focus) {
+    return function (scope, elem, attr) {
+        elem.on('click', function () {
+            $focus(attr.onClickFocus);
+        });
+
+        // Removes bound events in the element itself when the scope is destroyed
+        scope.$on('$destroy', function () {
+            elem.off('click');
+        });
+    };
+});
+
+// Navigation bar controller.
+consoleModule.controller('activeLink', [
+    '$scope', function ($scope) {
+        $scope.isActive = function (path) {
+            return window.location.pathname.substr(0, path.length) == path;
+        };
+    }]);
+
+// Login popup controller.
+consoleModule.controller('auth', [
+    '$scope', '$modal', '$http', '$window', '$common', '$focus',
+    function ($scope, $modal, $http, $window, $common, $focus) {
+        $scope.action = 'login';
+
+        $scope.userDropdown = [{text: 'Profile', href: '/profile'}];
+
+        $focus('user_email');
+
+        if (!$scope.becomeUsed) {
+            if ($scope.user && $scope.user.admin)
+                $scope.userDropdown.push({text: 'Admin Panel', href: '/admin'});
+
+            $scope.userDropdown.push({text: 'Log Out', href: '/logout'});
+        }
+
+        if ($scope.token && !$scope.error)
+            $focus('user_password');
+
+        // Try to authorize user with provided credentials.
+        $scope.auth = function (action, user_info) {
+            $http.post('/' + action, user_info)
+                .success(function () {
+                    $window.location = '/configuration/clusters';
+                })
+                .error(function (err, status) {
+                    if (status == 403) {
+                        $window.location = '/password/reset';
+                    }
+                    else
+                        $common.showPopoverMessage(undefined, undefined, 'user_email', err);
+                });
+        };
+
+        // Try to reset user password for provided token.
+        $scope.resetPassword = function (reset_info) {
+            $http.post('/password/reset', reset_info)
+                .success(function (data) {
+                    $common.showInfo('Password successfully changed');
+
+                    $scope.user_info = {email: data};
+                    $window.location = '/';
+                })
+                .error(function (data, state) {
+                    $common.showError(data);
+
+                    if (state == 503) {
+                        $scope.user_info = {};
+                        $scope.login();
+                    }
+                });
+        }
+    }]);
+
+// Download agent controller.
+consoleModule.controller('agent-download', [
+    '$common', '$scope', '$interval', '$modal', '$window', function ($common, $scope, $interval, $modal, $window) {
+        // Pre-fetch modal dialogs.
+        var _agentDownloadModal = $modal({scope: $scope, templateUrl: '/agent/download', show: false});
+
+        var _agentDownloadHide = _agentDownloadModal.hide;
+
+        _agentDownloadModal.hide = function () {
+            if (_agentDownloadModal.$isShown)
+                $common.hideAlert();
+
+            if (!$scope.checkConnection)
+                $interval.cancel(_agentDownloadModal.updatePromise);
+
+            _agentDownloadHide();
+        };
+
+        $scope.goHome = function () {
+            if ($scope.checkConnection)
+                $window.location = '/';
+
+            _agentDownloadModal.hide()
+        };
+
+        $scope.downloadAgent = function () {
+            var lnk = document.createElement('a');
+
+            lnk.setAttribute('href', '/agent/ignite-web-agent-1.5.0-SNAPSHOT.zip');
+            lnk.style.display = 'none';
+
+            document.body.appendChild(lnk);
+
+            lnk.click();
+
+            document.body.removeChild(lnk);
+        };
+
+        var _handleException = function (errMsg, status) {
+            if (!_agentDownloadModal.$isShown)
+                _agentDownloadModal.$promise.then(_agentDownloadModal.show);
+
+            if (status != 503)
+                $common.showError(errMsg, 'top-right', 'body', true);
+        };
+
+        $scope.awaitAgent = function (checkFn) {
+            _agentDownloadModal.checkFn = checkFn;
+
+            _agentDownloadModal.updatePromise = $interval(function () {
+                checkFn(_agentDownloadModal.hide, _handleException);
+            }, 5000, 0, false);
+
+            checkFn(_agentDownloadModal.hide, _handleException);
+        };
+
+        $scope.checkNodeConnection = function (checkFn) {
+            _agentDownloadModal.checkFn = checkFn;
+
+            $scope.checkConnection = true;
+
+            _agentDownloadModal.$options.backdrop = 'static';
+
+            _agentDownloadModal.updatePromise = $interval(function () {
+                checkFn(_agentDownloadModal.hide, _handleException);
+            }, 5000, 0, false);
+
+            checkFn(_agentDownloadModal.hide, _handleException);
+        }
+    }]);
+
+// Navigation bar controller.
+consoleModule.controller('notebooks', ['$scope', '$modal', '$window', '$http', '$common',
+    function ($scope, $modal, $window, $http, $common) {
+    $scope.$root.notebooks = [];
+
+    // Pre-fetch modal dialogs.
+    var _notebookNewModal = $modal({scope: $scope, templateUrl: '/notebooks/new', show: false});
+
+    $scope.$root.rebuildDropdown = function() {
+        $scope.notebookDropdown = [
+            {text: 'Create new notebook', click: 'inputNotebookName()'},
+            {divider: true}
+        ];
+
+        _.forEach($scope.$root.notebooks, function (notebook) {
+            $scope.notebookDropdown.push({
+                text: notebook.name,
+                href: '/sql/' + notebook._id,
+                target: '_self'
+            });
+        });
+    };
+
+    $scope.$root.reloadNotebooks = function() {
+        // When landing on the page, get clusters and show them.
+        $http.post('/notebooks/list')
+            .success(function (data) {
+                $scope.$root.notebooks = data;
+
+                $scope.$root.rebuildDropdown();
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+    };
+
+    $scope.$root.inputNotebookName = function() {
+        _notebookNewModal.$promise.then(_notebookNewModal.show);
+    };
+
+    $scope.$root.createNewNotebook = function(name) {
+        $http.post('/notebooks/new', {name: name})
+            .success(function (id) {
+                _notebookNewModal.hide();
+
+                $window.location = '/sql/' + id;
+            })
+            .error(function (message, state) {
+                $common.showError(message);
+            });
+    };
+
+    $scope.$root.reloadNotebooks();
+}]);
+
+// Navigation bar controller.
+consoleModule.controller('save-remove', ['$scope', function ($scope) {
+    $scope.removeDropdown = [{ 'text': 'Remove All', 'click': 'removeAllItems()'}];
+}]);


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/helpers/data-structures.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/helpers/data-structures.js b/modules/control-center-web/src/main/js/helpers/data-structures.js
new file mode 100644
index 0000000..71e38a9
--- /dev/null
+++ b/modules/control-center-web/src/main/js/helpers/data-structures.js
@@ -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.
+ *
+ */
+
+// For server side we should load required libraries.
+if (typeof window === 'undefined') {
+    $commonUtils = require('./common-utils');
+}
+
+// Entry point for common data structures.
+$dataStructures = {};
+
+// Ignite events groups.
+$dataStructures.EVENT_GROUPS = {
+    EVTS_CHECKPOINT: ['EVT_CHECKPOINT_SAVED', 'EVT_CHECKPOINT_LOADED', 'EVT_CHECKPOINT_REMOVED'],
+    EVTS_DEPLOYMENT: ['EVT_CLASS_DEPLOYED', 'EVT_CLASS_UNDEPLOYED', 'EVT_CLASS_DEPLOY_FAILED', 'EVT_TASK_DEPLOYED',
+        'EVT_TASK_UNDEPLOYED', 'EVT_TASK_DEPLOY_FAILED'],
+    EVTS_ERROR: ['EVT_JOB_TIMEDOUT', 'EVT_JOB_FAILED', 'EVT_JOB_FAILED_OVER', 'EVT_JOB_REJECTED', 'EVT_JOB_CANCELLED',
+        'EVT_TASK_TIMEDOUT', 'EVT_TASK_FAILED', 'EVT_CLASS_DEPLOY_FAILED', 'EVT_TASK_DEPLOY_FAILED',
+        'EVT_TASK_DEPLOYED', 'EVT_TASK_UNDEPLOYED', 'EVT_CACHE_REBALANCE_STARTED', 'EVT_CACHE_REBALANCE_STOPPED'],
+    EVTS_DISCOVERY: ['EVT_NODE_JOINED', 'EVT_NODE_LEFT', 'EVT_NODE_FAILED', 'EVT_NODE_SEGMENTED',
+        'EVT_CLIENT_NODE_DISCONNECTED', 'EVT_CLIENT_NODE_RECONNECTED'],
+    EVTS_JOB_EXECUTION: ['EVT_JOB_MAPPED', 'EVT_JOB_RESULTED', 'EVT_JOB_FAILED_OVER', 'EVT_JOB_STARTED',
+        'EVT_JOB_FINISHED', 'EVT_JOB_TIMEDOUT', 'EVT_JOB_REJECTED', 'EVT_JOB_FAILED', 'EVT_JOB_QUEUED',
+        'EVT_JOB_CANCELLED'],
+    EVTS_TASK_EXECUTION: ['EVT_TASK_STARTED', 'EVT_TASK_FINISHED', 'EVT_TASK_FAILED', 'EVT_TASK_TIMEDOUT',
+        'EVT_TASK_SESSION_ATTR_SET', 'EVT_TASK_REDUCED'],
+    EVTS_CACHE: ['EVT_CACHE_ENTRY_CREATED', 'EVT_CACHE_ENTRY_DESTROYED', 'EVT_CACHE_OBJECT_PUT',
+        'EVT_CACHE_OBJECT_READ', 'EVT_CACHE_OBJECT_REMOVED', 'EVT_CACHE_OBJECT_LOCKED', 'EVT_CACHE_OBJECT_UNLOCKED',
+        'EVT_CACHE_OBJECT_SWAPPED', 'EVT_CACHE_OBJECT_UNSWAPPED', 'EVT_CACHE_OBJECT_EXPIRED'],
+    EVTS_CACHE_REBALANCE: ['EVT_CACHE_REBALANCE_STARTED', 'EVT_CACHE_REBALANCE_STOPPED',
+        'EVT_CACHE_REBALANCE_PART_LOADED', 'EVT_CACHE_REBALANCE_PART_UNLOADED', 'EVT_CACHE_REBALANCE_OBJECT_LOADED',
+        'EVT_CACHE_REBALANCE_OBJECT_UNLOADED', 'EVT_CACHE_REBALANCE_PART_DATA_LOST'],
+    EVTS_CACHE_LIFECYCLE: ['EVT_CACHE_STARTED', 'EVT_CACHE_STOPPED', 'EVT_CACHE_NODES_LEFT'],
+    EVTS_CACHE_QUERY: ['EVT_CACHE_QUERY_EXECUTED', 'EVT_CACHE_QUERY_OBJECT_READ'],
+    EVTS_SWAPSPACE: ['EVT_SWAP_SPACE_CLEARED', 'EVT_SWAP_SPACE_DATA_REMOVED', 'EVT_SWAP_SPACE_DATA_READ',
+        'EVT_SWAP_SPACE_DATA_STORED', 'EVT_SWAP_SPACE_DATA_EVICTED'],
+    EVTS_IGFS: ['EVT_IGFS_FILE_CREATED', 'EVT_IGFS_FILE_RENAMED', 'EVT_IGFS_FILE_DELETED', 'EVT_IGFS_FILE_OPENED_READ',
+        'EVT_IGFS_FILE_OPENED_WRITE', 'EVT_IGFS_FILE_CLOSED_WRITE', 'EVT_IGFS_FILE_CLOSED_READ', 'EVT_IGFS_FILE_PURGED',
+        'EVT_IGFS_META_UPDATED', 'EVT_IGFS_DIR_CREATED', 'EVT_IGFS_DIR_RENAMED', 'EVT_IGFS_DIR_DELETED']
+};
+
+// Pairs of Java build-in classes.
+$dataStructures.JAVA_BUILD_IN_CLASSES = [
+    {short: 'BigDecimal', full: 'java.math.BigDecimal'},
+    {short: 'Boolean', full: 'java.lang.Boolean'},
+    {short: 'Byte', full: 'java.lang.Byte'},
+    {short: 'Date', full: 'java.sql.Date'},
+    {short: 'Double', full: 'java.lang.Double'},
+    {short: 'Float', full: 'java.lang.Float'},
+    {short: 'Integer', full: 'java.lang.Integer'},
+    {short: 'Long', full: 'java.lang.Long'},
+    {short: 'Object', full: 'java.lang.Object'},
+    {short: 'Short', full: 'java.lang.Short'},
+    {short: 'String', full: 'java.lang.String'},
+    {short: 'Time', full: 'java.sql.Time'},
+    {short: 'Timestamp', full: 'java.sql.Timestamp'},
+    {short: 'UUID', full: 'java.util.UUID'}
+];
+
+/**
+ * @param clsName Class name to check.
+ * @returns 'true' if given class name is a java build-in type.
+ */
+$dataStructures.isJavaBuildInClass = function (clsName) {
+    if ($commonUtils.isDefined(clsName)) {
+        for (var i = 0; i < $dataStructures.JAVA_BUILD_IN_CLASSES.length; i++) {
+            var jbic = $dataStructures.JAVA_BUILD_IN_CLASSES[i];
+
+            if (clsName == jbic.short || clsName == jbic.full)
+                return true;
+        }
+    }
+
+    return false;
+};
+
+/**
+ * @param clsName Class name to check.
+ * @returns Full class name for java build-in types or source class otherwise.
+ */
+$dataStructures.fullClassName = function (clsName) {
+    if ($commonUtils.isDefined(clsName)) {
+        for (var i = 0; i < $dataStructures.JAVA_BUILD_IN_CLASSES.length; i++) {
+            var jbic = $dataStructures.JAVA_BUILD_IN_CLASSES[i];
+
+            if (clsName == jbic.short)
+                return jbic.full;
+        }
+    }
+
+    return clsName;
+};
+
+// For server side we should export properties generation entry point.
+if (typeof window === 'undefined') {
+    module.exports = $dataStructures;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/keys/test.crt
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/keys/test.crt b/modules/control-center-web/src/main/js/keys/test.crt
new file mode 100644
index 0000000..50c6d5c
--- /dev/null
+++ b/modules/control-center-web/src/main/js/keys/test.crt
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB6zCCAVQCCQDcAphbU6UcLjANBgkqhkiG9w0BAQsFADA6MRIwEAYDVQQDDAls
+b2NhbGhvc3QxJDAiBgkqhkiG9w0BCQEWFXNldmRva2ltb3ZAYXBhY2hlLm9yZzAe
+Fw0xNTA3MTQxMzAyNTNaFw0xODA2MjMxMzAyNTNaMDoxEjAQBgNVBAMMCWxvY2Fs
+aG9zdDEkMCIGCSqGSIb3DQEJARYVc2V2ZG9raW1vdkBhcGFjaGUub3JnMIGfMA0G
+CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP/zpJrdHqCj6lPpeFF6LQtzKef6UiyBBo
+rbuOtCCgW8KMJJciluBWk2126qLt9smBN4jBpSNU3pq0r9gBMUTd/LSe7aY4D5ED
+Pjp7XsypNVKeHaHbFi7KhfHy0LYxsWiNPmmHJv4dtYOp+pGK25rkXNfyJxxjgxN6
+wo34+MnZIQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFk9XEjcdyihws+fVmdGGUFo
+bVxI9YGH6agiNbU3WNF4B4VRzcPPW8z2mEo7eF9kgYmq/YzH4T8tgi/qkL/u8eZV
+Wmi9bg6RThLN6/hj3wVoOFKykbDQ05FFdhIJXN5UOjPmxYM97EKqg6J0W2HAb8SG
++UekPnmAo/2HTKsLykH8
+-----END CERTIFICATE-----

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/keys/test.key
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/keys/test.key b/modules/control-center-web/src/main/js/keys/test.key
new file mode 100644
index 0000000..1b395c0
--- /dev/null
+++ b/modules/control-center-web/src/main/js/keys/test.key
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,6798185330CE2EE2
+
+sOwkmD8rvjx11l09V26dJhLhl+SyPIhyeZ3TqHXrYCATKoXlzidT+uPu1jVYtrwr
+nBLA6TrIDYRrBNlEsqGZ0cSvWTIczzVW1xZKHEJo5q2vUT/W8u/Q1QQtS3P3GeKF
+dEzx496rpZqwwVw59GNbuIwyYoVvQf3iEXzfhplGmLPELYIplDFOLgNuXQyXSGx6
+rwKsCxXMLsDyrA6DCz0Odf08p2HvWk/s5Ne3DFcQlqRNtIrBVGD2O0/Fp8ZZ2I4E
+Yn2OIIWJff3HanOjLOWKdN8YAn5UleNmlEUdIHeS5qaQ68mabOxLkSef9qglV+sd
+FHTtUq0cG6t6nhxZBziexha6v1yl/xABAHHhNPOfak+HthWxRD4N9f1yFYAeTmkn
+4kwBWoSUe12XRf2pGNqhEUKN/KhDmWk85wI55i/Cu2XmNoiBFlS9BXrRYU8uVCJw
+KlxjKTDWl1opCyvxTDxJnMkt44ZT445LRePKVueGIIKSUIXNQypOE+C1I0CL0N2W
+Ts3m9nthquvLeMx92k7b8yW69BER5uac3SIlGCOJObQXsHgyk8wYiyd/zLKfjctG
+PXieaW81UKjp+GqWpvWPz3VqnKwoyUWeVOOTviurli6kYOrHuySTMqMb6hxJctw9
+grAQTT0UPiAKWcM7InLzZnRjco+v9QLLEokjVngXPba16K/CItFY16xuGlaFLW7Y
+XTc67AkL8b76HBZelMjmCsqjvSoULhuMFwTOvUMm/mSM8rMoi9asrJRLQHRMWCST
+/6RENPLzPlOMnNLBujpBbn8V3/aYzEZsHMI+6S3d27WYlTJIqpabSA==
+-----END RSA PRIVATE KEY-----

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/package.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/package.json b/modules/control-center-web/src/main/js/package.json
new file mode 100644
index 0000000..fdaca39
--- /dev/null
+++ b/modules/control-center-web/src/main/js/package.json
@@ -0,0 +1,53 @@
+{
+  "name": "ignite-web-console",
+  "version": "1.0.0",
+  "description": "Interactive Web console for configuration, executing SQL queries and monitoring of Apache Ignite Cluster",
+  "private": true,
+  "scripts": {
+    "start": "node ./bin/www"
+  },
+  "author": "",
+  "contributors": [
+    {
+      "name": "",
+      "email": ""
+    }
+  ],
+  "license": "Apache-2.0",
+  "keywords": "grid",
+  "homepage": "https://ignite.apache.org/",
+  "engines": {
+    "node": ">=0.12.4"
+  },
+  "dependencies": {
+    "archiver": "^0.15.1",
+    "async": "1.4.2",
+    "body-parser": "~1.14.1",
+    "bootstrap-sass": "^3.3.5",
+    "compression": "1.6.0",
+    "connect-mongo": "^0.8.1",
+    "cookie-parser": "~1.4.0",
+    "debug": "~2.2.0",
+    "express": "~4.13.3",
+    "express-force-ssl": "^0.3.0",
+    "express-session": "^1.11.1",
+    "jade": "~1.11.0",
+    "lodash": "3.10.1",
+    "mongoose": "^4.1.10",
+    "mongoose-deep-populate": "2.0.2",
+    "nconf": "^0.8.2",
+    "node-sass-middleware": "0.9.6",
+    "nodemailer": "1.8.0",
+    "passport": "^0.3.0",
+    "passport-local": "^1.0.0",
+    "passport-local-mongoose": "3.1.0",
+    "serve-favicon": "~2.3.0",
+    "ws": "~0.8.0"
+  },
+  "devDependencies": {
+    "morgan": "~1.6.1",
+    "supertest": "^1.1.0",
+    "mocha": "~2.3.3",
+    "should": "~7.1.0"
+  }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss
new file mode 100644
index 0000000..f7e29c4
--- /dev/null
+++ b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss
@@ -0,0 +1,67 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Core variables and mixins
+@import "bootstrap-variables";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/mixins";
+
+// Reset and dependencies
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/normalize";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/print";
+
+// Core CSS
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/type";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/code";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/grid";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tables";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/forms";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/buttons";
+
+// Components
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/component-animations";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/input-groups";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/navs";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/navbar";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/pagination";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/pager";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/labels";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/badges";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/jumbotron";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/thumbnails";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/alerts";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/progress-bars";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/media";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/list-group";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/panels";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/responsive-embed";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/wells";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/close";
+
+// Components w/ JavaScript
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/modals";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tooltip";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/popovers";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/carousel";
+
+// Utility classes
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/utilities";
+@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities";

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss
new file mode 100644
index 0000000..7b63443
--- /dev/null
+++ b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss
@@ -0,0 +1,890 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+$bootstrap-sass-asset-helper: false !default;
+//
+// Variables
+// --------------------------------------------------
+
+
+//== Colors
+//
+//## Gray and brand colors for use across Bootstrap.
+
+$gray-base:              #000 !default;
+$gray-darker:            lighten($gray-base, 13.5%) !default; // #222
+$gray-dark:              lighten($gray-base, 20%) !default;   // #333
+$gray:                   lighten($gray-base, 33.5%) !default; // #555
+$gray-light:             lighten($gray-base, 46.7%) !default; // #777
+$gray-lighter:           lighten($gray-base, 93.5%) !default; // #eee
+
+$brand-primary:         #ec1c24 !default;
+$brand-success:         #50af51 !default;
+$brand-info:            #248fb2 !default;
+$brand-warning:         #f0ad4e !default;
+$brand-danger:          #d9534f !default;
+
+
+//== Scaffolding
+//
+//## Settings for some of the most global styles.
+
+//** Background color for `<body>`.
+$body-bg:               #f9f9f9 !default;
+//** Global text color on `<body>`.
+$text-color:            $gray-dark !default;
+
+//** Global textual link color.
+$link-color:            $brand-primary !default;
+//** Link hover color set via `darken()` function.
+$link-hover-color:      darken($link-color, 15%) !default;
+//** Link hover decoration.
+$link-hover-decoration: underline !default;
+
+
+//== Typography
+//
+//## Font, line-height, and color for body text, headings, and more.
+
+$font-family-sans-serif:  Roboto Slab, sans-serif !default;
+$font-family-serif:       Roboto Slab, serif !default;
+//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
+$font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace !default;
+$font-family-base:        $font-family-sans-serif !default;
+
+$font-size-base:          14px !default;
+$font-size-large:         ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-small:         ceil(($font-size-base * 0.85)) !default; // ~12px
+
+$font-size-h1:            floor(($font-size-base * 2.6)) !default; // ~36px
+$font-size-h2:            floor(($font-size-base * 2.15)) !default; // ~30px
+$font-size-h3:            ceil(($font-size-base * 1.7)) !default; // ~24px
+$font-size-h4:            ceil(($font-size-base * 1.25)) !default; // ~18px
+$font-size-h5:            $font-size-base !default;
+$font-size-h6:            ceil(($font-size-base * 0.85)) !default; // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+$line-height-base:        1.428571429 !default; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+$line-height-computed:    floor(($font-size-base * $line-height-base)) !default; // ~20px
+
+//** By default, this inherits from the `<body>`.
+$headings-font-family:    inherit !default;
+$headings-font-weight:    500 !default;
+$headings-line-height:    1.1 !default;
+$headings-color:          inherit !default;
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+
+// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
+// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
+$icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/") !default;
+
+//** File name for all font files.
+$icon-font-name:          "glyphicons-halflings-regular" !default;
+//** Element ID within SVG icon file.
+$icon-font-svg-id:        "glyphicons_halflingsregular" !default;
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+$padding-base-vertical:     6px !default;
+$padding-base-horizontal:   12px !default;
+
+$padding-large-vertical:    10px !default;
+$padding-large-horizontal:  16px !default;
+
+$padding-small-vertical:    5px !default;
+$padding-small-horizontal:  10px !default;
+
+$padding-xs-vertical:       1px !default;
+$padding-xs-horizontal:     5px !default;
+
+$line-height-large:         1.3333333 !default; // extra decimals for Win 8.1 Chrome
+$line-height-small:         1.5 !default;
+
+$border-radius-base:        4px !default;
+$border-radius-large:       6px !default;
+$border-radius-small:       3px !default;
+
+//** Global color for active items (e.g., navs or dropdowns).
+$component-active-color:    $link-color !default;
+//** Global background color for active items (e.g., navs or dropdowns).
+$component-active-bg:       $brand-primary !default;
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+$caret-width-base:          4px !default;
+//** Carets increase slightly in size for larger components.
+$caret-width-large:         5px !default;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for `<th>`s and `<td>`s.
+$table-cell-padding:            8px !default;
+//** Padding for cells in `.table-condensed`.
+$table-condensed-cell-padding:  5px !default;
+
+//** Default background color used for all tables.
+$table-bg:                      transparent !default;
+//** Background color used for `.table-striped`.
+$table-bg-accent:               #f9f9f9 !default;
+//** Background color used for `.table-hover`.
+$table-bg-hover:                #f5f5f5 !default;
+$table-bg-active:               $table-bg-hover !default;
+
+//** Border color for table and cell borders.
+$table-border-color:            #ddd !default;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+$btn-font-weight:                normal !default;
+
+$btn-default-color:              #333 !default;
+$btn-default-bg:                 #fff !default;
+$btn-default-border:             #ccc !default;
+
+$btn-primary-color:              #fff !default;
+$btn-primary-bg:                 $brand-primary !default;
+$btn-primary-border:             darken($btn-primary-bg, 5%) !default;
+
+$btn-success-color:              #fff !default;
+$btn-success-bg:                 $brand-success !default;
+$btn-success-border:             darken($btn-success-bg, 5%) !default;
+
+$btn-info-color:                 #fff !default;
+$btn-info-bg:                    $brand-info !default;
+$btn-info-border:                darken($btn-info-bg, 5%) !default;
+
+$btn-warning-color:              #fff !default;
+$btn-warning-bg:                 $brand-warning !default;
+$btn-warning-border:             darken($btn-warning-bg, 5%) !default;
+
+$btn-danger-color:               #fff !default;
+$btn-danger-bg:                  $brand-danger !default;
+$btn-danger-border:              darken($btn-danger-bg, 5%) !default;
+
+$btn-link-disabled-color:        $gray-light !default;
+
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius-base:         $border-radius-base !default;
+$btn-border-radius-large:        $border-radius-large !default;
+$btn-border-radius-small:        $border-radius-small !default;
+
+
+//== Forms
+//
+//##
+
+//** `<input>` background color
+$input-bg:                       #fff !default;
+//** `<input disabled>` background color
+$input-bg-disabled:              $gray-lighter !default;
+
+//** Text color for `<input>`s
+$input-color:                    $gray !default;
+//** `<input>` border color
+$input-border:                   #ccc !default;
+
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
+$input-border-radius:            $border-radius-base !default;
+//** Large `.form-control` border radius
+$input-border-radius-large:      $border-radius-large !default;
+//** Small `.form-control` border radius
+$input-border-radius-small:      $border-radius-small !default;
+
+//** Border color for inputs on focus
+$input-border-focus:             #66afe9 !default;
+
+//** Placeholder text color
+$input-color-placeholder:        #999 !default;
+
+//** Default `.form-control` height
+$input-height-base:              ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
+//** Large `.form-control` height
+$input-height-large:             (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
+//** Small `.form-control` height
+$input-height-small:             (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
+
+//** `.form-group` margin
+$form-group-margin-bottom:       15px !default;
+
+$legend-color:                   $gray-dark !default;
+$legend-border-color:            #e5e5e5 !default;
+
+//** Background color for textual input addons
+$input-group-addon-bg:           $gray-lighter !default;
+//** Border color for textual input addons
+$input-group-addon-border-color: $input-border !default;
+
+//** Disabled cursor for form controls and buttons.
+$cursor-disabled:                not-allowed !default;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+$dropdown-bg:                    #fff !default;
+//** Dropdown menu `border-color`.
+$dropdown-border:                rgba(0,0,0,.15) !default;
+//** Dropdown menu `border-color` **for IE8**.
+$dropdown-fallback-border:       #ccc !default;
+//** Divider color for between dropdown items.
+$dropdown-divider-bg:            #e5e5e5 !default;
+
+//** Dropdown link text color.
+$dropdown-link-color:            #555 !default;
+//** Hover color for dropdown links.
+$dropdown-link-hover-color:      $link-hover-color !default;
+//** Hover background for dropdown links.
+$dropdown-link-hover-bg:         transparent !default;
+
+//** Active dropdown menu item text color.
+$dropdown-link-active-color:     $component-active-color !default;
+//** Active dropdown menu item background color.
+$dropdown-link-active-bg:        transparent !default;
+
+//** Disabled dropdown menu item background color.
+$dropdown-link-disabled-color:   $gray-light !default;
+
+//** Text color for headers within dropdown menus.
+$dropdown-header-color:          $gray-light !default;
+
+//** Deprecated `$dropdown-caret-color` as of v3.1.0
+$dropdown-caret-color:           #000 !default;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+$zindex-navbar:            1000 !default;
+$zindex-dropdown:          1002 !default;
+$zindex-popover:           1060 !default;
+$zindex-tooltip:           1070 !default;
+$zindex-navbar-fixed:      1030 !default;
+$zindex-modal-background:  1040 !default;
+$zindex-modal:             1050 !default;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+$screen-xs:                  480px !default;
+//** Deprecated `$screen-xs-min` as of v3.2.0
+$screen-xs-min:              $screen-xs !default;
+//** Deprecated `$screen-phone` as of v3.0.1
+$screen-phone:               $screen-xs-min !default;
+
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+$screen-sm:                  768px !default;
+$screen-sm-min:              $screen-sm !default;
+//** Deprecated `$screen-tablet` as of v3.0.1
+$screen-tablet:              $screen-sm-min !default;
+
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+$screen-md:                  992px !default;
+$screen-md-min:              $screen-md !default;
+//** Deprecated `$screen-desktop` as of v3.0.1
+$screen-desktop:             $screen-md-min !default;
+
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+$screen-lg:                  1200px !default;
+$screen-lg-min:              $screen-lg !default;
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+$screen-lg-desktop:          $screen-lg-min !default;
+
+// So media queries don't overlap when required, provide a maximum
+$screen-xs-max:              ($screen-sm-min - 1) !default;
+$screen-sm-max:              ($screen-md-min - 1) !default;
+$screen-md-max:              ($screen-lg-min - 1) !default;
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+$grid-columns:              12 !default;
+//** Padding between columns. Gets divided in half for the left and right.
+$grid-gutter-width:         0 !default;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+$grid-float-breakpoint:     $screen-sm-min !default;
+//** Point at which the navbar begins collapsing.
+$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+$container-tablet:             (720px + $grid-gutter-width) !default;
+//** For `$screen-sm-min` and up.
+$container-sm:                 $container-tablet !default;
+
+// Medium screen / desktop
+$container-desktop:            (940px + $grid-gutter-width) !default;
+//** For `$screen-md-min` and up.
+$container-md:                 $container-desktop !default;
+
+// Large screen / wide desktop
+$container-large-desktop:      (1140px + $grid-gutter-width) !default;
+//** For `$screen-lg-min` and up.
+$container-lg:                 $container-large-desktop !default;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+$navbar-height:                    50px !default;
+$navbar-margin-bottom:             $line-height-computed !default;
+$navbar-border-radius:             $border-radius-base !default;
+$navbar-padding-horizontal:        floor(($grid-gutter-width / 2)) !default;
+$navbar-padding-vertical:          (($navbar-height - $line-height-computed) / 2) !default;
+$navbar-collapse-max-height:       340px !default;
+
+$navbar-default-color:             #bbb !default;
+$navbar-default-bg:                #f8f8f8 !default;
+$navbar-default-border:            darken($navbar-default-bg, 6.5%) !default;
+
+// Navbar links
+$navbar-default-link-color:                #bbb !default;
+$navbar-default-link-hover-color:          $link-hover-color !default;
+$navbar-default-link-hover-bg:             transparent !default;
+$navbar-default-link-active-color:         $component-active-color !default;
+$navbar-default-link-active-bg:            darken($navbar-default-bg, 6.5%) !default;
+$navbar-default-link-disabled-color:       #ccc !default;
+$navbar-default-link-disabled-bg:          transparent !default;
+
+// Navbar brand label
+$navbar-default-brand-color:               $navbar-default-link-color !default;
+$navbar-default-brand-hover-color:         darken($link-color, 15%) !default;
+$navbar-default-brand-hover-bg:            transparent !default;
+
+// Navbar toggle
+$navbar-default-toggle-hover-bg:           #ddd !default;
+$navbar-default-toggle-icon-bar-bg:        #888 !default;
+$navbar-default-toggle-border-color:       #ddd !default;
+
+
+//=== Inverted navbar
+// Reset inverted navbar basics
+$navbar-inverse-color:                      lighten($gray-light, 15%) !default;
+$navbar-inverse-bg:                         #222 !default;
+$navbar-inverse-border:                     darken($navbar-inverse-bg, 10%) !default;
+
+// Inverted navbar links
+$navbar-inverse-link-color:                 lighten($gray-light, 15%) !default;
+$navbar-inverse-link-hover-color:           #fff !default;
+$navbar-inverse-link-hover-bg:              transparent !default;
+$navbar-inverse-link-active-color:          $navbar-inverse-link-hover-color !default;
+$navbar-inverse-link-active-bg:             darken($navbar-inverse-bg, 10%) !default;
+$navbar-inverse-link-disabled-color:        #444 !default;
+$navbar-inverse-link-disabled-bg:           transparent !default;
+
+// Inverted navbar brand label
+$navbar-inverse-brand-color:                $navbar-inverse-link-color !default;
+$navbar-inverse-brand-hover-color:          #fff !default;
+$navbar-inverse-brand-hover-bg:             transparent !default;
+
+// Inverted navbar toggle
+$navbar-inverse-toggle-hover-bg:            #333 !default;
+$navbar-inverse-toggle-icon-bar-bg:         #fff !default;
+$navbar-inverse-toggle-border-color:        #333 !default;
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+$nav-link-padding:                          10px 15px !default;
+$nav-link-hover-bg:                         transparent !default;
+
+$nav-disabled-link-color:                   $gray-light !default;
+$nav-disabled-link-hover-color:             $gray-light !default;
+
+//== Tabs
+$nav-tabs-border-color:                     #ddd !default;
+
+$nav-tabs-link-hover-border-color:          $gray-lighter !default;
+
+$nav-tabs-active-link-hover-bg:             $body-bg !default;
+$nav-tabs-active-link-hover-color:          $gray !default;
+$nav-tabs-active-link-hover-border-color:   #ddd !default;
+
+$nav-tabs-justified-link-border-color:            #ddd !default;
+$nav-tabs-justified-active-link-border-color:     $body-bg !default;
+
+//== Pills
+$nav-pills-border-radius:                   $border-radius-base !default;
+$nav-pills-active-link-hover-bg:            $component-active-bg !default;
+$nav-pills-active-link-hover-color:         $component-active-color !default;
+
+
+//== Pagination
+//
+//##
+
+$pagination-color:                     $link-color !default;
+$pagination-bg:                        #fff !default;
+$pagination-border:                    #ddd !default;
+
+$pagination-hover-color:               $link-hover-color !default;
+$pagination-hover-bg:                  $gray-lighter !default;
+$pagination-hover-border:              #ddd !default;
+
+$pagination-active-color:              #fff !default;
+$pagination-active-bg:                 $brand-primary !default;
+$pagination-active-border:             $brand-primary !default;
+
+$pagination-disabled-color:            $gray-light !default;
+$pagination-disabled-bg:               #fff !default;
+$pagination-disabled-border:           #ddd !default;
+
+
+//== Pager
+//
+//##
+
+$pager-bg:                             $pagination-bg !default;
+$pager-border:                         $pagination-border !default;
+$pager-border-radius:                  15px !default;
+
+$pager-hover-bg:                       $pagination-hover-bg !default;
+
+$pager-active-bg:                      $pagination-active-bg !default;
+$pager-active-color:                   $pagination-active-color !default;
+
+$pager-disabled-color:                 $pagination-disabled-color !default;
+
+
+//== Jumbotron
+//
+//##
+
+$jumbotron-padding:              30px !default;
+$jumbotron-color:                inherit !default;
+$jumbotron-bg:                   $gray-lighter !default;
+$jumbotron-heading-color:        inherit !default;
+$jumbotron-font-size:            ceil(($font-size-base * 1.5)) !default;
+$jumbotron-heading-font-size:    ceil(($font-size-base * 4.5)) !default;
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+$state-success-text:             #3c763d !default;
+$state-success-bg:               #dff0d8 !default;
+$state-success-border:           darken(adjust-hue($state-success-bg, -10), 5%) !default;
+
+$state-info-text:                #31708f !default;
+$state-info-bg:                  #d9edf7 !default;
+$state-info-border:              darken(adjust-hue($state-info-bg, -10), 7%) !default;
+
+$state-warning-text:             #8a6d3b !default;
+$state-warning-bg:               #fcf8e3 !default;
+$state-warning-border:           darken(adjust-hue($state-warning-bg, -10), 5%) !default;
+
+$state-danger-text:              #a94442 !default;
+$state-danger-bg:                #f2dede !default;
+$state-danger-border:            darken(adjust-hue($state-danger-bg, -10), 5%) !default;
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+$tooltip-max-width:           400px !default;
+//** Tooltip text color
+$tooltip-color:               #000 !default;
+//** Tooltip background color
+$tooltip-bg:                  #f5f5f5 !default;
+$tooltip-opacity:             1 !default;
+
+//** Tooltip arrow width
+$tooltip-arrow-width:         5px !default;
+//** Tooltip arrow color
+$tooltip-arrow-color:         #ccc !default;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+$popover-bg:                          #fff !default;
+//** Popover maximum width
+$popover-max-width:                   auto !default;
+//** Popover border color
+$popover-border-color:                rgba(0,0,0,.2) !default;
+//** Popover fallback border color
+$popover-fallback-border-color:       #ccc !default;
+
+//** Popover title background color
+$popover-title-bg:                    darken($popover-bg, 3%) !default;
+
+//** Popover arrow width
+$popover-arrow-width:                 10px !default;
+//** Popover arrow color
+$popover-arrow-color:                 $popover-bg !default;
+
+//** Popover outer arrow width
+$popover-arrow-outer-width:           ($popover-arrow-width + 1) !default;
+//** Popover outer arrow color
+$popover-arrow-outer-color:           fade_in($popover-border-color, 0.05) !default;
+//** Popover outer arrow fallback color
+$popover-arrow-outer-fallback-color:  darken($popover-fallback-border-color, 20%) !default;
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+$label-default-bg:            $gray-light !default;
+//** Primary label background color
+$label-primary-bg:            $brand-primary !default;
+//** Success label background color
+$label-success-bg:            $brand-success !default;
+//** Info label background color
+$label-info-bg:               $brand-info !default;
+//** Warning label background color
+$label-warning-bg:            $brand-warning !default;
+//** Danger label background color
+$label-danger-bg:             $brand-danger !default;
+
+//** Default label text color
+$label-color:                 #fff !default;
+//** Default text color of a linked label
+$label-link-hover-color:      #fff !default;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+$modal-inner-padding:         15px !default;
+
+//** Padding applied to the modal title
+$modal-title-padding:         15px !default;
+//** Modal title line-height
+$modal-title-line-height:     $line-height-base !default;
+
+//** Background color of modal content area
+$modal-content-bg:                             #fff !default;
+//** Modal content border color
+$modal-content-border-color:                   rgba(0,0,0,.2) !default;
+//** Modal content border color **for IE8**
+$modal-content-fallback-border-color:          #999 !default;
+
+//** Modal backdrop background color
+$modal-backdrop-bg:           #000 !default;
+//** Modal backdrop opacity
+$modal-backdrop-opacity:      .5 !default;
+//** Modal header border color
+$modal-header-border-color:   #e5e5e5 !default;
+//** Modal footer border color
+$modal-footer-border-color:   $modal-header-border-color !default;
+
+$modal-lg:                    900px !default;
+$modal-md:                    600px !default;
+$modal-sm:                    300px !default;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+$alert-padding:               15px !default;
+$alert-border-radius:         $border-radius-base !default;
+$alert-link-font-weight:      bold !default;
+
+$alert-success-bg:            $state-success-bg !default;
+$alert-success-text:          $state-success-text !default;
+$alert-success-border:        $state-success-border !default;
+
+$alert-info-bg:               $state-info-bg !default;
+$alert-info-text:             $state-info-text !default;
+$alert-info-border:           $state-info-border !default;
+
+$alert-warning-bg:            $state-warning-bg !default;
+$alert-warning-text:          $state-warning-text !default;
+$alert-warning-border:        $state-warning-border !default;
+
+$alert-danger-bg:             $state-danger-bg !default;
+$alert-danger-text:           $state-danger-text !default;
+$alert-danger-border:         $state-danger-border !default;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+$progress-bg:                 #f5f5f5 !default;
+//** Progress bar text color
+$progress-bar-color:          #fff !default;
+//** Variable for setting rounded corners on progress bar.
+$progress-border-radius:      $border-radius-base !default;
+
+//** Default progress bar color
+$progress-bar-bg:             $brand-primary !default;
+//** Success progress bar color
+$progress-bar-success-bg:     $brand-success !default;
+//** Warning progress bar color
+$progress-bar-warning-bg:     $brand-warning !default;
+//** Danger progress bar color
+$progress-bar-danger-bg:      $brand-danger !default;
+//** Info progress bar color
+$progress-bar-info-bg:        $brand-info !default;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+$list-group-bg:                 #fff !default;
+//** `.list-group-item` border color
+$list-group-border:             #ddd !default;
+//** List group border radius
+$list-group-border-radius:      $border-radius-base !default;
+
+//** Background color of single list items on hover
+$list-group-hover-bg:           #f5f5f5 !default;
+//** Text color of active list items
+$list-group-active-color:       $component-active-color !default;
+//** Background color of active list items
+$list-group-active-bg:          $component-active-bg !default;
+//** Border color of active list elements
+$list-group-active-border:      $list-group-active-bg !default;
+//** Text color for content within active list items
+$list-group-active-text-color:  lighten($list-group-active-bg, 40%) !default;
+
+//** Text color of disabled list items
+$list-group-disabled-color:      $gray-light !default;
+//** Background color of disabled list items
+$list-group-disabled-bg:         $gray-lighter !default;
+//** Text color for content within disabled list items
+$list-group-disabled-text-color: $list-group-disabled-color !default;
+
+$list-group-link-color:         #555 !default;
+$list-group-link-hover-color:   $list-group-link-color !default;
+$list-group-link-heading-color: #333 !default;
+
+
+//== Panels
+//
+//##
+
+$panel-bg:                    #fff !default;
+$panel-body-padding:          15px !default;
+$panel-heading-padding:       10px 15px !default;
+$panel-footer-padding:        $panel-heading-padding !default;
+$panel-border-radius:         $border-radius-base !default;
+
+//** Border color for elements within panels
+$panel-inner-border:          #ddd !default;
+$panel-footer-bg:             #f5f5f5 !default;
+
+$panel-default-text:          $gray-dark !default;
+$panel-default-border:        #ddd !default;
+$panel-default-heading-bg:    #f5f5f5 !default;
+
+$panel-primary-text:          #fff !default;
+$panel-primary-border:        $brand-primary !default;
+$panel-primary-heading-bg:    $brand-primary !default;
+
+$panel-success-text:          $state-success-text !default;
+$panel-success-border:        $state-success-border !default;
+$panel-success-heading-bg:    $state-success-bg !default;
+
+$panel-info-text:             $state-info-text !default;
+$panel-info-border:           $state-info-border !default;
+$panel-info-heading-bg:       $state-info-bg !default;
+
+$panel-warning-text:          $state-warning-text !default;
+$panel-warning-border:        $state-warning-border !default;
+$panel-warning-heading-bg:    $state-warning-bg !default;
+
+$panel-danger-text:           $state-danger-text !default;
+$panel-danger-border:         $state-danger-border !default;
+$panel-danger-heading-bg:     $state-danger-bg !default;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+$thumbnail-padding:           4px !default;
+//** Thumbnail background color
+$thumbnail-bg:                $body-bg !default;
+//** Thumbnail border color
+$thumbnail-border:            #ddd !default;
+//** Thumbnail border radius
+$thumbnail-border-radius:     $border-radius-base !default;
+
+//** Custom text color for thumbnail captions
+$thumbnail-caption-color:     $text-color !default;
+//** Padding around the thumbnail caption
+$thumbnail-caption-padding:   9px !default;
+
+
+//== Wells
+//
+//##
+
+$well-bg:                     #f5f5f5 !default;
+$well-border:                 darken($well-bg, 7%) !default;
+
+
+//== Badges
+//
+//##
+
+$badge-color:                 #fff !default;
+//** Linked badge text color on hover
+$badge-link-hover-color:      #fff !default;
+$badge-bg:                    $gray-light !default;
+
+//** Badge text color in active nav link
+$badge-active-color:          $link-color !default;
+//** Badge background color in active nav link
+$badge-active-bg:             #fff !default;
+
+$badge-font-weight:           bold !default;
+$badge-line-height:           1 !default;
+$badge-border-radius:         10px !default;
+
+
+//== Breadcrumbs
+//
+//##
+
+$breadcrumb-padding-vertical:   8px !default;
+$breadcrumb-padding-horizontal: 15px !default;
+//** Breadcrumb background color
+$breadcrumb-bg:                 #f5f5f5 !default;
+//** Breadcrumb text color
+$breadcrumb-color:              #ccc !default;
+//** Text color of current page in the breadcrumb
+$breadcrumb-active-color:       $gray-light !default;
+//** Textual separator for between breadcrumb elements
+$breadcrumb-separator:          "/" !default;
+
+
+//== Carousel
+//
+//##
+
+$carousel-text-shadow:                        none !default;
+
+$carousel-control-color:                      #333 !default;
+$carousel-control-width:                      25% !default;
+$carousel-control-opacity:                    1 !default;
+$carousel-control-font-size:                  20px !default;
+
+$carousel-indicator-active-bg:                #333 !default;
+$carousel-indicator-border-color:             #333 !default;
+
+$carousel-caption-color:                      #333 !default;
+
+
+//== Close
+//
+//##
+
+$close-font-weight:           bold !default;
+$close-color:                 #000 !default;
+$close-text-shadow:           0 1px 0 #fff !default;
+
+
+//== Code
+//
+//##
+
+$code-color:                  #c7254e !default;
+$code-bg:                     #f9f2f4 !default;
+
+$kbd-color:                   #fff !default;
+$kbd-bg:                      #333 !default;
+
+$pre-bg:                      #f5f5f5 !default;
+$pre-color:                   $gray-dark !default;
+$pre-border-color:            #ccc !default;
+$pre-scrollable-max-height:   340px !default;
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+$component-offset-horizontal: 180px !default;
+//** Text muted color
+$text-muted:                  $gray-light !default;
+//** Abbreviations and acronyms border color
+$abbr-border-color:           $gray-light !default;
+//** Headings small color
+$headings-small-color:        $gray-light !default;
+//** Blockquote small color
+$blockquote-small-color:      $gray-light !default;
+//** Blockquote font size
+$blockquote-font-size:        ($font-size-base * 1.25) !default;
+//** Blockquote border color
+$blockquote-border-color:     $gray-lighter !default;
+//** Page header border color
+$page-header-border-color:    $gray-lighter !default;
+//** Width of horizontal description list titles
+$dl-horizontal-offset:        $component-offset-horizontal !default;
+//** Horizontal line color.
+$hr-border:                   $gray-lighter !default;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/public/stylesheets/style.scss
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/stylesheets/style.scss b/modules/control-center-web/src/main/js/public/stylesheets/style.scss
new file mode 100644
index 0000000..ba8ff46
--- /dev/null
+++ b/modules/control-center-web/src/main/js/public/stylesheets/style.scss
@@ -0,0 +1,1838 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+@import "bootstrap-custom";
+
+$logo-path: "https://www.filepicker.io/api/file/QagunjDGRFul2JgNCAli";
+$input-height: 28px;
+$ignite-block-callout-left-background: #f4f8fa;
+$ignite-block-callout-right-background: #f3f8f3;
+$ignite-block-callout-gradient-to: #ffffff;
+$ignite-placeholder-color: #999999;
+$ignite-border-color: #ddd;
+$ignite-darck-border-color: #aaa;
+$ignite-border-bottom-color: $brand-primary;
+$ignite-background-color: #fff;
+$ignite-header-color: #555;
+$ignite-invalid-color: $brand-primary;
+
+hr {
+    margin: 20px 0;
+}
+
+a.active {
+    font-weight: bold;
+    font-size: 1.1em;
+}
+
+.navbar-default .navbar-brand, .navbar-default .navbar-brand:hover {
+    position: absolute;
+    width: 100%;
+    left: 0;
+    text-align: center;
+}
+
+.modal.center .modal-dialog {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    -webkit-transform: translateX(-50%) translateY(-50%);
+    transform: translateX(-50%) translateY(-50%);
+}
+
+.border-left {
+    box-shadow: 1px 0 0 0 $gray-lighter inset;
+}
+
+.border-right {
+    box-shadow: 1px 0 0 0 $gray-lighter;
+}
+
+.theme-line header {
+    background-color: $ignite-background-color;
+}
+
+.theme-line .docs-header h1 {
+    color: $ignite-header-color;
+    margin-top: 0;
+    font-size: 22px;
+}
+
+ul.navbar-nav, .sidebar-nav {
+    li > a.active:not(.dropdown-toggle) {
+        cursor: default;
+        pointer-events: none;
+    }
+}
+
+.theme-line .sidebar-nav {
+    padding-bottom: 30px;
+
+    ul {
+        padding: 0;
+        list-style: none;
+        font-size: $font-size-base;
+        margin: 3px 0 0;
+
+        li {
+            line-height: $input-height;
+
+            span.fa-stack {
+                margin-right: 5px;
+                font-size: 12px;
+                height: 26px;
+            }
+
+            a {
+                font-size: 18px;
+                color: $ignite-header-color;
+                position: relative;
+                white-space: nowrap;
+                overflow: hidden;
+                -o-text-overflow: ellipsis;
+                text-overflow: ellipsis;
+            }
+
+            a:hover {
+                color: $link-hover-color;
+            }
+
+            a.active {
+                color: $link-color;
+            }
+        }
+    }
+}
+
+.theme-line .sidebar-nav ul li a:hover {
+    text-decoration: none;
+}
+
+.theme-line .select {
+    li a.active {
+        color: $dropdown-link-active-color;
+    }
+
+    li a:hover {
+        color: $dropdown-link-hover-color;
+    }
+}
+
+.theme-line .select,
+.theme-line .typeahead {
+    .active {
+        font-size: 1em;
+        background-color: $gray-lighter;
+    }
+}
+
+.theme-line button.form-control.placeholder {
+    color: $input-color-placeholder;
+}
+
+.theme-line ul.dropdown-menu {
+    min-width: 120px;
+    max-width: 500px;
+    max-height: 20em;
+    overflow: auto;
+    overflow-x: hidden;
+
+    li > a {
+        display: block;
+
+        //cursor: default;
+        padding: 3px 10px;
+
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+
+        i {
+            float: right;
+            color: $brand-primary;
+            background-color: transparent;
+            line-height: $line-height-base;
+            margin-left: 5px;
+            margin-right: 0;
+        }
+    }
+}
+
+.theme-line .border-left .sidebar-nav {
+    padding-left: 15px;
+}
+
+.theme-line .suggest {
+    padding: 5px;
+    display: inline-block;
+    font-size: 12px;
+}
+
+.header {
+    padding: 15px;
+}
+
+.header h1.navbar-brand {
+    height: 40px;
+    width: 200px;
+    padding: 0;
+    margin: 5px 15px 0 0;
+}
+
+.header h1.navbar-brand a {
+    text-indent: -99999px;
+    background: no-repeat center center;
+    display: block;
+    width: 100%;
+    height: 100%;
+    background-size: contain;
+}
+
+.header .nav.navbar-nav.pull-right {
+    position: relative;
+    right: -30px;
+}
+
+.header .nav.navbar-nav .not-link {
+    padding: 15px;
+    display: inline-block;
+}
+
+.theme-line header {
+    border-bottom: 8px solid;
+}
+
+.theme-line header p {
+    color: $ignite-header-color;
+}
+
+.theme-line header {
+    border-bottom-color: $ignite-border-bottom-color;
+}
+
+.nav > li {
+    > a {
+        color: $navbar-default-link-color
+    }
+    > a:hover {
+        color: $link-hover-color
+    }
+    > a.active {
+        color: $link-color
+    }
+}
+
+.theme-line header .navbar-nav a {
+    font-size: 18px;
+}
+
+.theme-line .section-right {
+    padding-left: 30px;
+}
+
+.body-overlap .main-content {
+    margin-top: 30px;
+}
+
+.body-box .main-content,
+.body-overlap .main-content {
+    padding: 30px;
+    box-shadow: 0 0 0 1px $ignite-border-color;
+    background-color: $ignite-background-color;
+}
+
+body {
+    font-weight: 400;
+}
+
+h1, h2, h3, h4, h5, h6 {
+    font-weight: 700;
+    margin-bottom: 10px;
+}
+
+.header h1.navbar-brand a {
+    background-image: url("#{$logo-path}");
+}
+
+.header h1.navbar-brand {
+    width: 96px;
+}
+
+.container-footer {
+    margin-top: 20px;
+
+    p {
+        font-size: 12px;
+    }
+}
+
+/* Modal */
+.modal {
+    display: block;
+    overflow: hidden;
+}
+
+.modal .close {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    float: none;
+}
+
+// Close icon
+.modal-header .close {
+    margin-right: -2px;
+}
+
+.modal .modal-dialog {
+    width: 650px;
+}
+
+.modal .modal-content {
+    border-radius: 0;
+    background-color: $gray-lighter;
+
+    .input-tip {
+        padding-top: 1px;
+    }
+}
+
+.modal .modal-content .modal-header {
+    background-color: $ignite-background-color;
+    text-align: center;
+    color: $ignite-header-color;
+    padding: 15px 25px 15px 15px;
+}
+
+.modal .modal-content .modal-header h4 {
+    font-size: 22px;
+}
+
+.modal .modal-content .modal-footer {
+    margin-top: 0;
+}
+
+.modal-footer {
+    .checkbox {
+        margin: 0;
+
+        label {
+            vertical-align: middle;
+        }
+    }
+}
+
+.login-header {
+    margin-top: 0;
+    margin-bottom: 20px;
+    font-size: 2em;
+}
+
+.login-footer {
+    @extend .modal-footer;
+
+    padding-left: 0;
+    padding-right: 0;
+
+    .btn {
+        margin-right: 0;
+    }
+}
+
+.modal-body {
+    margin-left: 20px;
+    margin-right: 20px;
+}
+
+.greedy {
+    min-height: 100%;
+    height: #{"calc(100vh - 290px)"};
+}
+
+@media (min-width: 768px) {
+    .navbar-nav > li > a {
+        padding-top: 18px;
+        padding-bottom: 10px;
+    }
+}
+
+.details-row {
+    padding: 0 10px;
+}
+
+.details-row, .settings-row {
+    display: block;
+    margin: 10px 0;
+
+    [class*="col-"] {
+        display: inline-block;
+        vertical-align: middle;
+        float: none;
+    }
+
+    input[type="checkbox"] {
+        line-height: 20px;
+        margin-right: 5px;
+    }
+
+    .checkbox label {
+        line-height: 20px;
+        vertical-align: middle;
+    }
+}
+
+button, a.btn {
+    margin-right: 5px;
+}
+
+.btn {
+    padding: 3px 6px;
+
+    :focus {
+        //outline: none;
+        //border: 1px solid $btn-default-border;
+    }
+}
+
+.btn-group {
+    margin-right: 5px;
+
+    > button, a.btn {
+        margin-right: 0;
+    }
+
+    button.btn + .btn {
+        margin-left: 0;
+    }
+
+    > button.btn-primary:not(:last-child) {
+        border-right-color: black;
+    }
+}
+
+h1,
+h2,
+h3 {
+    user-select: none;
+    font-weight: normal;
+    /* Makes the vertical size of the text the same for all fonts. */
+    line-height: 1;
+}
+
+h3 {
+    font-size: 1.2em;
+    margin-top: 0;
+    margin-bottom: 1.5em;
+}
+
+.base-control {
+    text-align: left;
+    padding: 3px 3px;
+    height: $input-height;
+}
+
+.sql-name-input {
+    @extend .form-control;
+
+    width: auto;
+}
+
+.form-control {
+    @extend .base-control;
+
+    display: inline-block;
+
+    button {
+        text-align: left;
+    }
+}
+
+button.form-control {
+    display: block;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.theme-line .notebook-header {
+    border-color: $gray-lighter;
+
+    line-height: 44px;
+    height: 44px;
+
+    h1 {
+        padding: 0;
+        line-height: 44px;
+        height: 44px;
+        margin: 0;
+    }
+
+    .btn-group {
+        margin: -5px 0 0 10px;
+    }
+
+    input {
+        font-size: 22px;
+        line-height: 44px;
+        height: 44px;
+    }
+}
+
+.theme-line .paragraphs {
+    .panel-group .panel + .panel {
+        margin-top: 30px;
+    }
+
+    .btn-group {
+        margin-right: 0;
+    }
+
+    .sql-editor {
+        padding: 5px 0;
+
+        .ace_cursor {
+            opacity: 1;
+        }
+
+        .ace_hidden-cursors {
+            opacity: 1;
+        }
+
+        .ace_gutter-cell, .ace_folding-enabled > .ace_gutter-cell {
+            padding-right: 5px;
+        }
+    }
+
+    .sql-table-total {
+        padding: 0 10px;
+
+        label, b {
+            display: inline-block;
+
+            padding-top: 5px;
+
+            height: 27px;
+        }
+
+        margin-bottom: 10px;
+    }
+
+    .sql-table {
+        height: 400px;
+    }
+
+    table thead {
+        background-color: white;
+    }
+
+    .wrong-caches-filter {
+        text-align: center;
+        color: $ignite-placeholder-color;
+        height: 65px;
+        line-height: 65px;
+    }
+
+    .empty-caches {
+        text-align: center;
+        color: $ignite-placeholder-color;
+        height: 100px;
+        line-height: 100px;
+    }
+
+    .sql-empty-result {
+        margin-bottom: 10px;
+        text-align: center;
+        color: $ignite-placeholder-color;
+    }
+
+    .sql-error-result {
+        margin-bottom: 10px;
+        text-align: center;
+        color: $brand-danger;
+    }
+}
+
+.theme-line .panel-heading {
+    padding: 5px 10px;
+    margin: 0;
+    cursor: pointer;
+    font-size: $font-size-large;
+    line-height: 27px;
+
+    a { color: black; }
+
+    .btn-group { margin-left: 10px; }
+
+    .fa-undo {
+        font-size: 16px;
+
+        padding: 5px 7px;
+    }
+
+    .fa-undo:hover {
+        padding: 4px 6px;
+
+        border-radius: 5px;
+        border: thin dotted $ignite-darck-border-color;
+    }
+}
+
+.theme-line .panel-heading:hover {
+    text-decoration: underline;
+}
+
+.theme-line .panel-body {
+    padding: 10px 20px;
+}
+
+.theme-line .main-content a.customize {
+    margin-left: 5px;
+}
+
+.theme-line .panel-collapse {
+    margin: 0;
+}
+
+.theme-line table.links {
+    table-layout: fixed;
+    border-collapse: collapse;
+
+    width: 100%;
+
+    label.placeholder {
+        font-size: 0.8em;
+        line-height: 0.8em;
+        color: $ignite-placeholder-color;
+        width: 100%;
+    }
+
+    input[type="text"] {
+        font-weight: normal;
+    }
+
+    input[type="radio"] {
+        margin-left: 1px;
+        margin-right: 5px;
+    }
+
+    tbody {
+        border-left: 10px solid transparent;
+    }
+
+    tbody td:first-child {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+
+    tfoot > tr > td {
+        padding: 0;
+
+        .pagination {
+            margin: 10px 0;
+
+            > .active > a {
+                border-color: $table-border-color;
+                background-color: $gray-lighter;
+            }
+        }
+    }
+}
+
+.theme-line table.links-edit {
+    @extend table.links;
+
+    margin-top: 0;
+    margin-bottom: 5px;
+
+    label {
+        line-height: $input-height;
+    }
+
+    td {
+        padding-left: 0;
+    }
+}
+
+.theme-line table.links-edit-sub {
+    @extend table.links-edit;
+
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+.theme-line table.links-edit-details {
+    @extend table.links;
+
+    margin-bottom: 10px;
+
+    label {
+        line-height: $input-height;
+        color: $ignite-header-color;
+    }
+
+    td {
+        padding: 0;
+
+        .input-tip {
+            padding: 0;
+        }
+    }
+}
+
+.theme-line table.admin {
+    tr:hover {
+        cursor: default;
+    }
+
+    thead > tr th.header {
+        padding: 0 0 10px;
+
+        div {
+            padding: 0;
+        }
+
+        input[type="text"] {
+            font-weight: normal;
+        }
+    }
+
+    margin-bottom: 10px;
+
+    label {
+        line-height: $input-height;
+        color: $ignite-header-color;
+    }
+
+    thead > tr th, td {
+        padding: 10px 10px;
+
+        .input-tip {
+            padding: 0;
+        }
+    }
+
+    tfoot > tr > td {
+        padding: 0;
+
+        .pagination {
+            margin: 10px 0;
+
+            > .active > a {
+                border-color: $table-border-color;
+                background-color: $gray-lighter;
+            }
+        }
+    }
+}
+
+.metadata-package-name {
+    margin: 10px 0;
+    overflow: hidden;
+
+    label {
+        line-height: $input-height;
+        float: left;
+    }
+
+    input {
+        width: 100%
+    }
+
+    span {
+        display: block;
+        overflow: hidden;
+        padding-left: 10px;
+    }
+}
+
+.metadata-content {
+    margin: 15px 30px;
+}
+
+.scrollable-y {
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+.theme-line table.metadata {
+    margin-bottom: 10px;
+
+    tr:hover {
+        cursor: default;
+    }
+
+    thead > tr {
+        label {
+            font-weight: bold;
+        }
+
+        input[type="checkbox"] {
+            cursor: pointer;
+        }
+    }
+
+    thead > tr th.header {
+        padding: 0 0 10px;
+
+        .pull-right {
+            padding: 0;
+        }
+
+        input[type="checkbox"] {
+            cursor: pointer;
+        }
+
+        input[type="text"] {
+            font-weight: normal;
+        }
+    }
+
+    tbody > tr > td {
+        padding: 0;
+    }
+}
+
+.table-modal-striped {
+    width: 100%;
+
+    > tbody > tr {
+        border-bottom: 2px solid $ignite-border-color;
+
+        input[type="checkbox"] {
+            cursor: pointer;
+        }
+    }
+
+    > tbody > tr > td {
+        padding: 8px !important;
+    }
+}
+
+.theme-line table.sql-results {
+    margin: 0;
+
+    td {
+        padding: 3px 6px;
+    }
+
+    > thead > tr > td {
+        padding: 3px 0;
+    }
+
+    thead > tr > th {
+        padding: 3px 6px;
+
+        line-height: $input-height;
+    }
+
+    tfoot > tr > td {
+        padding: 0;
+
+        .pagination {
+            margin: 10px 0 0 0;
+
+            > .active > a {
+                border-color: $table-border-color;
+                background-color: $gray-lighter;
+            }
+        }
+    }
+}
+
+.affix {
+    z-index: 910;
+    background-color: white;
+
+    hr {
+        margin: 0;
+    }
+}
+
+.affix.padding-top-dflt {
+    hr {
+        margin-top: 10px;
+    }
+}
+
+.panel-details {
+    margin-top: 10px;
+
+    padding: 0;
+
+    border-radius: 5px;
+    border: thin dotted $ignite-border-color;
+}
+
+.table-details {
+    border-radius: 5px;
+    border: thin dotted $table-border-color;
+
+    margin-top: 10px;
+
+    padding-left: 10px;
+    padding-right: 5px;
+}
+
+.group-section {
+    margin-top: 30px;
+}
+
+.group {
+    border-radius: 5px;
+    border: thin dotted $ignite-border-color;
+
+    text-align: left;
+}
+
+.group-legend {
+    margin: -10px 5px 0 10px;
+    overflow: visible;
+    position: relative;
+
+    label {
+        padding: 0 5px;
+        background: white;
+    }
+}
+
+.group-legend-btn {
+    background: white;
+    float: right;
+    line-height: 20px;
+    padding: 0 5px 0 5px;
+}
+
+.group-content {
+    margin: 0 10px 10px 10px;
+}
+
+.group-content-empty {
+    color: $input-color-placeholder;
+
+    padding: 10px 0;
+    position: relative;
+
+    text-align: center;
+}
+
+.tooltip > .tooltip-inner {
+    text-align: left;
+    border: solid 1px #ccc;
+}
+
+.popover-footer {
+    margin: 0; // reset heading margin
+    padding: 8px 14px;
+    font-size: $font-size-base;
+    color: $input-color-placeholder;
+    background-color: $popover-title-bg;
+    border-top: 1px solid darken($popover-title-bg, 5%);
+    border-radius: 0 0 ($border-radius-large - 1) ($border-radius-large - 1);
+}
+
+.popover-content {
+    padding: 5px;
+}
+
+.popover:focus {
+    outline: none;
+    border: 1px solid $btn-default-border;
+}
+
+.theme-line .popover.settings {
+    .close {
+        position: absolute;
+        top: 5px;
+        right: 5px;
+    }
+}
+
+.theme-line .popover.cache-metadata {
+    @extend .popover.settings;
+
+    min-width: 305px;
+
+    .popover-title {
+        color: black;
+
+        line-height: $line-height-base;
+
+        padding: 8px 20px 8px 10px;
+
+        white-space: nowrap;
+        overflow: hidden;
+        -o-text-overflow: ellipsis;
+        text-overflow: ellipsis;
+    }
+
+    > .popover-content {
+        overflow: scroll;
+
+        min-height: 400px;
+        max-height: 400px;
+
+        min-width: 300px;
+        max-width: 300px;
+
+        .content-empty {
+            display: block;
+            text-align: center;
+            line-height: 380px;
+
+            color: $input-color-placeholder;
+        }
+    }
+
+    label.clickable {
+        cursor: pointer;
+    }
+}
+
+.theme-line .popover.validation-error {
+    max-width: 400px;
+    color: $brand-primary;
+    background: white;
+    border: 1px solid $brand-primary;
+
+    &.right > .arrow {
+        border-right-color: $brand-primary;
+    }
+
+    .close {
+        vertical-align: middle;
+    }
+}
+
+label {
+    font-weight: normal;
+    margin-bottom: 0;
+}
+
+.form-horizontal .checkbox {
+    padding-top: 0;
+}
+
+.input-tip {
+    display: block;
+    overflow: hidden;
+}
+
+.labelHeader {
+    font-weight: bold;
+}
+
+.labelField {
+    float: left;
+    margin-right: 5px;
+}
+
+.labelFormField {
+    float: left;
+    line-height: $input-height;
+}
+
+.labelLogin {
+    margin-right: 10px;
+}
+
+.form-horizontal .form-group {
+    margin: 0;
+}
+
+.form-horizontal .has-feedback .form-control-feedback {
+    right: 0;
+}
+
+.tipField {
+    float: right;
+    line-height: $input-height;
+    margin-left: 5px;
+}
+
+.tipLabel {
+    font-size: $font-size-base;
+    margin-left: 5px;
+}
+
+.fieldSep {
+    float: right;
+    line-height: $input-height;
+    margin: 0 5px;
+}
+
+.fieldButton {
+    float: right;
+    margin-left: 5px;
+    margin-right: 0;
+}
+
+.fa {
+    cursor: pointer;
+}
+
+.fa-remove {
+    color: $brand-primary;
+}
+
+.fa-chevron-circle-down {
+    color: $brand-primary;
+    margin-right: 5px;
+}
+
+.fa-chevron-circle-up {
+    color: $brand-primary;
+    margin-right: 5px;
+}
+
+.fa-chevron-circle-left {
+    color: $brand-primary;
+    margin-right: 5px;
+
+    font-size: 15px;
+
+    line-height: $line-height-base;
+}
+
+.fa-chevron-circle-right {
+    color: $brand-primary;
+    margin-right: 5px;
+
+    font-size: 15px;
+
+    line-height: $line-height-base;
+}
+
+label.required:after {
+    color: $brand-primary;
+    content: ' *';
+    display: inline;
+}
+
+.blank {
+    visibility: hidden;
+}
+
+.alert {
+    outline: 0
+}
+
+.alert.bottom, .alert.bottom-left, .alert.bottom-right, .alert.top,
+.alert.top-left, .alert.top-right {
+    position: fixed;
+    z-index: 1050;
+    margin: 20px
+}
+
+.alert.top, .alert.top-left, .alert.top-right {
+    top: 50px
+}
+
+.alert.top {
+    right: 0;
+    left: 0
+}
+
+.alert.top-right {
+    right: 0
+}
+
+.alert.top-right .close {
+    padding-left: 10px
+}
+
+.alert.top-left {
+    left: 0
+}
+
+.alert.top-left .close {
+    padding-right: 10px
+}
+
+.alert.bottom, .alert.bottom-left, .alert.bottom-right {
+    bottom: 0
+}
+
+.alert.bottom {
+    right: 0;
+    left: 0
+}
+
+.alert.bottom-right {
+    right: 0
+}
+
+.alert.bottom-right .close {
+    padding-left: 10px
+}
+
+.alert.bottom-left {
+    left: 0
+}
+
+.alert.bottom-left .close {
+    padding-right: 10px
+}
+
+.summary-tabs {
+    margin-top: 0.65em;
+}
+
+.summary-tab {
+    img {
+        margin-right: 5px;
+        height: 16px;
+        width: 16px;
+        float: left;
+    }
+}
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+    -webkit-appearance: none;
+    margin: 0;
+}
+
+input[type="number"] {
+    -moz-appearance: textfield;
+}
+
+input.ng-dirty.ng-invalid, button.ng-dirty.ng-invalid {
+    border-color: $ignite-invalid-color;
+
+    :focus {
+        border-color: $ignite-invalid-color;
+    }
+}
+
+.form-control-feedback {
+    display: inline-block;
+    color: $brand-primary;
+    right: 18px;
+    line-height: $input-height;
+    pointer-events: initial;
+}
+
+.theme-line .nav-tabs > li > a {
+    padding: 5px 5px;
+    color: $ignite-header-color;
+}
+
+.viewedUser {
+    position: absolute;
+    width: 100%;
+    left: 0;
+
+    text-align: center;
+
+    margin-top: -15px;
+}
+
+a {
+    cursor: pointer;
+}
+
+.st-sort-ascent:after {
+    content: '\25B2';
+}
+
+.st-sort-descent:after {
+    content: '\25BC';
+}
+
+.panel {
+    margin-bottom: 0;
+}
+
+.panel-group {
+    margin-bottom: 0;
+}
+
+.panel-group .panel + .panel {
+    margin-top: 20px;
+}
+
+.section {
+    margin-top: 20px;
+}
+
+.section-top {
+    width: 100%;
+    margin-top: 10px;
+    margin-bottom: 20px;
+}
+
+.advanced-options {
+    @extend .section;
+    margin-bottom: 20px;
+
+    i {
+        font-size: 16px;
+    }
+}
+
+.modal-advanced-options {
+    @extend .advanced-options;
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.margin-left-dflt {
+    margin-left: 10px;
+}
+
+.margin-top-dflt {
+    margin-top: 10px;
+}
+
+.margin-bottom-dflt {
+    margin-bottom: 10px;
+}
+
+.margin-dflt {
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.padding-top-dflt {
+    padding-top: 10px;
+}
+
+.padding-bottom-dflt {
+    padding-bottom: 10px;
+}
+
+.padding-dflt {
+    padding-top: 10px;
+    padding-bottom: 10px;
+}
+
+.agent-download {
+    padding: 10px 10px 10px 20px;
+}
+
+.block-callout-parent {
+    margin-top: 10px;
+
+    table {
+        width: 100%
+    }
+}
+
+.block-callout {
+    border-left: 5px solid;
+    vertical-align: top;
+
+    i {
+        padding: 10px 5px 0 10px;
+    }
+
+    label {
+        font-weight: bold;
+    }
+
+    ul {
+        padding: 5px 0 10px 20px;
+        margin: 0 0 0 10px;
+    }
+}
+
+.block-callout-left {
+    @extend .block-callout;
+
+    background: linear-gradient(to right, $ignite-block-callout-left-background, $ignite-block-callout-gradient-to);
+    border-color: $brand-info;
+
+    i {
+        color: $brand-info;
+    }
+
+    label {
+        color: $brand-info;
+    }
+}
+
+.block-callout-right {
+    @extend .block-callout;
+
+    background: linear-gradient(to right, $ignite-block-callout-right-background, $ignite-block-callout-gradient-to);
+    border-color: $brand-success;
+
+    i {
+        color: $brand-success;
+    }
+
+    label {
+        color: $brand-success;
+    }
+}
+
+.ace_content {
+    padding-left: 5px;
+}
+
+.ace_hidden-cursors {
+    opacity: 0;
+}
+
+.ace_cursor {
+    opacity: 0;
+}
+
+.ace_editor {
+    margin: 7px 5px 7px 0;
+
+    .ace_gutter {
+        background: transparent !important;
+        border: 1px $ignite-border-color;
+        border-right-style: solid;
+    }
+
+    .ace_gutter-cell, .ace_folding-enabled > .ace_gutter-cell {
+        padding-left: 0.65em;
+    }
+}
+
+.preview-highlight-1 {
+    position: absolute;
+    background-color: #f7faff;
+    z-index: 20;
+}
+
+.preview-highlight-2 {
+    position: absolute;
+    background-color: #f0f6ff;
+    z-index: 21;
+}
+
+.preview-highlight-3 {
+    position: absolute;
+    background-color: #e8f2ff;
+    z-index: 22;
+}
+
+.preview-highlight-4 {
+    position: absolute;
+    background-color: #e1eeff;
+    z-index: 23;
+}
+
+.preview-highlight-5 {
+    position: absolute;
+    background-color: #DAEAFF;
+    z-index: 24;
+}
+
+.preview-highlight-6 {
+    position: absolute;
+    background-color: #D2E5FF;
+    z-index: 25;
+}
+
+.preview-highlight-7 {
+    position: absolute;
+    background-color: #CBE1FF;
+    z-index: 26;
+}
+
+.preview-highlight-8 {
+    position: absolute;
+    background-color: #C3DDFF;
+    z-index: 27;
+}
+
+.preview-highlight-9 {
+    position: absolute;
+    background-color: #BCD9FF;
+    z-index: 28;
+}
+
+.preview-highlight-10 {
+    position: absolute;
+    background-color: #B5D5FF;
+    z-index: 29;
+}
+
+.preview-panel {
+    min-height: 28px;
+
+    margin: 10px 0 10px 20px;
+
+    border-radius: 5px;
+    border: thin dotted $ignite-border-color;
+
+    padding: 0;
+}
+
+.preview-legend {
+    top: -1px;
+    right: 15px;
+    margin-right: 10px;
+    position: absolute;
+    z-index: 900;
+
+    a {
+        background-color: white;
+        margin-left: 5px;
+        font-size: 0.9em;
+    }
+
+    .inactive {
+        color: $input-color-placeholder;
+    }
+}
+
+.preview-content-empty {
+    color: $input-color-placeholder;
+    display: table;
+    width: 100%;
+    height: 26px;
+
+    label {
+        display: table-cell;
+        text-align: center;
+        vertical-align: middle;
+    }
+}
+
+.chart-settings-link {
+    font-size: $font-size-base;
+    color: $link-color !important;
+    margin-left: 10px;
+}
+
+.chart-settings {
+    margin: 10px 5px 5px 5px !important;
+}
+
+.chart-settings-columns-list {
+    border: 1px solid $ignite-border-color;
+    list-style: none;
+    margin-bottom: 10px;
+    min-height: 30px;
+    max-height: 200px;
+    padding: 5px;
+
+    overflow: auto;
+
+    li {
+        float: left;
+    }
+
+    li:nth-child(even) {
+        margin-right: 0;
+    }
+
+    .fa-close {
+        margin-left: 10px;
+    }
+}
+
+.btn-chart-column {
+    border-radius: 3px;
+    font-size: 12px;
+    margin: 3px 3px;
+    padding: 1px 5px;
+    line-height: 1.5;
+    cursor: default;
+}
+
+.btn-chart-column-movable {
+    @extend .btn-chart-column;
+    cursor: move;
+}
+
+.page-loading-overlay {
+    background-color: rgba(255, 255, 255, 1) !important;
+}
+
+.dw-loading {
+    min-height: 200px;
+}
+
+.dw-loading > .dw-loading-body > .dw-loading-text {
+    left: -50%;
+}
+
+.panel-tip-container {
+    display: inline-block;
+    margin: -3px -3px -3px -3px;
+    padding: 3px 3px 3px 3px;
+}
+
+button.dropdown-toggle {
+    margin-right: 5px;
+}
+
+button.select-toggle {
+    padding-right: 15px;
+}
+
+button.select-toggle::after {
+    vertical-align: middle;
+    content: "";
+    border-top: 0.3em solid;
+    border-right: 0.3em solid transparent;
+    border-left: 0.3em solid transparent;
+    position: absolute;
+    right: 5px;
+    top: 50%;
+}
+
+button.btn.select-toggle::after {
+    right: 15px;
+}
+
+.input-tip > button.select-toggle::after {
+    right: 20px;
+}
+
+// Prevent scroll bars from being hidden for OS X.
+::-webkit-scrollbar {
+    -webkit-appearance: none;
+}
+
+::-webkit-scrollbar:vertical {
+    width: 10px;
+}
+
+::-webkit-scrollbar:horizontal {
+    height: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+    border-radius: 8px;
+    border: 2px solid white; /* should match background, can't be transparent */
+    background-color: rgba(0, 0, 0, .5);
+}
+
+::-webkit-scrollbar-track {
+    background-color: white;
+    border-radius: 8px;
+}
+
+treecontrol.tree-classic {
+    > ul > li {
+        padding: 0;
+    }
+
+    li.tree-expanded i.tree-branch-head, li.tree-collapsed i.tree-branch-head {
+        background: none;
+        padding-left: 0;
+    }
+
+    li .tree-selected {
+        background-color: white;
+        font-weight: normal;
+    }
+}
+
+.docs-content {
+    .affix {
+        border-bottom: 1px solid $gray-lighter;
+    }
+}
+
+.ag-bootstrap {
+    :focus {
+        outline: none;
+    }
+
+    .ag-root {
+    }
+
+    .ag-cell {
+        padding: 3px 6px;
+        border-right: 1px solid #ccc;
+    }
+
+    .ag-pinned-header {
+        border-bottom: 2px solid $table-border-color;
+    }
+
+    .ag-header-container {
+        border-bottom: 2px solid $table-border-color;
+    }
+
+    .ag-header-cell {
+        text-align: left;
+        border-right: 1px solid #ccc
+    }
+
+    .ag-header-group-cell {
+        border-right: 1px solid grey;
+    }
+
+    .ag-header-group-cell-with-group {
+        border-bottom: 1px solid grey;
+    }
+
+    .ag-header-cell-label {
+        padding: 5px;
+        font-weight: 700;
+        font-size: 13px;
+    }
+
+    .ag-header-group-cell-label {
+        padding: 5px;
+        font-weight: bold;
+    }
+
+    .ag-header-cell-menu-button {
+        padding: 2px;
+        margin-top: 4px;
+        border: 1px solid transparent;
+        border-radius: 3px;
+        box-sizing: content-box; /* When using bootstrap, box-sizing was set to 'border-box' */
+    }
+
+    .ag-header-cell-menu-button:hover {
+        border: 1px solid black;
+    }
+
+    .ag-row-odd {
+        background-color: #f6f6f6;
+    }
+
+    .ag-row-even {
+        background-color: white;
+    }
+
+    .ag-body {
+        background-color: $table-bg;
+    }
+
+    .ag-row-selected {
+        background-color: #B2DFEE;
+        /*background-color: #ECF1EF;*/
+    }
+
+    .ag-group-cell-entire-row {
+        background-color: #aaa;
+    }
+
+    .ag-group-cell {
+    }
+
+    .ag-filter-checkbox {
+        position: relative;
+        top: 2px;
+        left: 2px;
+    }
+
+    .ag-filter-header-container {
+        border-bottom: 1px solid lightgrey;
+    }
+
+    .ag-filter {
+        border: 1px solid black;
+        background-color: #f0f0f0;
+    }
+
+    .ag-selection-checkbox {
+        margin-left: 4px;
+    }
+
+    .ag-loading-panel {
+        background-color: rgba(255, 255, 255, 0.9);
+    }
+
+    .ag-loading-center {
+        background-color: white;
+        border: 1px solid $ignite-darck-border-color;
+        border-radius: 10px;
+        padding: 10px;
+    }
+}
+
+.carousel-caption {
+    position: relative;
+    left: auto;
+    right: auto;
+
+    margin-top: 10px;
+
+    h3 {
+        margin-bottom: 10px;
+    }
+}
+
+.carousel-control {
+    font-size: 20px;
+    z-index: 16;
+
+    // Toggles
+    .fa-chevron-left,.fa-chevron-right {
+        position: absolute;
+        bottom: 28px;
+        margin-top: -10px;
+        z-index: 16;
+        display: inline-block;
+        margin-left: -10px;
+    }
+
+    .fa-chevron-left {
+        left: 90%;
+        margin-left: -10px;
+    }
+
+    .fa-chevron-right {
+        right: 90%;
+        margin-right: -10px;
+    }
+}
+
+.carousel-control.left {
+    background-image: none;
+}
+
+.carousel-control.right {
+    background-image: none;
+}
+
+.home-panel {
+    border-radius: 5px;
+    border: thin dotted $panel-default-border;
+    background-color: $panel-default-heading-bg;
+
+    margin-top: 20px;
+    padding: 10px;
+}
+
+.home {
+    min-height: 880px;
+    padding: 20px;
+
+    @media(min-width: 992px) {
+        min-height: 450px;
+    }
+}
+
+.additional-filter {
+    input[type="checkbox"] {
+        position: absolute;
+        margin-top: 8px;
+    }
+
+    a {
+        font-family: normal;
+        padding-left: 20px;
+        float: none;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/admin.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/admin.js b/modules/control-center-web/src/main/js/routes/admin.js
new file mode 100644
index 0000000..66bdaab
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/admin.js
@@ -0,0 +1,127 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+var nodemailer = require('nodemailer');
+
+var db = require('../db');
+var config = require('../helpers/configuration-loader.js');
+
+router.get('/', function (req, res) {
+    res.render('settings/admin');
+});
+
+/**
+ * Get list of user accounts.
+ */
+router.post('/list', function (req, res) {
+    db.Account.find({}).sort('username').exec(function (err, users) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        res.json(users);
+    });
+});
+
+// Remove user.
+router.post('/remove', function (req, res) {
+    var userId = req.body.userId;
+
+    db.Account.findByIdAndRemove(userId, function (err, user) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        db.Space.find({owner: userId}, function(err, spaces) {
+            _.forEach(spaces, function (space) {
+                db.Cluster.remove({space: space._id}).exec();
+                db.Cache.remove({space: space._id}).exec();
+                db.CacheTypeMetadata.remove({space: space._id}).exec();
+                db.DatabasePreset.remove({space: space._id}).exec();
+                db.Notebook.remove({space: space._id}).exec();
+                db.Space.remove({owner: space._id}).exec();
+            });
+        });
+
+        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 != '') {
+            var mailer  = nodemailer.createTransport(transporter);
+
+            var mailOptions = {
+                from: transporter.auth.user,
+                to: user.email,
+                subject: 'Your account was deleted',
+                text: 'You are receiving this e-mail because admin remove your account.\n\n' +
+                '--------------\n' +
+                'Apache Ignite Web Console http://' + req.headers.host + '\n'
+            };
+
+            mailer.sendMail(mailOptions, function(err){
+                if (err)
+                    return res.status(503).send('Account was removed, but failed to send e-mail notification to user!<br />' + err);
+
+                res.sendStatus(200);
+            });
+        }
+        else
+            res.sendStatus(200);
+    });
+});
+
+// Save user.
+router.post('/save', function (req, res) {
+    var userId = req.body.userId;
+    var adminFlag = req.body.adminFlag;
+
+    db.Account.findByIdAndUpdate(userId, {admin: adminFlag}, function (err) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        res.sendStatus(200);
+    });
+});
+
+// Become user.
+router.get('/become', function (req, res) {
+    var viewedUserId = req.query.viewedUserId;
+
+    if (!viewedUserId) {
+        req.session.viewedUser = null;
+
+        return res.redirect('/admin');
+    }
+
+    db.Account.findById(viewedUserId).exec(function (err, viewedUser) {
+        if (err)
+            return res.sendStatus(404);
+
+        req.session.viewedUser = viewedUser;
+
+        res.redirect('/');
+    })
+});
+
+module.exports = router;


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/generator/generator-java.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-java.js b/modules/control-center-web/src/main/js/routes/generator/generator-java.js
new file mode 100644
index 0000000..1e7139d
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-java.js
@@ -0,0 +1,1581 @@
+/*
+ *
+ *  * 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');
+}
+
+// Java generation entry point.
+$generatorJava = {};
+
+/**
+ * Translate some value to valid java code.
+ *
+ * @param val Value to convert.
+ * @param type Value type.
+ * @returns {*} String with value that will be valid for java.
+ */
+$generatorJava.toJavaCode = function (val, type) {
+    if (val == null)
+        return 'null';
+
+    if (type == 'raw')
+        return val;
+
+    if (type == 'class')
+        return val + '.class';
+
+    if (type == 'float')
+        return val + 'f';
+
+    if (type == 'path')
+        return '"' + val.replace(/\\/g, '\\\\') + '"';
+
+    if (type)
+        return type + '.' + val;
+
+    if (typeof(val) == 'string')
+        return '"' + val.replace('"', '\\"') + '"';
+
+    if (typeof(val) == 'number' || typeof(val) == 'boolean')
+        return '' + val;
+
+    throw "Unknown type: " + typeof(val) + ' (' + val + ')';
+};
+
+/**
+ * @param propName Property name.
+ * @param setterName Optional concrete setter name.
+ * @returns Property setter with name by java conventions.
+ */
+$generatorJava.setterName = function (propName, setterName) {
+    return setterName ? setterName : $commonUtils.toJavaName('set', propName);
+};
+
+/**
+ * @param res Result holder.
+ * @param varName Variable name to check.
+ * @returns {boolean} 'true' if new variable required.
+ */
+$generatorJava.needNewVariable = function (res, varName) {
+    var needNew = !res[varName];
+
+    if (needNew)
+        res[varName] = true;
+
+    return needNew;
+};
+
+/**
+ * Add variable declaration.
+ *
+ * @param res Resulting output with generated code.
+ * @param varNew If 'true' then declare new variable otherwise reuse previously declared variable.
+ * @param varName Variable name.
+ * @param varFullType Variable full class name to be added to imports.
+ * @param varFullActualType Variable actual full class name to be added to imports.
+ * @param varFullGenericType1 Optional full class name of first generic.
+ * @param varFullGenericType2 Optional full class name of second generic.
+ */
+$generatorJava.declareVariable = function (res, varNew, varName, varFullType, varFullActualType, varFullGenericType1, varFullGenericType2) {
+    res.emptyLineIfNeeded();
+
+    var varType = res.importClass(varFullType);
+
+    if (varFullActualType && varFullGenericType1) {
+        var varActualType = res.importClass(varFullActualType);
+        var varGenericType1 = res.importClass(varFullGenericType1);
+
+        if (varFullGenericType2)
+            var varGenericType2 = res.importClass(varFullGenericType2);
+
+        res.line((varNew ? (varType + '<' + varGenericType1 + (varGenericType2 ? ', ' + varGenericType2 : '') + '> ') : '') + varName + ' = new ' + varActualType + '<>();');
+    }
+    else
+        res.line((varNew ? (varType + ' ') : '') + varName + ' = new ' + varType + '();');
+
+    res.needEmptyLine = true;
+};
+
+/**
+ * Add property via setter / property name.
+ *
+ * @param res Resulting output with generated code.
+ * @param varName Variable name.
+ * @param obj Source object with data.
+ * @param propName Property name to take from source object.
+ * @param dataType Optional info about property data type.
+ * @param setterName Optional special setter name.
+ * @param dflt Optional default value.
+ */
+$generatorJava.property = function (res, varName, obj, propName, dataType, setterName, dflt) {
+    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)) {
+            res.emptyLineIfNeeded();
+
+            res.line(varName + '.' + $generatorJava.setterName(propName, setterName)
+                + '(' + $generatorJava.toJavaCode(val, dataType) + ');');
+
+            return true;
+        }
+    }
+
+    return false;
+};
+
+/**
+ * Add property via setter assuming that it is a 'Class'.
+ *
+ * @param res Resulting output with generated code.
+ * @param varName Variable name.
+ * @param obj Source object with data.
+ * @param propName Property name to take from source object.
+ */
+$generatorJava.classNameProperty = function (res, varName, obj, propName) {
+    var val = obj[propName];
+
+    if ($commonUtils.isDefined(val)) {
+        res.emptyLineIfNeeded();
+
+        res.line(varName + '.' + $generatorJava.setterName(propName) + '(' + res.importClass(val) + '.class);');
+    }
+};
+
+/**
+ * Add list property.
+ *
+ * @param res Resulting output with generated code.
+ * @param varName Variable name.
+ * @param obj Source object with data.
+ * @param propName Property name to take from source object.
+ * @param dataType Optional data type.
+ * @param setterName Optional setter name.
+ */
+$generatorJava.listProperty = function (res, varName, obj, propName, dataType, setterName) {
+    var val = obj[propName];
+
+    if (val && val.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.importClass('java.util.Arrays');
+
+        res.line(varName + '.' + $generatorJava.setterName(propName, setterName) +
+            '(Arrays.asList(' +
+                _.map(val, function (v) {
+                    return $generatorJava.toJavaCode(v, dataType)
+                }).join(', ') +
+            '));');
+
+        res.needEmptyLine = true;
+    }
+};
+
+/**
+ * Add array property.
+ *
+ * @param res Resulting output with generated code.
+ * @param varName Variable name.
+ * @param obj Source object with data.
+ * @param propName Property name to take from source object.
+ * @param dataType Optional data type.
+ * @param setterName Optional setter name.
+ */
+$generatorJava.arrayProperty = function (res, varName, obj, propName, setterName) {
+    var val = obj[propName];
+
+    if (val && val.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.line(varName + '.' + $generatorJava.setterName(propName, setterName) + '({ ' +
+            _.map(val, function (v) {
+                return 'new ' + res.importClass(v) + '()'
+            }).join(', ') +
+        ' });');
+
+        res.needEmptyLine = true;
+    }
+};
+
+/**
+ * Add multi-param property (setter with several arguments).
+ *
+ * @param res Resulting output with generated code.
+ * @param varName Variable name.
+ * @param obj Source object with data.
+ * @param propName Property name to take from source object.
+ * @param dataType Optional data type.
+ * @param setterName Optional setter name.
+ */
+$generatorJava.multiparamProperty = function (res, varName, obj, propName, dataType, setterName) {
+    var val = obj[propName];
+
+    if (val && val.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.append(varName + '.' + $generatorJava.setterName(propName, setterName) + '(');
+
+        for (var i = 0; i < val.length; i++) {
+            if (i > 0)
+                res.append(', ');
+
+            res.append($generatorJava.toJavaCode(val[i], dataType));
+        }
+
+        res.line(');');
+    }
+};
+
+/**
+ * Add complex bean.
+ * @param res Resulting output with generated code.
+ * @param varName Variable name.
+ * @param bean
+ * @param beanPropName
+ * @param beanVarName
+ * @param beanClass
+ * @param props
+ * @param createBeanAlthoughNoProps If 'true' then create empty bean.
+ */
+$generatorJava.beanProperty = function (res, varName, bean, beanPropName, beanVarName, beanClass, props, createBeanAlthoughNoProps) {
+    if (bean && $commonUtils.hasProperty(bean, props)) {
+        res.emptyLineIfNeeded();
+
+        $generatorJava.declareVariable(res, true, beanVarName, beanClass);
+
+        for (var propName in props) {
+            if (props.hasOwnProperty(propName)) {
+                var descr = props[propName];
+
+                if (descr) {
+                    switch (descr.type) {
+                        case 'list':
+                            $generatorJava.listProperty(res, beanVarName, bean, propName, descr.elementsType, descr.setterName);
+                            break;
+
+                        case 'array':
+                            $generatorJava.arrayProperty(res, beanVarName, bean, propName, descr.setterName);
+                            break;
+
+                        case 'enum':
+                            $generatorJava.property(res, beanVarName, bean, propName, res.importClass(descr.enumClass), descr.setterName);
+                            break;
+
+                        case 'float':
+                            $generatorJava.property(res, beanVarName, bean, propName, 'float', descr.setterName);
+                            break;
+
+                        case 'path':
+                            $generatorJava.property(res, beanVarName, bean, propName, 'path', descr.setterName);
+                            break;
+
+                        case 'raw':
+                            $generatorJava.property(res, beanVarName, bean, propName, 'raw', descr.setterName);
+                            break;
+
+                        case 'propertiesAsList':
+                            var val = bean[propName];
+
+                            if (val && val.length > 0) {
+                                res.line('Properties ' + descr.propVarName + ' = new Properties();');
+
+                                for (var i = 0; i < val.length; i++) {
+                                    var nameAndValue = val[i];
+
+                                    var eqIndex = nameAndValue.indexOf('=');
+                                    if (eqIndex >= 0) {
+                                        res.line(descr.propVarName + '.setProperty('
+                                            + nameAndValue.substring(0, eqIndex) + ', '
+                                            + nameAndValue.substr(eqIndex + 1) + ');');
+                                    }
+
+                                }
+
+                                res.line(beanVarName + '.' + $generatorJava.setterName(propName) + '(' + descr.propVarName + ');');
+                            }
+                            break;
+
+                        case 'jdbcDialect':
+                            if (bean[propName]) {
+                                var jdbcDialectClsName = res.importClass($generatorCommon.jdbcDialectClassName(bean[propName]));
+
+                                res.line(beanVarName + '.' + $generatorJava.setterName(propName) + '(new ' + jdbcDialectClsName + '());');
+                            }
+
+                            break;
+
+                        default:
+                            $generatorJava.property(res, beanVarName, bean, propName, null, descr.setterName);
+                    }
+                }
+                else {
+                    $generatorJava.property(res, beanVarName, bean, propName);
+                }
+            }
+        }
+
+        res.needEmptyLine = true;
+
+        res.line(varName + '.' + $generatorJava.setterName(beanPropName) + '(' + beanVarName + ');');
+
+        res.needEmptyLine = true;
+    }
+    else if (createBeanAlthoughNoProps) {
+        res.emptyLineIfNeeded();
+        res.line(varName + '.' + $generatorJava.setterName(beanPropName) + '(new ' + res.importClass(beanClass) + '());');
+
+        res.needEmptyLine = true;
+    }
+};
+
+/**
+ * Add eviction policy.
+ *
+ * @param res Resulting output with generated code.
+ * @param varName Current using variable name.
+ * @param evtPlc Data to add.
+ * @param propName Name in source data.
+ */
+$generatorJava.evictionPolicy = function (res, varName, evtPlc, propName) {
+    if (evtPlc && evtPlc.kind) {
+        var evictionPolicyDesc = $generatorCommon.EVICTION_POLICIES[evtPlc.kind];
+
+        var obj = evtPlc[evtPlc.kind.toUpperCase()];
+
+        $generatorJava.beanProperty(res, varName, obj, propName, propName,
+            evictionPolicyDesc.className, evictionPolicyDesc.fields, true);
+    }
+};
+
+// Generate cluster general group.
+$generatorJava.clusterGeneral = function (cluster, clientNearCfg, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.declareVariable(res, true, 'cfg', 'org.apache.ignite.configuration.IgniteConfiguration');
+
+    $generatorJava.property(res, 'cfg', cluster, 'name', null, 'setGridName');
+    res.needEmptyLine = true;
+
+    if (clientNearCfg) {
+        res.line('cfg.setClientMode(true);');
+
+        res.needEmptyLine = true;
+    }
+
+    if (cluster.discovery) {
+        var d = cluster.discovery;
+
+        $generatorJava.declareVariable(res, true, 'discovery', 'org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi');
+
+        switch (d.kind) {
+            case 'Multicast':
+                $generatorJava.beanProperty(res, 'discovery', d.Multicast, 'ipFinder', 'ipFinder',
+                    'org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder',
+                    {
+                        multicastGroup: null,
+                        multicastPort: null,
+                        responseWaitTime: null,
+                        addressRequestAttempts: null,
+                        localAddress: null,
+                        addresses: {type: 'list'}
+                    }, true);
+
+                break;
+
+            case 'Vm':
+                $generatorJava.beanProperty(res, 'discovery', d.Vm, 'ipFinder', 'ipFinder',
+                    'org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder',
+                    {addresses: {type: 'list'}}, true);
+
+                break;
+
+            case 'S3':
+                $generatorJava.beanProperty(res, 'discovery', d.S3, 'ipFinder', 'ipFinder',
+                    'org.apache.ignite.spi.discovery.tcp.ipfinder.s3.TcpDiscoveryS3IpFinder', {bucketName: null}, true);
+
+                break;
+
+            case 'Cloud':
+                $generatorJava.beanProperty(res, 'discovery', d.Cloud, 'ipFinder', 'ipFinder',
+                    'org.apache.ignite.spi.discovery.tcp.ipfinder.cloud.TcpDiscoveryCloudIpFinder',
+                    {
+                        credential: null,
+                        credentialPath: null,
+                        identity: null,
+                        provider: null,
+                        regions: {type: 'list'},
+                        zones: {type: 'list'}
+                    }, true);
+
+                break;
+
+            case 'GoogleStorage':
+                $generatorJava.beanProperty(res, 'discovery', d.GoogleStorage, 'ipFinder', 'ipFinder',
+                    'org.apache.ignite.spi.discovery.tcp.ipfinder.gce.TcpDiscoveryGoogleStorageIpFinder',
+                    {
+                        projectName: null,
+                        bucketName: null,
+                        serviceAccountP12FilePath: null,
+                        serviceAccountId: null
+                    }, true);
+
+                break;
+
+            case 'Jdbc':
+                $generatorJava.beanProperty(res, 'discovery', d.Jdbc, 'ipFinder', 'ipFinder',
+                    'org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinder', {initSchema: null}, true);
+
+                break;
+
+            case 'SharedFs':
+                $generatorJava.beanProperty(res, 'discovery', d.SharedFs, 'ipFinder', 'ipFinder',
+                    'org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinder', {path: null}, true);
+
+                break;
+
+            default:
+                res.line('Unknown discovery kind: ' + d.kind);
+        }
+
+        res.needEmptyLine = false;
+
+        $generatorJava.clusterDiscovery(d, res);
+
+        res.emptyLineIfNeeded();
+
+        res.line('cfg.setDiscoverySpi(discovery);');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate atomics group.
+$generatorJava.clusterAtomics = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    var atomics = cluster.atomicConfiguration;
+
+    if ($commonUtils.hasAtLeastOneProperty(atomics, ['cacheMode', 'atomicSequenceReserveSize', 'backups'])) {
+        res.startSafeBlock();
+
+        $generatorJava.declareVariable(res, true, 'atomicCfg', 'org.apache.ignite.configuration.AtomicConfiguration');
+
+        $generatorJava.property(res, 'atomicCfg', atomics, 'cacheMode');
+
+        var cacheMode = atomics.cacheMode ? atomics.cacheMode : 'PARTITIONED';
+
+        var hasData = cacheMode != 'PARTITIONED';
+
+        hasData = $generatorJava.property(res, 'atomicCfg', atomics, 'atomicSequenceReserveSize') || hasData;
+
+        if (cacheMode == 'PARTITIONED')
+            hasData = $generatorJava.property(res, 'atomicCfg', atomics, 'backups') || hasData;
+
+        res.needEmptyLine = true;
+
+        res.line('cfg.setAtomicConfiguration(atomicCfg);');
+
+        res.needEmptyLine = true;
+
+        if (!hasData)
+            res.rollbackSafeBlock();
+    }
+
+    return res;
+};
+
+// Generate communication group.
+$generatorJava.clusterCommunication = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, 'cfg', cluster, 'networkTimeout');
+    $generatorJava.property(res, 'cfg', cluster, 'networkSendRetryDelay');
+    $generatorJava.property(res, 'cfg', cluster, 'networkSendRetryCount');
+    $generatorJava.property(res, 'cfg', cluster, 'segmentCheckFrequency');
+    $generatorJava.property(res, 'cfg', cluster, 'waitForSegmentOnStart', null, null, false);
+    $generatorJava.property(res, 'cfg', cluster, 'discoveryStartupDelay');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate deployment group.
+$generatorJava.clusterDeployment = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, 'cfg', cluster, 'deploymentMode', null, null, 'SHARED');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate discovery group.
+$generatorJava.clusterDiscovery = function (disco, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, 'discovery', disco, 'localAddress');
+    $generatorJava.property(res, 'discovery', disco, 'localPort', undefined, undefined, 47500);
+    $generatorJava.property(res, 'discovery', disco, 'localPortRange', undefined, undefined, 100);
+
+    if ($commonUtils.isDefinedAndNotEmpty(disco.addressResolver)) {
+        $generatorJava.beanProperty(res, 'discovery', disco, 'addressResolver', 'addressResolver', disco.addressResolver, {}, true);
+        res.needEmptyLine = false;
+    }
+
+    $generatorJava.property(res, 'discovery', disco, 'socketTimeout');
+    $generatorJava.property(res, 'discovery', disco, 'ackTimeout');
+    $generatorJava.property(res, 'discovery', disco, 'maxAckTimeout', undefined, undefined, 600000);
+    $generatorJava.property(res, 'discovery', disco, 'networkTimeout', undefined, undefined, 5000);
+    $generatorJava.property(res, 'discovery', disco, 'joinTimeout', undefined, undefined, 0);
+    $generatorJava.property(res, 'discovery', disco, 'threadPriority', undefined, undefined, 10);
+    $generatorJava.property(res, 'discovery', disco, 'heartbeatFrequency', undefined, undefined, 2000);
+    $generatorJava.property(res, 'discovery', disco, 'maxMissedHeartbeats', undefined, undefined, 1);
+    $generatorJava.property(res, 'discovery', disco, 'maxMissedClientHeartbeats', undefined, undefined, 5);
+    $generatorJava.property(res, 'discovery', disco, 'topHistorySize', undefined, undefined, 100);
+
+    if ($commonUtils.isDefinedAndNotEmpty(disco.listener)) {
+        $generatorJava.beanProperty(res, 'discovery', disco, 'listener', 'listener', disco.listener, {}, true);
+        res.needEmptyLine = false;
+    }
+
+    if ($commonUtils.isDefinedAndNotEmpty(disco.dataExchange)) {
+        $generatorJava.beanProperty(res, 'discovery', disco, 'dataExchange', 'dataExchange', disco.dataExchange, {}, true);
+        res.needEmptyLine = false;
+    }
+
+    if ($commonUtils.isDefinedAndNotEmpty(disco.metricsProvider)) {
+        $generatorJava.beanProperty(res, 'discovery', disco, 'metricsProvider', 'metricsProvider', disco.metricsProvider, {}, true);
+        res.needEmptyLine = false;
+    }
+
+    $generatorJava.property(res, 'discovery', disco, 'reconnectCount', undefined, undefined, 10);
+    $generatorJava.property(res, 'discovery', disco, 'statisticsPrintFrequency', undefined, undefined, 0);
+    $generatorJava.property(res, 'discovery', disco, 'ipFinderCleanFrequency', undefined, undefined, 60000);
+
+    if ($commonUtils.isDefinedAndNotEmpty(disco.authenticator)) {
+        $generatorJava.beanProperty(res, 'discovery', disco, 'authenticator', 'authenticator', disco.authenticator, {}, true);
+        res.needEmptyLine = false;
+    }
+
+    $generatorJava.property(res, 'discovery', disco, 'forceServerMode', undefined, undefined, false);
+    $generatorJava.property(res, 'discovery', disco, 'clientReconnectDisabled', undefined, undefined, false);
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate events group.
+$generatorJava.clusterEvents = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cluster.includeEventTypes && cluster.includeEventTypes.length > 0) {
+        res.emptyLineIfNeeded();
+
+        if (cluster.includeEventTypes.length == 1) {
+            res.importClass('org.apache.ignite.events.EventType');
+
+            res.line('cfg.setIncludeEventTypes(EventType.' + cluster.includeEventTypes[0] + ');');
+        }
+        else {
+            res.append('int[] events = new int[EventType.' + cluster.includeEventTypes[0] + '.length');
+
+            for (i = 1; i < cluster.includeEventTypes.length; i++) {
+                res.needEmptyLine = true;
+
+                res.append('    + EventType.' + cluster.includeEventTypes[i] + '.length');
+            }
+
+            res.line('];');
+
+            res.needEmptyLine = true;
+
+            res.line('int k = 0;');
+
+            for (i = 0; i < cluster.includeEventTypes.length; i++) {
+                res.needEmptyLine = true;
+
+                var e = cluster.includeEventTypes[i];
+
+                res.line('System.arraycopy(EventType.' + e + ', 0, events, k, EventType.' + e + '.length);');
+                res.line('k += EventType.' + e + '.length;');
+            }
+
+            res.needEmptyLine = true;
+
+            res.line('cfg.setIncludeEventTypes(events);');
+        }
+
+        res.needEmptyLine = true;
+    }
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate marshaller group.
+$generatorJava.clusterMarshaller = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    var marshaller = cluster.marshaller;
+
+    if (marshaller && marshaller.kind) {
+        var marshallerDesc = $generatorCommon.MARSHALLERS[marshaller.kind];
+
+        $generatorJava.beanProperty(res, 'cfg', marshaller[marshaller.kind], 'marshaller', 'marshaller',
+            marshallerDesc.className, marshallerDesc.fields, true);
+
+        $generatorJava.beanProperty(res, 'marshaller', marshaller[marshaller.kind], marshallerDesc.className, marshallerDesc.fields, true);
+    }
+
+    $generatorJava.property(res, 'cfg', cluster, 'marshalLocalJobs', null, null, false);
+    $generatorJava.property(res, 'cfg', cluster, 'marshallerCacheKeepAliveTime');
+    $generatorJava.property(res, 'cfg', cluster, 'marshallerCacheThreadPoolSize');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metrics group.
+$generatorJava.clusterMetrics = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, 'cfg', cluster, 'metricsExpireTime');
+    $generatorJava.property(res, 'cfg', cluster, 'metricsHistorySize');
+    $generatorJava.property(res, 'cfg', cluster, 'metricsLogFrequency');
+    $generatorJava.property(res, 'cfg', cluster, 'metricsUpdateFrequency');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate PeerClassLoading group.
+$generatorJava.clusterP2p = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    var p2pEnabled = cluster.peerClassLoadingEnabled;
+
+    if ($commonUtils.isDefined(p2pEnabled)) {
+        $generatorJava.property(res, 'cfg', cluster, 'peerClassLoadingEnabled', null, null, false);
+
+        if (p2pEnabled) {
+            $generatorJava.property(res, 'cfg', cluster, 'peerClassLoadingMissedResourcesCacheSize');
+            $generatorJava.property(res, 'cfg', cluster, 'peerClassLoadingThreadPoolSize');
+            $generatorJava.multiparamProperty(res, 'cfg', cluster, 'peerClassLoadingLocalClassPathExclude');
+        }
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate swap group.
+$generatorJava.clusterSwap = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cluster.swapSpaceSpi && cluster.swapSpaceSpi.kind == 'FileSwapSpaceSpi') {
+        $generatorJava.beanProperty(res, 'cfg', cluster.swapSpaceSpi.FileSwapSpaceSpi, 'swapSpaceSpi', 'swapSpi',
+            $generatorCommon.SWAP_SPACE_SPI.className, $generatorCommon.SWAP_SPACE_SPI.fields, true);
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate time group.
+$generatorJava.clusterTime = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, 'cfg', cluster, 'clockSyncSamples');
+    $generatorJava.property(res, 'cfg', cluster, 'clockSyncFrequency');
+    $generatorJava.property(res, 'cfg', cluster, 'timeServerPortBase');
+    $generatorJava.property(res, 'cfg', cluster, 'timeServerPortRange');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate thread pools group.
+$generatorJava.clusterPools = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, 'cfg', cluster, 'publicThreadPoolSize');
+    $generatorJava.property(res, 'cfg', cluster, 'systemThreadPoolSize');
+    $generatorJava.property(res, 'cfg', cluster, 'managementThreadPoolSize');
+    $generatorJava.property(res, 'cfg', cluster, 'igfsThreadPoolSize');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate transactions group.
+$generatorJava.clusterTransactions = function (cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.beanProperty(res, 'cfg', cluster.transactionConfiguration, 'transactionConfiguration',
+        'transactionConfiguration', $generatorCommon.TRANSACTION_CONFIGURATION.className,
+        $generatorCommon.TRANSACTION_CONFIGURATION.fields);
+
+    return res;
+};
+
+// Generate cache general group.
+$generatorJava.cacheGeneral = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, varName, cache, 'name');
+
+    res.importClass('org.apache.ignite.cache.CacheAtomicityMode');
+    res.importClass('org.apache.ignite.cache.CacheMode');
+
+    $generatorJava.property(res, varName, cache, 'cacheMode', 'CacheMode');
+    $generatorJava.property(res, varName, cache, 'atomicityMode', 'CacheAtomicityMode');
+
+    if (cache.cacheMode == 'PARTITIONED')
+        $generatorJava.property(res, varName, cache, 'backups');
+
+    $generatorJava.property(res, varName, cache, 'readFromBackup');
+    $generatorJava.property(res, varName, cache, 'copyOnRead');
+    $generatorJava.property(res, varName, cache, 'invalidate');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache memory group.
+$generatorJava.cacheMemory = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, varName, cache, 'memoryMode', 'CacheMemoryMode');
+    $generatorJava.property(res, varName, cache, 'offHeapMaxMemory');
+
+    res.needEmptyLine = true;
+
+    $generatorJava.evictionPolicy(res, varName, cache.evictionPolicy, 'evictionPolicy');
+
+    $generatorJava.property(res, varName, cache, 'swapEnabled');
+    $generatorJava.property(res, varName, cache, 'startSize');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache query & indexing group.
+$generatorJava.cacheQuery = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, varName, cache, 'sqlOnheapRowCacheSize');
+    $generatorJava.property(res, varName, cache, 'longQueryWarningTimeout');
+
+    if (cache.indexedTypes && cache.indexedTypes.length > 0) {
+        res.emptyLineIfNeeded();
+
+        res.append(varName + '.setIndexedTypes(');
+
+        for (var i = 0; i < cache.indexedTypes.length; i++) {
+            if (i > 0)
+                res.append(', ');
+
+            var pair = cache.indexedTypes[i];
+
+            res.append($generatorJava.toJavaCode(res.importClass(pair.keyClass), 'class')).append(', ').append($generatorJava.toJavaCode(res.importClass(pair.valueClass), 'class'))
+        }
+
+        res.line(');');
+    }
+
+    $generatorJava.multiparamProperty(res, varName, cache, 'sqlFunctionClasses', 'class');
+
+    $generatorJava.property(res, varName, cache, 'sqlEscapeAll');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache store group.
+$generatorJava.cacheStore = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) {
+        var storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind];
+
+        if (storeFactory) {
+            var storeFactoryDesc = $generatorCommon.STORE_FACTORIES[cache.cacheStoreFactory.kind];
+
+            var sfVarName = $commonUtils.toJavaName('storeFactory', cache.name);
+            var dsVarName = 'none';
+
+            if (storeFactory.dialect) {
+                var dataSourceBean = storeFactory.dataSourceBean;
+
+                dsVarName = $commonUtils.toJavaName('dataSource', dataSourceBean);
+
+                if (!_.contains(res.datasources, dataSourceBean)) {
+                    res.datasources.push(dataSourceBean);
+
+                    var dsClsName = $generatorCommon.dataSourceClassName(storeFactory.dialect);
+
+                    res.needEmptyLine = true;
+
+                    $generatorJava.declareVariable(res, true, dsVarName, dsClsName);
+
+                    switch (storeFactory.dialect) {
+                        case 'DB2':
+                            res.line(dsVarName + '.setServerName(_SERVER_NAME_);');
+                            res.line(dsVarName + '.setPortNumber(_PORT_NUMBER_);');
+                            res.line(dsVarName + '.setDatabaseName(_DATABASE_NAME_);');
+                            res.line(dsVarName + '.setDriverType(_DRIVER_TYPE_);');
+                            break;
+
+                        default:
+                            res.line(dsVarName + '.setURL(_URL_);');
+                    }
+
+                    res.line(dsVarName + '.setUsername(_User_Name_);');
+                    res.line(dsVarName + '.setPassword(_Password_);');
+                }
+            }
+
+            $generatorJava.beanProperty(res, varName, storeFactory, 'cacheStoreFactory', sfVarName,
+                storeFactoryDesc.className, storeFactoryDesc.fields, true);
+
+            if (dsVarName != 'none')
+                res.line(sfVarName + '.setDataSource(' + dsVarName + ');');
+
+            res.needEmptyLine = true;
+        }
+    }
+
+    $generatorJava.property(res, varName, cache, 'loadPreviousValue');
+    $generatorJava.property(res, varName, cache, 'readThrough');
+    $generatorJava.property(res, varName, cache, 'writeThrough');
+
+    res.needEmptyLine = true;
+
+    $generatorJava.property(res, varName, cache, 'writeBehindEnabled');
+    $generatorJava.property(res, varName, cache, 'writeBehindBatchSize');
+    $generatorJava.property(res, varName, cache, 'writeBehindFlushSize');
+    $generatorJava.property(res, varName, cache, 'writeBehindFlushFrequency');
+    $generatorJava.property(res, varName, cache, 'writeBehindFlushThreadCount');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache concurrency group.
+$generatorJava.cacheConcurrency = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, varName, cache, 'maxConcurrentAsyncOperations');
+    $generatorJava.property(res, varName, cache, 'defaultLockTimeout');
+    $generatorJava.property(res, varName, cache, 'atomicWriteOrderMode');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache rebalance group.
+$generatorJava.cacheRebalance = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cache.cacheMode != 'LOCAL') {
+        $generatorJava.property(res, varName, cache, 'rebalanceMode', 'CacheRebalanceMode');
+        $generatorJava.property(res, varName, cache, 'rebalanceThreadPoolSize');
+        $generatorJava.property(res, varName, cache, 'rebalanceBatchSize');
+        $generatorJava.property(res, varName, cache, 'rebalanceOrder');
+        $generatorJava.property(res, varName, cache, 'rebalanceDelay');
+        $generatorJava.property(res, varName, cache, 'rebalanceTimeout');
+        $generatorJava.property(res, varName, cache, 'rebalanceThrottle');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate cache server near cache group.
+$generatorJava.cacheServerNearCache = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cache.cacheMode == 'PARTITIONED' && cache.nearCacheEnabled) {
+        res.needEmptyLine = true;
+
+        res.importClass('org.apache.ignite.configuration.NearCacheConfiguration');
+
+        $generatorJava.beanProperty(res, varName, cache.nearConfiguration, 'nearConfiguration', 'nearConfiguration',
+            'NearCacheConfiguration', {nearStartSize: null}, true);
+
+        if (cache.nearConfiguration && cache.nearConfiguration.nearEvictionPolicy && cache.nearConfiguration.nearEvictionPolicy.kind) {
+            $generatorJava.evictionPolicy(res, 'nearConfiguration', cache.nearConfiguration.nearEvictionPolicy, 'nearEvictionPolicy');
+        }
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate cache statistics group.
+$generatorJava.cacheStatistics = function (cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, varName, cache, 'statisticsEnabled');
+    $generatorJava.property(res, varName, cache, 'managementEnabled');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metadata query fields.
+$generatorJava.metadataQueryFields = function (res, meta, fieldProperty) {
+    var fields = meta[fieldProperty];
+
+    if (fields && fields.length > 0) {
+        $generatorJava.declareVariable(res, $generatorJava.needNewVariable(res, fieldProperty), fieldProperty, 'java.util.Map', 'java.util.LinkedHashMap', 'java.lang.String', 'java.lang.Class<?>');
+
+        _.forEach(fields, function (field) {
+            res.line(fieldProperty + '.put("' + field.name.toUpperCase() + '", ' + res.importClass(field.className) + '.class);');
+        });
+
+        res.needEmptyLine = true;
+
+        res.line('typeMeta.' + $commonUtils.toJavaName('set', fieldProperty) + '(' + fieldProperty + ');');
+
+        res.needEmptyLine = true;
+    }
+};
+
+// Generate metadata groups.
+$generatorJava.metadataGroups = function (res, meta) {
+    var groups = meta.groups;
+
+    if (groups && groups.length > 0) {
+        _.forEach(groups, function (group) {
+            var fields = group.fields;
+
+            if (fields && fields.length > 0) {
+                res.importClass('java.util.Map');
+                res.importClass('java.util.LinkedHashMap');
+                res.importClass('org.apache.ignite.lang.IgniteBiTuple');
+
+                var varNew = !res.groups;
+
+                res.needEmptyLine = true;
+
+                res.line((varNew ? 'Map<String, LinkedHashMap<String, IgniteBiTuple<Class<?>, Boolean>>> ' : '') +
+                    "groups = new LinkedHashMap<>();");
+
+                res.needEmptyLine = true;
+
+                if (varNew)
+                    res.groups = true;
+
+                varNew = !res.groupItems;
+
+                res.line((varNew ? 'LinkedHashMap<String, IgniteBiTuple<Class<?>, Boolean>> ' : '') +
+                    'groupItems = new LinkedHashMap<>();');
+
+                res.needEmptyLine = true;
+
+                if (varNew)
+                    res.groupItems = true;
+
+                _.forEach(fields, function (field) {
+                    res.line('groupItems.put("' + field.name + '", ' +
+                        'new IgniteBiTuple<Class<?>, Boolean>(' + res.importClass(field.className) + '.class, ' + field.direction + '));');
+                });
+
+                res.needEmptyLine = true;
+
+                res.line('groups.put("' + group.name + '", groupItems);');
+            }
+        });
+
+        res.needEmptyLine = true;
+
+        res.line('typeMeta.setGroups(groups);');
+
+        res.needEmptyLine = true;
+    }
+};
+
+// Generate metadata db fields.
+$generatorJava.metadataDatabaseFields = function (res, meta, fieldProperty) {
+    var dbFields = meta[fieldProperty];
+
+    if (dbFields && dbFields.length > 0) {
+        res.needEmptyLine = true;
+
+        $generatorJava.declareVariable(res, $generatorJava.needNewVariable(res, fieldProperty), fieldProperty, 'java.util.Collection', 'java.util.ArrayList', 'org.apache.ignite.cache.CacheTypeFieldMetadata');
+
+        _.forEach(dbFields, function (field) {
+            res.line(fieldProperty + '.add(new CacheTypeFieldMetadata(' +
+                '"' + field.databaseName + '", ' +
+                'java.sql.Types.' + field.databaseType + ', ' +
+                '"' + field.javaName + '", ' +
+                field.javaType + '.class'
+                + '));');
+        });
+
+        res.line('typeMeta.' + $commonUtils.toJavaName('set', fieldProperty) + '(' + fieldProperty + ');');
+
+        res.needEmptyLine = true;
+    }
+};
+
+// Generate metadata general group.
+$generatorJava.metadataGeneral = function (meta, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.classNameProperty(res, 'typeMeta', meta, 'keyType');
+    $generatorJava.classNameProperty(res, 'typeMeta', meta, 'valueType');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metadata for query group.
+$generatorJava.metadataQuery = function (meta, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.metadataQueryFields(res, meta, 'queryFields');
+    $generatorJava.metadataQueryFields(res, meta, 'ascendingFields');
+    $generatorJava.metadataQueryFields(res, meta, 'descendingFields');
+
+    $generatorJava.listProperty(res, 'typeMeta', meta, 'textFields');
+
+    $generatorJava.metadataGroups(res, meta);
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate metadata for store group.
+$generatorJava.metadataStore = function (meta, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.property(res, 'typeMeta', meta, 'databaseSchema');
+    $generatorJava.property(res, 'typeMeta', meta, 'databaseTable');
+
+    if (!$dataStructures.isJavaBuildInClass(meta.keyType))
+        $generatorJava.metadataDatabaseFields(res, meta, 'keyFields');
+
+    $generatorJava.metadataDatabaseFields(res, meta, 'valueFields');
+
+    res.needEmptyLine = true;
+
+    return res;
+};
+
+// Generate cache type metadata config.
+$generatorJava.cacheMetadata = function(meta, res) {
+    $generatorJava.declareVariable(res, $generatorJava.needNewVariable(res, 'typeMeta'), 'typeMeta', 'org.apache.ignite.cache.CacheTypeMetadata');
+
+    $generatorJava.metadataGeneral(meta, res);
+    $generatorJava.metadataQuery(meta, res);
+    $generatorJava.metadataStore(meta, res);
+
+    res.emptyLineIfNeeded();
+    res.line('types.add(typeMeta);');
+
+    res.needEmptyLine = true;
+};
+
+// Generate cache type metadata configs.
+$generatorJava.cacheMetadatas = function (metadatas, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    // Generate cache type metadata configs.
+    if (metadatas && metadatas.length > 0) {
+        $generatorJava.declareVariable(res, $generatorJava.needNewVariable(res, 'types'), 'types', 'java.util.Collection', 'java.util.ArrayList', 'org.apache.ignite.cache.CacheTypeMetadata');
+
+        _.forEach(metadatas, function (meta) {
+            $generatorJava.cacheMetadata(meta, res);
+        });
+
+        res.line(varName + '.setTypeMetadata(types);');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+// Generate cache configs.
+$generatorJava.cache = function(cache, varName, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    $generatorJava.cacheGeneral(cache, varName, res);
+
+    $generatorJava.cacheMemory(cache, varName, res);
+
+    $generatorJava.cacheQuery(cache, varName, res);
+
+    $generatorJava.cacheStore(cache, varName, res);
+
+    $generatorJava.cacheConcurrency(cache, varName, res);
+
+    $generatorJava.cacheRebalance(cache, varName, res);
+
+    $generatorJava.cacheServerNearCache(cache, varName, res);
+
+    $generatorJava.cacheStatistics(cache, varName, res);
+
+    $generatorJava.cacheMetadatas(cache.metadatas, varName, res);
+};
+
+// Generate cluster caches.
+$generatorJava.clusterCaches = function (caches, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (caches && caches.length > 0) {
+        res.emptyLineIfNeeded();
+
+        var names = [];
+
+        _.forEach(caches, function (cache) {
+            res.emptyLineIfNeeded();
+
+            var cacheName = $commonUtils.toJavaName('cache', cache.name);
+
+            $generatorJava.declareVariable(res, true, cacheName, 'org.apache.ignite.configuration.CacheConfiguration');
+
+            $generatorJava.cache(cache, cacheName, res);
+
+            names.push(cacheName);
+
+            res.needEmptyLine = true;
+        });
+
+        res.line('cfg.setCacheConfiguration(' + names.join(', ') + ');');
+
+        res.needEmptyLine = true;
+    }
+
+    return res;
+};
+
+/**
+ * Generate java class code.
+ *
+ * @param meta Metadata object.
+ * @param key If 'true' then key class should be generated.
+ * @param pkg Package name.
+ * @param useConstructor If 'true' then empty and full constructors should be generated.
+ * @param includeKeyFields If 'true' then include key fields into value POJO.
+ */
+$generatorJava.javaClassCode = function (meta, key, pkg, useConstructor, includeKeyFields, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    var type = (key ? meta.keyType : meta.valueType);
+    type = type.substring(type.lastIndexOf('.') + 1);
+
+    // Class comment.
+    res.line('/**');
+    res.line(' * ' + type + ' definition.');
+    res.line(' *');
+    res.line(' * ' + $generatorCommon.mainComment());
+    res.line(' */');
+
+    res.startBlock('public class ' + type + ' implements ' + res.importClass('java.io.Serializable') + ' {');
+
+    res.line('/** */');
+    res.line('private static final long serialVersionUID = 0L;');
+    res.needEmptyLine = true;
+
+    var fields = (key || includeKeyFields) ? meta.keyFields.slice() : [];
+
+    if (!key)
+        fields.push.apply(fields, meta.valueFields);
+
+    for (var fldIx = fields.length - 1; fldIx >= 0; fldIx --) {
+        var field = fields[fldIx];
+
+        var ix = _.findIndex(fields, function(fld) {
+            return fld.javaName == field.javaName;
+        });
+
+        if (ix >= 0 && ix < fldIx)
+            fields.splice(fldIx, 1);
+    }
+
+    // Generate fields declaration.
+    _.forEach(fields, function (field) {
+        var fldName = field.javaName;
+
+        res.line('/** Value for ' + fldName + '. */');
+
+        res.line('private ' + res.importClass(field.javaType) + ' ' + fldName + ';');
+
+        res.needEmptyLine = true;
+    });
+
+    // Generate constructors.
+    if (useConstructor) {
+        res.line('/**');
+        res.line(' * Empty constructor.');
+        res.line(' */');
+        res.startBlock('public ' + type + '() {');
+        res.line('// No-op.');
+        res.endBlock('}');
+
+        res.needEmptyLine = true;
+
+        res.line('/**');
+        res.line(' * Full constructor.');
+        res.line(' */');
+        res.startBlock('public ' + type + '(');
+
+        for (fldIx = 0; fldIx < fields.length; fldIx ++) {
+            field = fields[fldIx];
+
+            res.line(res.importClass(field.javaType) + ' ' + field.javaName + (fldIx < fields.length - 1 ? ',' : ''))
+        }
+
+        res.endBlock(') {');
+
+        res.startBlock();
+
+        _.forEach(fields, function (field) {
+            res.line('this.' + field.javaName +' = ' + field.javaName + ';');
+        });
+
+        res.endBlock('}');
+
+        res.needEmptyLine = true;
+    }
+
+    // Generate getters and setters methods.
+    _.forEach(fields, function (field) {
+        var fldName = field.javaName;
+
+        var fldType = res.importClass(field.javaType);
+
+        res.line('/**');
+        res.line(' * Gets ' + fldName + '.');
+        res.line(' *');
+        res.line(' * @return Value for ' + fldName + '.');
+        res.line(' */');
+        res.startBlock('public ' + fldType + ' ' + $commonUtils.toJavaName('get', fldName) + '() {');
+        res.line('return ' + fldName + ';');
+        res.endBlock('}');
+
+        res.needEmptyLine = true;
+
+        res.line('/**');
+        res.line(' * Sets ' + fldName + '.');
+        res.line(' *');
+        res.line(' * @param ' + fldName + ' New value for ' + fldName + '.');
+        res.line(' */');
+        res.startBlock('public void ' + $commonUtils.toJavaName('set', fldName) + '(' + fldType + ' ' + fldName + ') {');
+        res.line('this.' + fldName + ' = ' + fldName + ';');
+        res.endBlock('}');
+
+        res.needEmptyLine = true;
+    });
+
+    // Generate equals() method.
+    res.line('/** {@inheritDoc} */');
+    res.startBlock('@Override public boolean equals(Object o) {');
+    res.startBlock('if (this == o)');
+    res.line('return true;');
+    res.endBlock();
+    res.append('');
+
+    res.startBlock('if (!(o instanceof ' + type + '))');
+    res.line('return false;');
+    res.endBlock();
+
+    res.needEmptyLine = true;
+
+    res.line(type + ' that = (' + type + ')o;');
+
+    _.forEach(fields, function (field) {
+        res.needEmptyLine = true;
+
+        var javaName = field.javaName;
+
+        res.startBlock('if (' + javaName + ' != null ? !' + javaName + '.equals(that.' + javaName + ') : that.' + javaName + ' != null)');
+
+        res.line('return false;');
+        res.endBlock()
+    });
+
+    res.needEmptyLine = true;
+
+    res.line('return true;');
+    res.endBlock('}');
+
+    res.needEmptyLine = true;
+
+    // Generate hashCode() method.
+    res.line('/** {@inheritDoc} */');
+    res.startBlock('@Override public int hashCode() {');
+
+    var first = true;
+
+    _.forEach(fields, function (field) {
+        var javaName = field.javaName;
+
+        if (!first)
+            res.needEmptyLine = true;
+
+        res.line(first ? 'int res = ' + javaName + ' != null ? ' + javaName + '.hashCode() : 0;'
+            : 'res = 31 * res + (' + javaName + ' != null ? ' + javaName + '.hashCode() : 0);');
+
+        first = false;
+    });
+
+    res.needEmptyLine = true;
+    res.line('return res;');
+    res.endBlock('}');
+    res.needEmptyLine = true;
+
+    // Generate toString() method.
+    res.line('/** {@inheritDoc} */');
+    res.startBlock('@Override public String toString() {');
+
+    if (fields.length > 0) {
+        field = fields[0];
+
+        res.startBlock('return \"' + type + ' [' + field.javaName + '=\" + ' + field.javaName + ' +', type);
+
+        for (fldIx = 1; fldIx < fields.length; fldIx ++) {
+            field = fields[fldIx];
+
+            var javaName = field.javaName;
+
+            res.line('\", ' + javaName + '=\" + ' + field.javaName + ' +');
+        }
+    }
+
+    res.line('\']\';');
+    res.endBlock();
+    res.endBlock('}');
+
+    res.endBlock('}');
+
+    return 'package ' + pkg + ';' + '\n\n' + res.generateImports() + '\n\n' + res.asString();
+};
+
+/**
+ * Generate source code for type by its metadata.
+ *
+ * @param caches TODO.
+ */
+$generatorJava.pojos = function (caches, useConstructor, includeKeyFields) {
+    var metadataNames = [];
+
+    $generatorJava.metadatas = [];
+
+    for (var cacheIx = 0; cacheIx < caches.length; cacheIx ++) {
+        var cache = caches[cacheIx];
+
+        for (var metaIx = 0; metaIx < cache.metadatas.length; metaIx ++) {
+            var meta = cache.metadatas[metaIx];
+
+            // Skip already generated classes.
+            if (metadataNames.indexOf(meta.valueType) < 0) {
+                // Skip metadata without value fields.
+                if ($commonUtils.isDefined(meta.valueFields) && meta.valueFields.length > 0) {
+                    var metadata = {};
+
+                    // Key class generation only if key is not build in java class.
+                    if ($commonUtils.isDefined(meta.keyFields) && meta.keyFields.length > 0) {
+                        metadata.keyType = meta.keyType;
+                        metadata.keyClass = $generatorJava.javaClassCode(meta, true,
+                            meta.keyType.substring(0, meta.keyType.lastIndexOf('.')), useConstructor, includeKeyFields);
+                    }
+
+                    metadata.valueType = meta.valueType;
+                    metadata.valueClass = $generatorJava.javaClassCode(meta, false,
+                        meta.valueType.substring(0, meta.valueType.lastIndexOf('.')), useConstructor, includeKeyFields);
+
+                    $generatorJava.metadatas.push(metadata);
+                }
+
+                metadataNames.push(meta.valueType);
+            }
+        }
+    }
+};
+
+/**
+ * @param type Full type name.
+ * @return Field java type name.
+ */
+$generatorJava.javaTypeName = function(type) {
+    var ix = $generatorJava.javaBuildInClasses.indexOf(type);
+
+    var resType = ix >= 0 ? $generatorJava.javaBuildInFullNameClasses[ix] : type;
+
+    return resType.indexOf("java.lang.") >= 0 ? resType.substring(10) : resType;
+};
+
+/**
+ * Java code 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
+ */
+$generatorJava.clusterSsl = function(cluster, res) {
+    if (!res)
+        res = $generatorCommon.builder();
+
+    if (cluster.sslEnabled && $commonUtils.isDefined(cluster.sslContextFactory)) {
+        cluster.sslContextFactory.keyStorePassword =
+            ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.keyStoreFilePath)) ? '_Key_Storage_Password_' : undefined;
+
+        cluster.sslContextFactory.trustStorePassword = ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustStoreFilePath)) ?
+            '_Trust_Storage_Password_' : undefined;
+
+        var propsDesc = $commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustManagers) ?
+            $generatorCommon.SSL_CONFIGURATION_TRUST_MANAGER_FACTORY.fields :
+            $generatorCommon.SSL_CONFIGURATION_TRUST_FILE_FACTORY.fields;
+
+        $generatorJava.beanProperty(res, 'cfg', cluster.sslContextFactory, 'sslContextFactory', 'sslContextFactory',
+            'org.apache.ignite.ssl.SslContextFactory', propsDesc, false);
+    }
+
+    return res;
+};
+
+/**
+ * Function to generate java code for cluster configuration.
+ *
+ * @param cluster Cluster to process.
+ * @param javaClass If 'true' then generate factory class otherwise generate code snippet.
+ * @param clientNearCfg Near cache configuration for client node.
+ */
+$generatorJava.cluster = function (cluster, javaClass, clientNearCfg) {
+    var res = $generatorCommon.builder();
+
+    if (cluster) {
+        if (javaClass) {
+            res.line('/**');
+            res.line(' * ' + $generatorCommon.mainComment());
+            res.line(' */');
+            res.startBlock('public class IgniteConfigurationFactory {');
+            res.line('/**');
+            res.line(' * Configure grid.');
+            res.line(' */');
+            res.startBlock('public IgniteConfiguration createConfiguration() {');
+        }
+
+        $generatorJava.clusterGeneral(cluster, clientNearCfg, res);
+
+        $generatorJava.clusterAtomics(cluster, res);
+
+        $generatorJava.clusterCommunication(cluster, res);
+
+        $generatorJava.clusterDeployment(cluster, res);
+
+        $generatorJava.clusterEvents(cluster, res);
+
+        $generatorJava.clusterMarshaller(cluster, res);
+
+        $generatorJava.clusterMetrics(cluster, res);
+
+        $generatorJava.clusterP2p(cluster, res);
+
+        $generatorJava.clusterSwap(cluster, res);
+
+        $generatorJava.clusterTime(cluster, res);
+
+        $generatorJava.clusterPools(cluster, res);
+
+        $generatorJava.clusterTransactions(cluster, res);
+
+        $generatorJava.clusterCaches(cluster.caches, res);
+
+        $generatorJava.clusterSsl(cluster, res);
+
+        if (javaClass) {
+            res.line('return cfg;');
+            res.endBlock('}');
+            res.endBlock('}');
+
+            return res.generateImports() + '\n\n' + res.asString();
+        }
+    }
+
+    return res.asString();
+};
+
+// For server side we should export Java code generation entry point.
+if (typeof window === 'undefined') {
+    module.exports = $generatorJava;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/generator/generator-properties.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-properties.js b/modules/control-center-web/src/main/js/routes/generator/generator-properties.js
new file mode 100644
index 0000000..8fe6fa3
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-properties.js
@@ -0,0 +1,112 @@
+/*
+ *
+ *  * 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');
+
+    $generatorCommon = require('./generator-common');
+}
+
+// Properties generation entry point.
+$generatorProperties = {};
+
+/**
+ * Generate properties file with properties stubs for stores data sources.
+ *
+ * @param cluster Configuration to process.
+ * @returns {string} Generated content.
+ */
+$generatorProperties.dataSourcesProperties = function (cluster, res) {
+    var datasources = [];
+
+    if (cluster.caches && cluster.caches.length > 0) {
+        _.forEach(cluster.caches, function (cache) {
+            if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) {
+                var storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind];
+
+                if (storeFactory.dialect) {
+                    var beanId = storeFactory.dataSourceBean;
+
+                    if (!_.contains(datasources, beanId)) {
+                        datasources.push(beanId);
+
+                        if (!res) {
+                            res = $generatorCommon.builder();
+
+                            res.line('# ' + $generatorCommon.mainComment());
+                        }
+
+                        res.needEmptyLine = true;
+
+                        switch (storeFactory.dialect) {
+                            case 'DB2':
+                                res.line(beanId + '.jdbc.server_name=YOUR_JDBC_SERVER_NAME');
+                                res.line(beanId + '.jdbc.port_number=YOUR_JDBC_PORT_NUMBER');
+                                res.line(beanId + '.jdbc.database_name=YOUR_JDBC_DATABASE_TYPE');
+                                res.line(beanId + '.jdbc.driver_type=YOUR_JDBC_DRIVER_TYPE');
+                                break;
+
+                            default:
+                                res.line(beanId + '.jdbc.url=YOUR_JDBC_URL');
+                        }
+
+                        res.line(beanId + '.jdbc.username=YOUR_USER_NAME');
+                        res.line(beanId + '.jdbc.password=YOUR_PASSWORD');
+                        res.line();
+                    }
+                }
+            }
+        });
+    }
+
+    return res;
+};
+
+/**
+ * Generate properties file with properties stubs for cluster SSL configuration.
+ *
+ * @param cluster Cluster to get SSL configuration.
+ * @param res Optional configuration presentation builder object.
+ * @returns Configuration presentation builder object
+ */
+$generatorProperties.sslProperties = function (cluster, res) {
+    if (cluster.sslEnabled && cluster.sslContextFactory) {
+        if (!res) {
+            res = $generatorCommon.builder();
+
+            res.line('# ' + $generatorCommon.mainComment());
+        }
+
+        res.needEmptyLine = true;
+
+        if ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.keyStoreFilePath))
+            res.line('ssl.key.storage.password=YOUR_SSL_KEY_STORAGE_PASSWORD');
+
+        if ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustStoreFilePath))
+            res.line('ssl.trust.storage.password=YOUR_SSL_TRUST_STORAGE_PASSWORD');
+    }
+
+    return res;
+}
+
+// For server side we should export properties generation entry point.
+if (typeof window === 'undefined') {
+    module.exports = $generatorProperties;
+}


[16/18] ignite git commit: IGNITE-843 Split schema-import

Posted by an...@apache.org.
IGNITE-843 Split schema-import


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

Branch: refs/heads/ignite-843-rc1
Commit: bd50bdf8dc58beaff947596f0bb530647aac6313
Parents: bce0deb
Author: Andrey <an...@gridgain.com>
Authored: Tue Oct 13 10:06:50 2015 +0700
Committer: Andrey <an...@gridgain.com>
Committed: Tue Oct 13 10:06:50 2015 +0700

----------------------------------------------------------------------
 assembly/release-schema-import.xml              |   6 +-
 modules/schema-import-db/README.txt             |   4 +
 modules/schema-import-db/pom.xml                |  39 ++
 .../apache/ignite/schema/parser/DbColumn.java   |  78 +++
 .../ignite/schema/parser/DbMetadataReader.java  | 167 ++++++
 .../apache/ignite/schema/parser/DbTable.java    | 109 ++++
 .../parser/dialect/DB2MetadataDialect.java      |  35 ++
 .../parser/dialect/DatabaseMetadataDialect.java |  97 ++++
 .../parser/dialect/JdbcMetadataDialect.java     | 193 +++++++
 .../parser/dialect/MySQLMetadataDialect.java    |  64 +++
 .../parser/dialect/OracleMetadataDialect.java   | 360 +++++++++++++
 modules/schema-import/pom.xml                   |   8 +-
 .../schema/parser/DatabaseMetadataParser.java   |  45 +-
 .../apache/ignite/schema/parser/DbColumn.java   |  76 ---
 .../apache/ignite/schema/parser/DbTable.java    | 107 ----
 .../parser/dialect/DB2MetadataDialect.java      |  33 --
 .../parser/dialect/DatabaseMetadataDialect.java |  95 ----
 .../parser/dialect/JdbcMetadataDialect.java     | 191 -------
 .../parser/dialect/MySQLMetadataDialect.java    |  61 ---
 .../parser/dialect/OracleMetadataDialect.java   | 358 -------------
 .../ignite/schema/ui/SchemaImportApp.java       | 514 +++++++------------
 parent/pom.xml                                  |   3 +-
 pom.xml                                         |  40 +-
 23 files changed, 1400 insertions(+), 1283 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/assembly/release-schema-import.xml
----------------------------------------------------------------------
diff --git a/assembly/release-schema-import.xml b/assembly/release-schema-import.xml
index b746c83..c991c9e 100644
--- a/assembly/release-schema-import.xml
+++ b/assembly/release-schema-import.xml
@@ -17,11 +17,11 @@
   limitations under the License.
 -->
 
-<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
-          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
           xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
           http://maven.apache.org/xsd/assembly-1.1.2.xsd">
-    <id>scala</id>
+    <id>release-schema-import</id>
 
     <includeBaseDirectory>false</includeBaseDirectory>
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/README.txt
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/README.txt b/modules/schema-import-db/README.txt
new file mode 100644
index 0000000..556e1c6
--- /dev/null
+++ b/modules/schema-import-db/README.txt
@@ -0,0 +1,4 @@
+Apache Ignite Schema Import DB Module
+------------------------------------------
+
+Utility classes to extract database metadata.

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/pom.xml
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/pom.xml b/modules/schema-import-db/pom.xml
new file mode 100644
index 0000000..6d0dc2c
--- /dev/null
+++ b/modules/schema-import-db/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ /*
+  ~  * 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.
+  ~  */
+  -->
+
+<!--
+    POM file.
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent</relativePath>
+    </parent>
+
+    <artifactId>ignite-schema-import-db</artifactId>
+    <version>1.5.0-SNAPSHOT</version>
+
+</project>

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbColumn.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbColumn.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbColumn.java
new file mode 100644
index 0000000..8244144
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbColumn.java
@@ -0,0 +1,78 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.schema.parser;
+
+/**
+ * Database table column.
+ */
+public class DbColumn {
+    /** Column name. */
+    private final String name;
+
+    /** Column JDBC type. */
+    private final int type;
+
+    /** Is this column belongs to primary key. */
+    private final boolean key;
+
+    /** Is {@code NULL} allowed for column in database. */
+    private final boolean nullable;
+
+    /**
+     * @param name Column name.
+     * @param type Column JDBC type.
+     * @param key {@code true} if this column belongs to primary key.
+     * @param nullable {@code true} if {@code NULL } allowed for column in database.
+     */
+    public DbColumn(String name, int type, boolean key, boolean nullable) {
+        this.name = name;
+        this.type = type;
+        this.key = key;
+        this.nullable = nullable;
+    }
+
+    /**
+     * @return Column name.
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * @return Column JDBC type.
+     */
+    public int type() {
+        return type;
+    }
+
+    /**
+     * @return {@code true} if this column belongs to primary key.
+     */
+    public boolean key() {
+        return key;
+    }
+
+    /**
+     * @return nullable {@code true} if {@code NULL } allowed for column in database.
+     */
+    public boolean nullable() {
+        return nullable;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbMetadataReader.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbMetadataReader.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbMetadataReader.java
new file mode 100644
index 0000000..856f26c
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbMetadataReader.java
@@ -0,0 +1,167 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.schema.parser;
+
+import org.apache.ignite.schema.parser.dialect.*;
+
+import java.io.*;
+import java.net.*;
+import java.sql.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Singleton to extract database metadata.
+ */
+public class DbMetadataReader {
+    /** Logger. */
+    private static final Logger log = Logger.getLogger(DbMetadataReader.class.getName());
+
+    /** */
+    private static final DbMetadataReader INSTANCE = new DbMetadataReader();
+
+    /** */
+    private final Map<String, Driver> drivers = new HashMap<>();
+
+    /**
+     * Default constructor.
+     */
+    private DbMetadataReader() {
+        // No-op.
+    }
+
+    /**
+     * @return Instance.
+     */
+    public static DbMetadataReader getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Get specified dialect object for selected database.
+     *
+     * @param conn Connection to database.
+     * @return Specific dialect object.
+     */
+    private DatabaseMetadataDialect dialect(Connection conn) {
+        try {
+            String dbProductName = conn.getMetaData().getDatabaseProductName();
+
+            if ("Oracle".equals(dbProductName))
+                return new OracleMetadataDialect();
+            else if (dbProductName.startsWith("DB2/"))
+                return new DB2MetadataDialect();
+            else if (dbProductName.equals("MySQL"))
+                return new MySQLMetadataDialect();
+            else
+                return new JdbcMetadataDialect();
+        }
+        catch (SQLException e) {
+            log.log(Level.SEVERE, "Failed to resolve dialect (JdbcMetaDataDialect will be used.", e);
+
+            return new JdbcMetadataDialect();
+        }
+    }
+
+    /**
+     * Get list of schemas from database.
+     *
+     * @param conn Connection to database.
+     * @return List of schema names.
+     * @throws SQLException If schemas loading failed.
+     */
+    public Collection<String> schemas(Connection conn) throws SQLException  {
+        return dialect(conn).schemas(conn);
+    }
+
+    /**
+     * Extract DB metadata.
+     *
+     * @param conn Connection.
+     * @param schemas List of database schemas to process. In case of empty list all schemas will be processed.
+     * @param tblsOnly Tables only flag.
+     */
+    public Collection<DbTable> metadata(Connection conn, List<String> schemas, boolean tblsOnly) throws SQLException {
+        DatabaseMetadataDialect dialect;
+
+        try {
+            String dbProductName = conn.getMetaData().getDatabaseProductName();
+
+            if ("Oracle".equals(dbProductName))
+                dialect = new OracleMetadataDialect();
+            else if (dbProductName.startsWith("DB2/"))
+                dialect = new DB2MetadataDialect();
+            else
+                dialect = new JdbcMetadataDialect();
+        }
+        catch (SQLException e) {
+            log.log(Level.SEVERE, "Failed to resolve dialect (JdbcMetaDataDialect will be used.", e);
+
+            dialect = new JdbcMetadataDialect();
+        }
+
+        return dialect.tables(conn, schemas, tblsOnly);
+    }
+
+    /**
+     * Connect to database.
+     *
+     * @param jdbcDrvJarPath Path to JDBC driver.
+     * @param jdbcDrvCls JDBC class name.
+     * @param jdbcUrl JDBC connection URL.
+     * @param jdbcInfo Connection properties.
+     * @return Connection to database.
+     * @throws SQLException if connection failed.
+     */
+    public Connection connect(String jdbcDrvJarPath, String jdbcDrvCls, String jdbcUrl, Properties jdbcInfo)
+        throws SQLException {
+        Driver drv = drivers.get(jdbcDrvCls);
+
+        if (drv == null) {
+            if (jdbcDrvJarPath.isEmpty())
+                throw new IllegalStateException("Driver jar file name is not specified.");
+
+            File drvJar = new File(jdbcDrvJarPath);
+
+            if (!drvJar.exists())
+                throw new IllegalStateException("Driver jar file is not found.");
+
+            try {
+                URL u = new URL("jar:" + drvJar.toURI() + "!/");
+
+                URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {u});
+
+                drv = (Driver)Class.forName(jdbcDrvCls, true, ucl).newInstance();
+
+                drivers.put(jdbcDrvCls, drv);
+            }
+            catch (Exception e) {
+                throw new IllegalStateException(e);
+            }
+        }
+
+        Connection conn = drv.connect(jdbcUrl, jdbcInfo);
+
+        if (conn == null)
+            throw new IllegalStateException("Connection was not established (JDBC driver returned null value).");
+
+        return conn;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbTable.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbTable.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbTable.java
new file mode 100644
index 0000000..6ec9d1f
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/DbTable.java
@@ -0,0 +1,109 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.schema.parser;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Database table.
+ */
+public class DbTable {
+    /** Schema name. */
+    private final String schema;
+
+    /** Table name. */
+    private final String tbl;
+
+    /** Columns. */
+    private final Collection<DbColumn> cols;
+
+    /** Columns in ascending order. */
+    private final Set<String> ascCols;
+
+    /** Columns in descending order. */
+    private final Set<String> descCols;
+
+    /** Indexes. */
+    private final Map<String, Map<String, Boolean>> idxs;
+
+    /**
+     * Default columns.
+     *
+     * @param schema Schema name.
+     * @param tbl Table name.
+     * @param cols Columns.
+     * @param ascCols Columns in ascending order.
+     * @param descCols Columns in descending order.
+     * @param idxs Indexes;
+     */
+    public DbTable(String schema, String tbl, Collection<DbColumn> cols, Set<String> ascCols, Set<String> descCols,
+        Map<String, Map<String, Boolean>> idxs) {
+        this.schema = schema;
+        this.tbl = tbl;
+        this.cols = cols;
+        this.ascCols = ascCols;
+        this.descCols = descCols;
+        this.idxs = idxs;
+    }
+
+    /**
+     * @return Schema name.
+     */
+    public String schema() {
+        return schema;
+    }
+
+    /**
+     * @return Table name.
+     */
+    public String table() {
+        return tbl;
+    }
+
+    /**
+     * @return Columns.
+     */
+    public Collection<DbColumn> columns() {
+        return cols;
+    }
+
+    /**
+     * @return Fields in ascending order
+     */
+    public Set<String> ascendingColumns() {
+        return ascCols;
+    }
+
+    /**
+     * @return Fields in descending order
+     */
+    public Set<String> descendingColumns() {
+        return descCols;
+    }
+
+    /**
+     * @return Indexes.
+     */
+    public Map<String, Map<String, Boolean>> indexes() {
+        return idxs;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java
new file mode 100644
index 0000000..5c659a0
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java
@@ -0,0 +1,35 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.schema.parser.dialect;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * DB2 specific metadata dialect.
+ */
+public class DB2MetadataDialect extends JdbcMetadataDialect {
+    /** {@inheritDoc} */
+    @Override public Set<String> systemSchemas() {
+        return new HashSet<>(Arrays.asList("SYSIBM", "SYSCAT", "SYSSTAT", "SYSTOOLS", "SYSFUN", "SYSIBMADM",
+            "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java
new file mode 100644
index 0000000..db8adfd
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java
@@ -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.
+ *
+ */
+
+package org.apache.ignite.schema.parser.dialect;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.schema.parser.DbColumn;
+import org.apache.ignite.schema.parser.DbTable;
+
+/**
+ * Base class for database metadata dialect.
+ */
+public abstract class DatabaseMetadataDialect {
+    /**
+     * Gets schemas from database.
+     *
+     * @param conn Database connection.
+     * @return Collection of schema descriptors.
+     * @throws SQLException If failed to get schemas.
+     */
+    public abstract Collection<String> schemas(Connection conn) throws SQLException;
+
+    /**
+     * Gets tables from database.
+     *
+     * @param conn Database connection.
+     * @param schemas Collention of schema names to load.
+     * @param tblsOnly If {@code true} then gets only tables otherwise gets tables and views.
+     * @return Collection of table descriptors.
+     * @throws SQLException If failed to get tables.
+     */
+    public abstract Collection<DbTable> tables(Connection conn, List<String> schemas, boolean tblsOnly)
+        throws SQLException;
+
+    /**
+     * @return Collection of database system schemas.
+     */
+    public Set<String> systemSchemas() {
+        return Collections.singleton("INFORMATION_SCHEMA");
+    }
+
+    /**
+     * Create table descriptor.
+     *
+     * @param schema Schema name.
+     * @param tbl Table name.
+     * @param cols Table columns.
+     * @param idxs Table indexes.
+     * @return New {@code DbTable} instance.
+     */
+    protected DbTable table(String schema, String tbl, Collection<DbColumn> cols, Map<String, Map<String, Boolean>>idxs) {
+        Set<String> ascCols = new HashSet<>();
+
+        Set<String> descCols = new HashSet<>();
+
+        for (Map<String, Boolean> idx : idxs.values()) {
+            if (idx.size() == 1)
+                for (Map.Entry<String, Boolean> idxCol : idx.entrySet()) {
+                    String colName = idxCol.getKey();
+
+                    Boolean desc = idxCol.getValue();
+
+                    if (desc != null) {
+                        if (desc)
+                            descCols.add(colName);
+                        else
+                            ascCols.add(colName);
+                    }
+                }
+        }
+
+        return new DbTable(schema, tbl, cols, ascCols, descCols, idxs);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java
new file mode 100644
index 0000000..f315481
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java
@@ -0,0 +1,193 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.schema.parser.dialect;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.schema.parser.DbColumn;
+import org.apache.ignite.schema.parser.DbTable;
+
+/**
+ * Metadata dialect that uses standard JDBC for reading metadata.
+ */
+public class JdbcMetadataDialect extends DatabaseMetadataDialect {
+    /** */
+    private static final String[] TABLES_ONLY = {"TABLE"};
+
+    /** */
+    private static final String[] TABLES_AND_VIEWS = {"TABLE", "VIEW"};
+
+    /** Schema catalog index. */
+    private static final int TBL_CATALOG_IDX = 1;
+
+    /** Schema name index. */
+    private static final int TBL_SCHEMA_IDX = 2;
+
+    /** Table name index. */
+    private static final int TBL_NAME_IDX = 3;
+
+    /** Primary key column name index. */
+    private static final int PK_COL_NAME_IDX = 4;
+
+    /** Column name index. */
+    private static final int COL_NAME_IDX = 4;
+
+    /** Column data type index. */
+    private static final int COL_DATA_TYPE_IDX = 5;
+
+    /** Column nullable index. */
+    private static final int COL_NULLABLE_IDX = 11;
+
+    /** Index name index. */
+    private static final int IDX_NAME_IDX = 6;
+
+    /** Index column name index. */
+    private static final int IDX_COL_NAME_IDX = 9;
+
+    /** Index column descend index. */
+    private static final int IDX_ASC_OR_DESC_IDX = 10;
+
+    /** {@inheritDoc} */
+    @Override public Collection<String> schemas(Connection conn) throws SQLException {
+        Collection<String> schemas = new ArrayList<>();
+
+        ResultSet rs = conn.getMetaData().getSchemas();
+
+        Set<String> sys = systemSchemas();
+
+        while(rs.next()) {
+            String schema = rs.getString(1);
+
+            // Skip system schemas.
+            if (sys.contains(schema))
+                continue;
+
+            schemas.add(schema);
+        }
+
+        return schemas;
+    }
+
+    /**
+     * @return If {@code true} use catalogs for table division.
+     */
+    protected boolean useCatalog() {
+        return false;
+    }
+
+    /**
+     * @return If {@code true} use schemas for table division.
+     */
+    protected boolean useSchema() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<DbTable> tables(Connection conn, List<String> schemas, boolean tblsOnly)
+        throws SQLException {
+        DatabaseMetaData dbMeta = conn.getMetaData();
+
+        Set<String> sys = systemSchemas();
+
+        Collection<DbTable> tbls = new ArrayList<>();
+
+        if (schemas.size() == 0)
+            schemas.add(null);
+
+        for (String toSchema: schemas) {
+            try (ResultSet tblsRs = dbMeta.getTables(useCatalog() ? toSchema : null, useSchema() ? toSchema : null, "%",
+                    tblsOnly ? TABLES_ONLY : TABLES_AND_VIEWS)) {
+                while (tblsRs.next()) {
+                    String tblCatalog = tblsRs.getString(TBL_CATALOG_IDX);
+                    String tblSchema = tblsRs.getString(TBL_SCHEMA_IDX);
+                    String tblName = tblsRs.getString(TBL_NAME_IDX);
+
+                    // In case of MySql we should use catalog.
+                    String schema = tblSchema != null ? tblSchema : tblCatalog;
+
+                    // Skip system schemas.
+                    if (sys.contains(schema))
+                        continue;
+
+                    Set<String> pkCols = new HashSet<>();
+
+                    try (ResultSet pkRs = dbMeta.getPrimaryKeys(tblCatalog, tblSchema, tblName)) {
+                        while (pkRs.next())
+                            pkCols.add(pkRs.getString(PK_COL_NAME_IDX));
+                    }
+
+                    List<DbColumn> cols = new ArrayList<>();
+
+                    try (ResultSet colsRs = dbMeta.getColumns(tblCatalog, tblSchema, tblName, null)) {
+                        while (colsRs.next()) {
+                            String colName = colsRs.getString(COL_NAME_IDX);
+
+                            cols.add(new DbColumn(
+                                    colName,
+                                    colsRs.getInt(COL_DATA_TYPE_IDX),
+                                    pkCols.contains(colName),
+                                    colsRs.getInt(COL_NULLABLE_IDX) == DatabaseMetaData.columnNullable));
+                        }
+                    }
+
+                    Map<String, Map<String, Boolean>> idxs = new LinkedHashMap<>();
+
+                    try (ResultSet idxRs = dbMeta.getIndexInfo(tblCatalog, tblSchema, tblName, false, true)) {
+                        while (idxRs.next()) {
+                            String idxName = idxRs.getString(IDX_NAME_IDX);
+
+                            String colName = idxRs.getString(IDX_COL_NAME_IDX);
+
+                            if (idxName == null || colName == null)
+                                continue;
+
+                            Map<String, Boolean> idx = idxs.get(idxName);
+
+                            if (idx == null) {
+                                idx = new LinkedHashMap<>();
+
+                                idxs.put(idxName, idx);
+                            }
+
+                            String askOrDesc = idxRs.getString(IDX_ASC_OR_DESC_IDX);
+
+                            Boolean desc = askOrDesc != null ? "D".equals(askOrDesc) : null;
+
+                            idx.put(colName, desc);
+                        }
+                    }
+
+                    tbls.add(table(schema, tblName, cols, idxs));
+                }
+            }
+        }
+
+        return tbls;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java
new file mode 100644
index 0000000..f5e9259
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java
@@ -0,0 +1,64 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.schema.parser.dialect;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * MySQL specific metadata dialect.
+ */
+public class MySQLMetadataDialect extends JdbcMetadataDialect {
+    /** {@inheritDoc} */
+    @Override public Collection<String> schemas(Connection conn) throws SQLException {
+        List<String> schemas = new ArrayList<>();
+
+        ResultSet rs = conn.getMetaData().getCatalogs();
+
+        Set<String> sys = systemSchemas();
+
+        while(rs.next()) {
+            String schema = rs.getString(1);
+
+            // Skip system schemas.
+            if (sys.contains(schema))
+                continue;
+
+            schemas.add(schema);
+        }
+
+        return schemas;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean useCatalog() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected boolean useSchema() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java
new file mode 100644
index 0000000..3f10672
--- /dev/null
+++ b/modules/schema-import-db/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java
@@ -0,0 +1,360 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.schema.parser.dialect;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.schema.parser.DbColumn;
+import org.apache.ignite.schema.parser.DbTable;
+
+import static java.sql.Types.BIGINT;
+import static java.sql.Types.BLOB;
+import static java.sql.Types.BOOLEAN;
+import static java.sql.Types.CHAR;
+import static java.sql.Types.CLOB;
+import static java.sql.Types.DATE;
+import static java.sql.Types.DOUBLE;
+import static java.sql.Types.FLOAT;
+import static java.sql.Types.INTEGER;
+import static java.sql.Types.LONGVARBINARY;
+import static java.sql.Types.LONGVARCHAR;
+import static java.sql.Types.NUMERIC;
+import static java.sql.Types.OTHER;
+import static java.sql.Types.SMALLINT;
+import static java.sql.Types.SQLXML;
+import static java.sql.Types.TIMESTAMP;
+import static java.sql.Types.TINYINT;
+import static java.sql.Types.VARCHAR;
+
+/**
+ * Oracle specific metadata dialect.
+ */
+public class OracleMetadataDialect extends DatabaseMetadataDialect {
+    /** SQL to get columns metadata. */
+    private static final String SQL_COLUMNS = "SELECT a.owner, a.table_name, a.column_name, a.nullable," +
+        " a.data_type, a.data_precision, a.data_scale " +
+        "FROM all_tab_columns a %s " +
+        " %s " +
+        " ORDER BY a.owner, a.table_name, a.column_id";
+
+    /** SQL to get list of PRIMARY KEYS columns. */
+    private static final String SQL_PRIMARY_KEYS = "SELECT b.column_name" +
+        " FROM all_constraints a" +
+        "  INNER JOIN all_cons_columns b ON a.owner = b.owner AND a.constraint_name = b.constraint_name" +
+        " WHERE a.owner = ? and a.table_name = ? AND a.constraint_type = 'P'";
+
+    /** SQL to get indexes metadata. */
+    private static final String SQL_INDEXES = "SELECT i.index_name, u.column_expression, i.column_name, i.descend" +
+        " FROM all_ind_columns i" +
+        " LEFT JOIN user_ind_expressions u on u.index_name = i.index_name and i.table_name = u.table_name" +
+        " WHERE i.index_owner = ? and i.table_name = ?" +
+        " ORDER BY i.index_name, i.column_position";
+
+    /** Owner index. */
+    private static final int OWNER_IDX = 1;
+
+    /** Table name index. */
+    private static final int TBL_NAME_IDX = 2;
+
+    /** Column name index. */
+    private static final int COL_NAME_IDX = 3;
+
+    /** Nullable index. */
+    private static final int NULLABLE_IDX = 4;
+
+    /** Data type index. */
+    private static final int DATA_TYPE_IDX = 5;
+
+    /** Numeric precision index. */
+    private static final int DATA_PRECISION_IDX = 6;
+
+    /** Numeric scale index. */
+    private static final int DATA_SCALE_IDX = 7;
+
+    /** Index name index. */
+    private static final int IDX_NAME_IDX = 1;
+
+    /** Index name index. */
+    private static final int IDX_EXPR_IDX = 2;
+
+    /** Index column name index. */
+    private static final int IDX_COL_NAME_IDX = 3;
+
+    /** Index column sort order index. */
+    private static final int IDX_COL_DESCEND_IDX = 4;
+
+    /** {@inheritDoc} */
+    @Override public Set<String> systemSchemas() {
+        return new HashSet<>(Arrays.asList("ANONYMOUS", "CTXSYS", "DBSNMP", "EXFSYS", "LBACSYS", "MDSYS", "MGMT_VIEW",
+            "OLAPSYS", "OWBSYS", "ORDPLUGINS", "ORDSYS", "OUTLN", "SI_INFORMTN_SCHEMA", "SYS", "SYSMAN", "SYSTEM",
+            "TSMSYS", "WK_TEST", "WKSYS", "WKPROXY", "WMSYS", "XDB",
+
+            "APEX_040000", "APEX_PUBLIC_USER", "DIP", "FLOWS_30000", "FLOWS_FILES", "MDDATA", "ORACLE_OCM",
+            "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "XS$NULL",
+
+            "BI", "HR", "OE", "PM", "IX", "SH"));
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<String> schemas(Connection conn) throws SQLException {
+        Collection<String> schemas = new ArrayList<>();
+
+        ResultSet rs = conn.getMetaData().getSchemas();
+
+        Set<String> sysSchemas = systemSchemas();
+
+        while(rs.next()) {
+            String schema = rs.getString(1);
+
+            if (!sysSchemas.contains(schema) && !schema.startsWith("FLOWS_"))
+                schemas.add(schema);
+        }
+
+        return schemas;
+    }
+
+    /**
+     * @param rs Result set with column type metadata from Oracle database.
+     * @return JDBC type.
+     * @throws SQLException If failed to decode type.
+     */
+    private int decodeType(ResultSet rs) throws SQLException {
+        String type = rs.getString(DATA_TYPE_IDX);
+
+        if (type.startsWith("TIMESTAMP"))
+            return TIMESTAMP;
+        else {
+            switch (type) {
+                case "CHAR":
+                case "NCHAR":
+                    return CHAR;
+
+                case "VARCHAR2":
+                case "NVARCHAR2":
+                    return VARCHAR;
+
+                case "LONG":
+                    return LONGVARCHAR;
+
+                case "LONG RAW":
+                    return LONGVARBINARY;
+
+                case "FLOAT":
+                    return FLOAT;
+
+                case "NUMBER":
+                    int precision = rs.getInt(DATA_PRECISION_IDX);
+                    int scale = rs.getInt(DATA_SCALE_IDX);
+
+                    if (scale > 0) {
+                        if (scale < 4 && precision < 19)
+                            return FLOAT;
+
+                        if (scale > 4 || precision > 19)
+                            return DOUBLE;
+
+                        return NUMERIC;
+                    }
+                    else {
+                        if (precision < 1)
+                            return INTEGER;
+
+                        if (precision < 2)
+                            return BOOLEAN;
+
+                        if (precision < 4)
+                            return TINYINT;
+
+                        if (precision < 6)
+                            return SMALLINT;
+
+                        if (precision < 11)
+                            return INTEGER;
+
+                        if (precision < 20)
+                            return BIGINT;
+
+                        return NUMERIC;
+                    }
+
+                case "DATE":
+                    return DATE;
+
+                case "BFILE":
+                case "BLOB":
+                    return BLOB;
+
+                case "CLOB":
+                case "NCLOB":
+                    return CLOB;
+
+                case "XMLTYPE":
+                    return SQLXML;
+            }
+        }
+
+        return OTHER;
+    }
+
+    /**
+     * Retrieve primary key columns.
+     *
+     * @param stmt Prepared SQL statement to execute.
+     * @param owner DB owner.
+     * @param tbl Table name.
+     * @return Primary key columns.
+     * @throws SQLException If failed to retrieve primary key columns.
+     */
+    private Set<String> primaryKeys(PreparedStatement stmt, String owner, String tbl) throws SQLException {
+        Set<String> pkCols = new HashSet<>();
+
+        stmt.setString(1, owner);
+        stmt.setString(2, tbl);
+
+        try (ResultSet pkRs = stmt.executeQuery()) {
+            while(pkRs.next())
+                pkCols.add(pkRs.getString(1));
+        }
+
+        return pkCols;
+    }
+
+    /**
+     * Retrieve index columns.
+     *
+     * @param stmt Prepared SQL statement to execute.
+     * @param owner DB owner.
+     * @param tbl Table name.
+     * @return Index columns.
+     * @throws SQLException If failed to retrieve indexes columns.
+     */
+    private Map<String, Map<String, Boolean>> indexes(PreparedStatement stmt, String owner, String tbl)
+        throws SQLException {
+        Map<String, Map<String, Boolean>> idxs = new LinkedHashMap<>();
+
+        stmt.setString(1, owner);
+        stmt.setString(2, tbl);
+
+        try (ResultSet idxsRs = stmt.executeQuery()) {
+            while (idxsRs.next()) {
+                String idxName = idxsRs.getString(IDX_NAME_IDX);
+
+                Map<String, Boolean> idx = idxs.get(idxName);
+
+                if (idx == null) {
+                    idx = new LinkedHashMap<>();
+
+                    idxs.put(idxName, idx);
+                }
+
+                String expr = idxsRs.getString(IDX_EXPR_IDX);
+
+                String col = expr == null ? idxsRs.getString(IDX_COL_NAME_IDX) : expr.replaceAll("\"", "");
+
+                idx.put(col, "DESC".equals(idxsRs.getString(IDX_COL_DESCEND_IDX)));
+            }
+        }
+
+        return idxs;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<DbTable> tables(Connection conn, List<String> schemas, boolean tblsOnly)
+        throws SQLException {
+        Collection<DbTable> tbls = new ArrayList<>();
+
+        PreparedStatement pkStmt = conn.prepareStatement(SQL_PRIMARY_KEYS);
+
+        PreparedStatement idxStmt = conn.prepareStatement(SQL_INDEXES);
+
+        if (schemas.size() == 0)
+            schemas.add(null);
+
+        Set<String> sysSchemas = systemSchemas();
+
+        try (Statement colsStmt = conn.createStatement()) {
+            for (String schema: schemas) {
+                if (systemSchemas().contains(schema) || (schema != null && schema.startsWith("FLOWS_")))
+                    continue;
+
+                Collection<DbColumn> cols = new ArrayList<>();
+
+                Set<String> pkCols = Collections.emptySet();
+                Map<String, Map<String, Boolean>> idxs = Collections.emptyMap();
+
+                String sql = String.format(SQL_COLUMNS,
+                        tblsOnly ? "INNER JOIN all_tables b on a.table_name = b.table_name and a.owner = b.owner" : "",
+                        schema != null ? String.format(" WHERE a.owner = '%s' ", schema) : "");
+
+                try (ResultSet colsRs = colsStmt.executeQuery(sql)) {
+                    String prevSchema = "";
+                    String prevTbl = "";
+
+                    boolean first = true;
+
+                    while (colsRs.next()) {
+                        String owner = colsRs.getString(OWNER_IDX);
+                        String tbl = colsRs.getString(TBL_NAME_IDX);
+
+                        if (sysSchemas.contains(owner) || (schema != null && schema.startsWith("FLOWS_")))
+                            continue;
+
+                        boolean changed = !owner.equals(prevSchema) || !tbl.equals(prevTbl);
+
+                        if (changed) {
+                            if (first)
+                                first = false;
+                            else
+                                tbls.add(table(prevSchema, prevTbl, cols, idxs));
+
+                            prevSchema = owner;
+                            prevTbl = tbl;
+                            cols = new ArrayList<>();
+                            pkCols = primaryKeys(pkStmt, owner, tbl);
+                            idxs = indexes(idxStmt, owner, tbl);
+                        }
+
+                        String colName = colsRs.getString(COL_NAME_IDX);
+
+                        cols.add(new DbColumn(colName, decodeType(colsRs), pkCols.contains(colName),
+                                !"N".equals(colsRs.getString(NULLABLE_IDX))));
+                    }
+
+                    if (!cols.isEmpty())
+                        tbls.add(table(prevSchema, prevTbl, cols, idxs));
+                }
+            }
+        }
+
+        return tbls;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/pom.xml
----------------------------------------------------------------------
diff --git a/modules/schema-import/pom.xml b/modules/schema-import/pom.xml
index 1e63cae..06832c2 100644
--- a/modules/schema-import/pom.xml
+++ b/modules/schema-import/pom.xml
@@ -20,7 +20,7 @@
 <!--
     POM file.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
@@ -42,6 +42,12 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-schema-import-db</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
             <version>1.3.175</version>

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java
index 3ecee53..940fd04 100644
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java
+++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DatabaseMetadataParser.java
@@ -38,48 +38,23 @@ import org.apache.ignite.schema.parser.dialect.JdbcMetadataDialect;
 import org.apache.ignite.schema.parser.dialect.MySQLMetadataDialect;
 import org.apache.ignite.schema.parser.dialect.OracleMetadataDialect;
 
+import java.sql.*;
+import java.util.*;
+import java.util.logging.*;
+
 /**
  * Database metadata parser.
  */
 public class DatabaseMetadataParser {
-    /** Logger. */
-    private static final Logger log = Logger.getLogger(DatabaseMetadataParser.class.getName());
-
-    /**
-     * Get specified dialect object for selected database.
-     *
-     * @param conn Connection to database.
-     * @return Specific dialect object.
-     */
-    private static DatabaseMetadataDialect dialect(Connection conn) {
-        try {
-            String dbProductName = conn.getMetaData().getDatabaseProductName();
-
-            if ("Oracle".equals(dbProductName))
-                return new OracleMetadataDialect();
-            else if (dbProductName.startsWith("DB2/"))
-                return new DB2MetadataDialect();
-            else if (dbProductName.equals("MySQL"))
-                return new MySQLMetadataDialect();
-            else
-                return new JdbcMetadataDialect();
-        }
-        catch (SQLException e) {
-            log.log(Level.SEVERE, "Failed to resolve dialect (JdbcMetaDataDialect will be used.", e);
-
-            return new JdbcMetadataDialect();
-        }
-    }
-
     /**
      * Get list of schemas from database.
      *
      * @param conn Connection to database.
      * @return List of schema descriptors.
-     * @throws SQLException If shemas loading failed.
+     * @throws SQLException If schemas loading failed.
      */
     public static ObservableList<SchemaDescriptor> schemas(Connection conn) throws SQLException  {
-        List<String> dbSchemas = dialect(conn).schemas(conn);
+        Collection<String> dbSchemas = DbMetadataReader.getInstance().schemas(conn);
 
         List<SchemaDescriptor> uiSchemas = new ArrayList<>(dbSchemas.size());
 
@@ -93,20 +68,18 @@ public class DatabaseMetadataParser {
      * Parse database metadata.
      *
      * @param conn Connection to database.
-     * @param schemas Collention of schema names to load.
+     * @param schemas Collection of schema names to process.
      * @param tblsOnly If {@code true} then process tables only else process tables and views.
      * @return Collection of POJO descriptors.
      * @throws SQLException If parsing failed.
      */
     public static ObservableList<PojoDescriptor> parse(Connection conn, List<String> schemas, boolean tblsOnly)
         throws SQLException {
-        DatabaseMetadataDialect dialect = dialect(conn);
-
         Map<String, PojoDescriptor> parents = new HashMap<>();
 
         Map<String, Collection<PojoDescriptor>> childrens = new HashMap<>();
 
-        for (DbTable tbl : dialect.tables(conn, schemas, tblsOnly)) {
+        for (DbTable tbl : DbMetadataReader.getInstance().metadata(conn, schemas, tblsOnly)) {
             String schema = tbl.schema();
 
             PojoDescriptor parent = parents.get(schema);
@@ -148,4 +121,4 @@ public class DatabaseMetadataParser {
 
         return FXCollections.observableList(res);
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java
deleted file mode 100644
index 9a548f4..0000000
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbColumn.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.ignite.schema.parser;
-
-/**
- * Database table column.
- */
-public class DbColumn {
-    /** Column name. */
-    private final String name;
-
-    /** Column JDBC type. */
-    private final int type;
-
-    /** Is this column belongs to primary key. */
-    private final boolean key;
-
-    /** Is {@code NULL} allowed for column in database. */
-    private final boolean nullable;
-
-    /**
-     * @param name Column name.
-     * @param type Column JDBC type.
-     * @param key {@code true} if this column belongs to primary key.
-     * @param nullable {@code true} if {@code NULL } allowed for column in database.
-     */
-    public DbColumn(String name, int type, boolean key, boolean nullable) {
-        this.name = name;
-        this.type = type;
-        this.key = key;
-        this.nullable = nullable;
-    }
-
-    /**
-     * @return Column name.
-     */
-    public String name() {
-        return name;
-    }
-
-    /**
-     * @return Column JDBC type.
-     */
-    public int type() {
-        return type;
-    }
-
-    /**
-     * @return {@code true} if this column belongs to primary key.
-     */
-    public boolean key() {
-        return key;
-    }
-
-    /**
-     * @return nullable {@code true} if {@code NULL } allowed for column in database.
-     */
-    public boolean nullable() {
-        return nullable;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java
deleted file mode 100644
index c54bfd8..0000000
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/DbTable.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.ignite.schema.parser;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Database table.
- */
-public class DbTable {
-    /** Schema name. */
-    private final String schema;
-
-    /** Table name. */
-    private final String tbl;
-
-    /** Columns. */
-    private final Collection<DbColumn> cols;
-
-    /** Columns in ascending order. */
-    private final Set<String> ascCols;
-
-    /** Columns in descending order. */
-    private final Set<String> descCols;
-
-    /** Indexes. */
-    private final Map<String, Map<String, Boolean>> idxs;
-
-    /**
-     * Default columns.
-     *
-     * @param schema Schema name.
-     * @param tbl Table name.
-     * @param cols Columns.
-     * @param ascCols Columns in ascending order.
-     * @param descCols Columns in descending order.
-     * @param idxs Indexes;
-     */
-    public DbTable(String schema, String tbl, Collection<DbColumn> cols, Set<String> ascCols, Set<String> descCols,
-        Map<String, Map<String, Boolean>> idxs) {
-        this.schema = schema;
-        this.tbl = tbl;
-        this.cols = cols;
-        this.ascCols = ascCols;
-        this.descCols = descCols;
-        this.idxs = idxs;
-    }
-
-    /**
-     * @return Schema name.
-     */
-    public String schema() {
-        return schema;
-    }
-
-    /**
-     * @return Table name.
-     */
-    public String table() {
-        return tbl;
-    }
-
-    /**
-     * @return Columns.
-     */
-    public Collection<DbColumn> columns() {
-        return cols;
-    }
-
-    /**
-     * @return Fields in ascending order
-     */
-    public Set<String> ascendingColumns() {
-        return ascCols;
-    }
-
-    /**
-     * @return Fields in descending order
-     */
-    public Set<String> descendingColumns() {
-        return descCols;
-    }
-
-    /**
-     * @return Indexes.
-     */
-    public Map<String, Map<String, Boolean>> indexes() {
-        return idxs;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java
deleted file mode 100644
index b191ec3..0000000
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DB2MetadataDialect.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.ignite.schema.parser.dialect;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * DB2 specific metadata dialect.
- */
-public class DB2MetadataDialect extends JdbcMetadataDialect {
-    /** {@inheritDoc} */
-    @Override public Set<String> systemSchemas() {
-        return new HashSet<>(Arrays.asList("SYSIBM", "SYSCAT", "SYSSTAT", "SYSTOOLS", "SYSFUN", "SYSIBMADM",
-            "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC"));
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java
deleted file mode 100644
index 5d3a0fd..0000000
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/DatabaseMetadataDialect.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.ignite.schema.parser.dialect;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.ignite.schema.parser.DbColumn;
-import org.apache.ignite.schema.parser.DbTable;
-
-/**
- * Base class for database metadata dialect.
- */
-public abstract class DatabaseMetadataDialect {
-    /**
-     * Gets schemas from database.
-     *
-     * @param conn Database connection.
-     * @return Collection of schema descriptors.
-     * @throws SQLException If failed to get schemas.
-     */
-    public abstract List<String> schemas(Connection conn) throws SQLException;
-
-    /**
-     * Gets tables from database.
-     *
-     * @param conn Database connection.
-     * @param schemas Collention of schema names to load.
-     * @param tblsOnly If {@code true} then gets only tables otherwise gets tables and views.
-     * @return Collection of table descriptors.
-     * @throws SQLException If failed to get tables.
-     */
-    public abstract Collection<DbTable> tables(Connection conn, List<String> schemas, boolean tblsOnly)
-        throws SQLException;
-
-    /**
-     * @return Collection of database system schemas.
-     */
-    public Set<String> systemSchemas() {
-        return Collections.singleton("INFORMATION_SCHEMA");
-    }
-
-    /**
-     * Create table descriptor.
-     *
-     * @param schema Schema name.
-     * @param tbl Table name.
-     * @param cols Table columns.
-     * @param idxs Table indexes.
-     * @return New {@code DbTable} instance.
-     */
-    protected DbTable table(String schema, String tbl, Collection<DbColumn> cols, Map<String, Map<String, Boolean>>idxs) {
-        Set<String> ascCols = new HashSet<>();
-
-        Set<String> descCols = new HashSet<>();
-
-        for (Map<String, Boolean> idx : idxs.values()) {
-            if (idx.size() == 1)
-                for (Map.Entry<String, Boolean> idxCol : idx.entrySet()) {
-                    String colName = idxCol.getKey();
-
-                    Boolean desc = idxCol.getValue();
-
-                    if (desc != null) {
-                        if (desc)
-                            descCols.add(colName);
-                        else
-                            ascCols.add(colName);
-                    }
-                }
-        }
-
-        return new DbTable(schema, tbl, cols, ascCols, descCols, idxs);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java
deleted file mode 100644
index 6f41195..0000000
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/JdbcMetadataDialect.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.ignite.schema.parser.dialect;
-
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.ignite.schema.parser.DbColumn;
-import org.apache.ignite.schema.parser.DbTable;
-
-/**
- * Metadata dialect that uses standard JDBC for reading metadata.
- */
-public class JdbcMetadataDialect extends DatabaseMetadataDialect {
-    /** */
-    private static final String[] TABLES_ONLY = {"TABLE"};
-
-    /** */
-    private static final String[] TABLES_AND_VIEWS = {"TABLE", "VIEW"};
-
-    /** Schema catalog index. */
-    private static final int TBL_CATALOG_IDX = 1;
-
-    /** Schema name index. */
-    private static final int TBL_SCHEMA_IDX = 2;
-
-    /** Table name index. */
-    private static final int TBL_NAME_IDX = 3;
-
-    /** Primary key column name index. */
-    private static final int PK_COL_NAME_IDX = 4;
-
-    /** Column name index. */
-    private static final int COL_NAME_IDX = 4;
-
-    /** Column data type index. */
-    private static final int COL_DATA_TYPE_IDX = 5;
-
-    /** Column nullable index. */
-    private static final int COL_NULLABLE_IDX = 11;
-
-    /** Index name index. */
-    private static final int IDX_NAME_IDX = 6;
-
-    /** Index column name index. */
-    private static final int IDX_COL_NAME_IDX = 9;
-
-    /** Index column descend index. */
-    private static final int IDX_ASC_OR_DESC_IDX = 10;
-
-    /** {@inheritDoc} */
-    @Override public List<String> schemas(Connection conn) throws SQLException {
-        List<String> schemas = new ArrayList<>();
-
-        ResultSet rs = conn.getMetaData().getSchemas();
-
-        Set<String> sys = systemSchemas();
-
-        while(rs.next()) {
-            String schema = rs.getString(1);
-
-            // Skip system schemas.
-            if (sys.contains(schema))
-                continue;
-
-            schemas.add(schema);
-        }
-
-        return schemas;
-    }
-
-    /**
-     * @return If {@code true} use catalogs for table division.
-     */
-    protected boolean useCatalog() {
-        return false;
-    }
-
-    /**
-     * @return If {@code true} use schemas for table division.
-     */
-    protected boolean useSchema() {
-        return true;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Collection<DbTable> tables(Connection conn, List<String> schemas, boolean tblsOnly)
-        throws SQLException {
-        DatabaseMetaData dbMeta = conn.getMetaData();
-
-        Set<String> sys = systemSchemas();
-
-        Collection<DbTable> tbls = new ArrayList<>();
-
-        if (schemas.size() == 0)
-            schemas.add(null);
-
-        for (String toSchema: schemas) {
-            try (ResultSet tblsRs = dbMeta.getTables(useCatalog() ? toSchema : null, useSchema() ? toSchema : null, "%",
-                    tblsOnly ? TABLES_ONLY : TABLES_AND_VIEWS)) {
-                while (tblsRs.next()) {
-                    String tblCatalog = tblsRs.getString(TBL_CATALOG_IDX);
-                    String tblSchema = tblsRs.getString(TBL_SCHEMA_IDX);
-                    String tblName = tblsRs.getString(TBL_NAME_IDX);
-
-                    // In case of MySql we should use catalog.
-                    String schema = tblSchema != null ? tblSchema : tblCatalog;
-
-                    // Skip system schemas.
-                    if (sys.contains(schema))
-                        continue;
-
-                    Set<String> pkCols = new HashSet<>();
-
-                    try (ResultSet pkRs = dbMeta.getPrimaryKeys(tblCatalog, tblSchema, tblName)) {
-                        while (pkRs.next())
-                            pkCols.add(pkRs.getString(PK_COL_NAME_IDX));
-                    }
-
-                    List<DbColumn> cols = new ArrayList<>();
-
-                    try (ResultSet colsRs = dbMeta.getColumns(tblCatalog, tblSchema, tblName, null)) {
-                        while (colsRs.next()) {
-                            String colName = colsRs.getString(COL_NAME_IDX);
-
-                            cols.add(new DbColumn(
-                                    colName,
-                                    colsRs.getInt(COL_DATA_TYPE_IDX),
-                                    pkCols.contains(colName),
-                                    colsRs.getInt(COL_NULLABLE_IDX) == DatabaseMetaData.columnNullable));
-                        }
-                    }
-
-                    Map<String, Map<String, Boolean>> idxs = new LinkedHashMap<>();
-
-                    try (ResultSet idxRs = dbMeta.getIndexInfo(tblCatalog, tblSchema, tblName, false, true)) {
-                        while (idxRs.next()) {
-                            String idxName = idxRs.getString(IDX_NAME_IDX);
-
-                            String colName = idxRs.getString(IDX_COL_NAME_IDX);
-
-                            if (idxName == null || colName == null)
-                                continue;
-
-                            Map<String, Boolean> idx = idxs.get(idxName);
-
-                            if (idx == null) {
-                                idx = new LinkedHashMap<>();
-
-                                idxs.put(idxName, idx);
-                            }
-
-                            String askOrDesc = idxRs.getString(IDX_ASC_OR_DESC_IDX);
-
-                            Boolean desc = askOrDesc != null ? "D".equals(askOrDesc) : null;
-
-                            idx.put(colName, desc);
-                        }
-                    }
-
-                    tbls.add(table(schema, tblName, cols, idxs));
-                }
-            }
-        }
-
-        return tbls;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java
deleted file mode 100644
index 090925e..0000000
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/MySQLMetadataDialect.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.ignite.schema.parser.dialect;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * MySQL specific metadata dialect.
- */
-public class MySQLMetadataDialect extends JdbcMetadataDialect {
-    /** {@inheritDoc} */
-    @Override public List<String> schemas(Connection conn) throws SQLException {
-        List<String> schemas = new ArrayList<>();
-
-        ResultSet rs = conn.getMetaData().getCatalogs();
-
-        Set<String> sys = systemSchemas();
-
-        while(rs.next()) {
-            String schema = rs.getString(1);
-
-            // Skip system schemas.
-            if (sys.contains(schema))
-                continue;
-
-            schemas.add(schema);
-        }
-
-        return schemas;
-    }
-
-    /** {@inheritDoc} */
-    @Override protected boolean useCatalog() {
-        return true;
-    }
-
-    /** {@inheritDoc} */
-    @Override protected boolean useSchema() {
-        return false;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java
deleted file mode 100644
index cf7e979..0000000
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/parser/dialect/OracleMetadataDialect.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * 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.
- */
-
-package org.apache.ignite.schema.parser.dialect;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.ignite.schema.parser.DbColumn;
-import org.apache.ignite.schema.parser.DbTable;
-
-import static java.sql.Types.BIGINT;
-import static java.sql.Types.BLOB;
-import static java.sql.Types.BOOLEAN;
-import static java.sql.Types.CHAR;
-import static java.sql.Types.CLOB;
-import static java.sql.Types.DATE;
-import static java.sql.Types.DOUBLE;
-import static java.sql.Types.FLOAT;
-import static java.sql.Types.INTEGER;
-import static java.sql.Types.LONGVARBINARY;
-import static java.sql.Types.LONGVARCHAR;
-import static java.sql.Types.NUMERIC;
-import static java.sql.Types.OTHER;
-import static java.sql.Types.SMALLINT;
-import static java.sql.Types.SQLXML;
-import static java.sql.Types.TIMESTAMP;
-import static java.sql.Types.TINYINT;
-import static java.sql.Types.VARCHAR;
-
-/**
- * Oracle specific metadata dialect.
- */
-public class OracleMetadataDialect extends DatabaseMetadataDialect {
-    /** SQL to get columns metadata. */
-    private static final String SQL_COLUMNS = "SELECT a.owner, a.table_name, a.column_name, a.nullable," +
-        " a.data_type, a.data_precision, a.data_scale " +
-        "FROM all_tab_columns a %s " +
-        " %s " +
-        " ORDER BY a.owner, a.table_name, a.column_id";
-
-    /** SQL to get list of PRIMARY KEYS columns. */
-    private static final String SQL_PRIMARY_KEYS = "SELECT b.column_name" +
-        " FROM all_constraints a" +
-        "  INNER JOIN all_cons_columns b ON a.owner = b.owner AND a.constraint_name = b.constraint_name" +
-        " WHERE a.owner = ? and a.table_name = ? AND a.constraint_type = 'P'";
-
-    /** SQL to get indexes metadata. */
-    private static final String SQL_INDEXES = "SELECT i.index_name, u.column_expression, i.column_name, i.descend" +
-        " FROM all_ind_columns i" +
-        " LEFT JOIN user_ind_expressions u on u.index_name = i.index_name and i.table_name = u.table_name" +
-        " WHERE i.index_owner = ? and i.table_name = ?" +
-        " ORDER BY i.index_name, i.column_position";
-
-    /** Owner index. */
-    private static final int OWNER_IDX = 1;
-
-    /** Table name index. */
-    private static final int TBL_NAME_IDX = 2;
-
-    /** Column name index. */
-    private static final int COL_NAME_IDX = 3;
-
-    /** Nullable index. */
-    private static final int NULLABLE_IDX = 4;
-
-    /** Data type index. */
-    private static final int DATA_TYPE_IDX = 5;
-
-    /** Numeric precision index. */
-    private static final int DATA_PRECISION_IDX = 6;
-
-    /** Numeric scale index. */
-    private static final int DATA_SCALE_IDX = 7;
-
-    /** Index name index. */
-    private static final int IDX_NAME_IDX = 1;
-
-    /** Index name index. */
-    private static final int IDX_EXPR_IDX = 2;
-
-    /** Index column name index. */
-    private static final int IDX_COL_NAME_IDX = 3;
-
-    /** Index column sort order index. */
-    private static final int IDX_COL_DESCEND_IDX = 4;
-
-    /** {@inheritDoc} */
-    @Override public Set<String> systemSchemas() {
-        return new HashSet<>(Arrays.asList("ANONYMOUS", "CTXSYS", "DBSNMP", "EXFSYS", "LBACSYS", "MDSYS", "MGMT_VIEW",
-            "OLAPSYS", "OWBSYS", "ORDPLUGINS", "ORDSYS", "OUTLN", "SI_INFORMTN_SCHEMA", "SYS", "SYSMAN", "SYSTEM",
-            "TSMSYS", "WK_TEST", "WKSYS", "WKPROXY", "WMSYS", "XDB",
-
-            "APEX_040000", "APEX_PUBLIC_USER", "DIP", "FLOWS_30000", "FLOWS_FILES", "MDDATA", "ORACLE_OCM",
-            "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "XS$NULL",
-
-            "BI", "HR", "OE", "PM", "IX", "SH"));
-    }
-
-    /** {@inheritDoc} */
-    @Override public List<String> schemas(Connection conn) throws SQLException {
-        List<String> schemas = new ArrayList<>();
-
-        ResultSet rs = conn.getMetaData().getSchemas();
-
-        Set<String> sysSchemas = systemSchemas();
-
-        while(rs.next()) {
-            String schema = rs.getString(1);
-
-            if (!sysSchemas.contains(schema) && !schema.startsWith("FLOWS_"))
-                schemas.add(schema);
-        }
-
-        return schemas;
-    }
-
-    /**
-     * @param rs Result set with column type metadata from Oracle database.
-     * @return JDBC type.
-     * @throws SQLException If failed to decode type.
-     */
-    private int decodeType(ResultSet rs) throws SQLException {
-        String type = rs.getString(DATA_TYPE_IDX);
-
-        if (type.startsWith("TIMESTAMP"))
-            return TIMESTAMP;
-        else {
-            switch (type) {
-                case "CHAR":
-                case "NCHAR":
-                    return CHAR;
-
-                case "VARCHAR2":
-                case "NVARCHAR2":
-                    return VARCHAR;
-
-                case "LONG":
-                    return LONGVARCHAR;
-
-                case "LONG RAW":
-                    return LONGVARBINARY;
-
-                case "FLOAT":
-                    return FLOAT;
-
-                case "NUMBER":
-                    int precision = rs.getInt(DATA_PRECISION_IDX);
-                    int scale = rs.getInt(DATA_SCALE_IDX);
-
-                    if (scale > 0) {
-                        if (scale < 4 && precision < 19)
-                            return FLOAT;
-
-                        if (scale > 4 || precision > 19)
-                            return DOUBLE;
-
-                        return NUMERIC;
-                    }
-                    else {
-                        if (precision < 1)
-                            return INTEGER;
-
-                        if (precision < 2)
-                            return BOOLEAN;
-
-                        if (precision < 4)
-                            return TINYINT;
-
-                        if (precision < 6)
-                            return SMALLINT;
-
-                        if (precision < 11)
-                            return INTEGER;
-
-                        if (precision < 20)
-                            return BIGINT;
-
-                        return NUMERIC;
-                    }
-
-                case "DATE":
-                    return DATE;
-
-                case "BFILE":
-                case "BLOB":
-                    return BLOB;
-
-                case "CLOB":
-                case "NCLOB":
-                    return CLOB;
-
-                case "XMLTYPE":
-                    return SQLXML;
-            }
-        }
-
-        return OTHER;
-    }
-
-    /**
-     * Retrieve primary key columns.
-     *
-     * @param stmt Prepared SQL statement to execute.
-     * @param owner DB owner.
-     * @param tbl Table name.
-     * @return Primary key columns.
-     * @throws SQLException If failed to retrieve primary key columns.
-     */
-    private Set<String> primaryKeys(PreparedStatement stmt, String owner, String tbl) throws SQLException {
-        Set<String> pkCols = new HashSet<>();
-
-        stmt.setString(1, owner);
-        stmt.setString(2, tbl);
-
-        try (ResultSet pkRs = stmt.executeQuery()) {
-            while(pkRs.next())
-                pkCols.add(pkRs.getString(1));
-        }
-
-        return pkCols;
-    }
-
-    /**
-     * Retrieve index columns.
-     *
-     * @param stmt Prepared SQL statement to execute.
-     * @param owner DB owner.
-     * @param tbl Table name.
-     * @return Index columns.
-     * @throws SQLException If failed to retrieve indexes columns.
-     */
-    private Map<String, Map<String, Boolean>> indexes(PreparedStatement stmt, String owner, String tbl)
-        throws SQLException {
-        Map<String, Map<String, Boolean>> idxs = new LinkedHashMap<>();
-
-        stmt.setString(1, owner);
-        stmt.setString(2, tbl);
-
-        try (ResultSet idxsRs = stmt.executeQuery()) {
-            while (idxsRs.next()) {
-                String idxName = idxsRs.getString(IDX_NAME_IDX);
-
-                Map<String, Boolean> idx = idxs.get(idxName);
-
-                if (idx == null) {
-                    idx = new LinkedHashMap<>();
-
-                    idxs.put(idxName, idx);
-                }
-
-                String expr = idxsRs.getString(IDX_EXPR_IDX);
-
-                String col = expr == null ? idxsRs.getString(IDX_COL_NAME_IDX) : expr.replaceAll("\"", "");
-
-                idx.put(col, "DESC".equals(idxsRs.getString(IDX_COL_DESCEND_IDX)));
-            }
-        }
-
-        return idxs;
-    }
-
-    /** {@inheritDoc} */
-    @Override public Collection<DbTable> tables(Connection conn, List<String> schemas, boolean tblsOnly)
-        throws SQLException {
-        Collection<DbTable> tbls = new ArrayList<>();
-
-        PreparedStatement pkStmt = conn.prepareStatement(SQL_PRIMARY_KEYS);
-
-        PreparedStatement idxStmt = conn.prepareStatement(SQL_INDEXES);
-
-        if (schemas.size() == 0)
-            schemas.add(null);
-
-        Set<String> sysSchemas = systemSchemas();
-
-        try (Statement colsStmt = conn.createStatement()) {
-            for (String schema: schemas) {
-                if (systemSchemas().contains(schema) || (schema != null && schema.startsWith("FLOWS_")))
-                    continue;
-
-                Collection<DbColumn> cols = new ArrayList<>();
-
-                Set<String> pkCols = Collections.emptySet();
-                Map<String, Map<String, Boolean>> idxs = Collections.emptyMap();
-
-                String sql = String.format(SQL_COLUMNS,
-                        tblsOnly ? "INNER JOIN all_tables b on a.table_name = b.table_name and a.owner = b.owner" : "",
-                        schema != null ? String.format(" WHERE a.owner = '%s' ", schema) : "");
-
-                try (ResultSet colsRs = colsStmt.executeQuery(sql)) {
-                    String prevSchema = "";
-                    String prevTbl = "";
-
-                    boolean first = true;
-
-                    while (colsRs.next()) {
-                        String owner = colsRs.getString(OWNER_IDX);
-                        String tbl = colsRs.getString(TBL_NAME_IDX);
-
-                        if (sysSchemas.contains(owner) || (schema != null && schema.startsWith("FLOWS_")))
-                            continue;
-
-                        boolean changed = !owner.equals(prevSchema) || !tbl.equals(prevTbl);
-
-                        if (changed) {
-                            if (first)
-                                first = false;
-                            else
-                                tbls.add(table(prevSchema, prevTbl, cols, idxs));
-
-                            prevSchema = owner;
-                            prevTbl = tbl;
-                            cols = new ArrayList<>();
-                            pkCols = primaryKeys(pkStmt, owner, tbl);
-                            idxs = indexes(idxStmt, owner, tbl);
-                        }
-
-                        String colName = colsRs.getString(COL_NAME_IDX);
-
-                        cols.add(new DbColumn(colName, decodeType(colsRs), pkCols.contains(colName),
-                                !"N".equals(colsRs.getString(NULLABLE_IDX))));
-                    }
-
-                    if (!cols.isEmpty())
-                        tbls.add(table(prevSchema, prevTbl, cols, idxs));
-                }
-            }
-        }
-
-        return tbls;
-    }
-}


[17/18] ignite git commit: IGNITE-843 Fixed after merge

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/sql-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/sql-controller.js b/modules/control-center-web/src/main/js/controllers/sql-controller.js
index 72afcc9..3b28883 100644
--- a/modules/control-center-web/src/main/js/controllers/sql-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/sql-controller.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Controller for SQL notebook screen.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/summary-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/summary-controller.js b/modules/control-center-web/src/main/js/controllers/summary-controller.js
index a3d2dd1..9247b14 100644
--- a/modules/control-center-web/src/main/js/controllers/summary-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/summary-controller.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Controller for Summary screen.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/db.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/db.js b/modules/control-center-web/src/main/js/db.js
index c9c6b39..8df7167 100644
--- a/modules/control-center-web/src/main/js/db.js
+++ b/modules/control-center-web/src/main/js/db.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var config = require('./helpers/configuration-loader.js');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/helpers/common-utils.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/helpers/common-utils.js b/modules/control-center-web/src/main/js/helpers/common-utils.js
index 598cdf8..7ea8885 100644
--- a/modules/control-center-web/src/main/js/helpers/common-utils.js
+++ b/modules/control-center-web/src/main/js/helpers/common-utils.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Entry point for common utils.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/helpers/configuration-loader.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/helpers/configuration-loader.js b/modules/control-center-web/src/main/js/helpers/configuration-loader.js
index 45616aa..6918297 100644
--- a/modules/control-center-web/src/main/js/helpers/configuration-loader.js
+++ b/modules/control-center-web/src/main/js/helpers/configuration-loader.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var config = require('nconf');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/helpers/data-structures.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/helpers/data-structures.js b/modules/control-center-web/src/main/js/helpers/data-structures.js
index 71e38a9..7eeca24 100644
--- a/modules/control-center-web/src/main/js/helpers/data-structures.js
+++ b/modules/control-center-web/src/main/js/helpers/data-structures.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // For server side we should load required libraries.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/favicon.ico
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/favicon.ico b/modules/control-center-web/src/main/js/public/favicon.ico
new file mode 100644
index 0000000..74ec626
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/favicon.ico differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/cache.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/cache.png b/modules/control-center-web/src/main/js/public/images/cache.png
new file mode 100755
index 0000000..28aa268
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/cache.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/cluster.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/cluster.png b/modules/control-center-web/src/main/js/public/images/cluster.png
new file mode 100755
index 0000000..c48b9ef
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/cluster.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/docker.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/docker.png b/modules/control-center-web/src/main/js/public/images/docker.png
new file mode 100755
index 0000000..7ec3aef
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/docker.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/java.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/java.png b/modules/control-center-web/src/main/js/public/images/java.png
new file mode 100755
index 0000000..ddb3b8e
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/java.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/logo.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/logo.png b/modules/control-center-web/src/main/js/public/images/logo.png
new file mode 100755
index 0000000..c3577c5
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/logo.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/metadata.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/metadata.png b/modules/control-center-web/src/main/js/public/images/metadata.png
new file mode 100755
index 0000000..1f2f8f5
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/metadata.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/query-chart.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/query-chart.png b/modules/control-center-web/src/main/js/public/images/query-chart.png
new file mode 100755
index 0000000..d053b6a
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/query-chart.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/query-metadata.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/query-metadata.png b/modules/control-center-web/src/main/js/public/images/query-metadata.png
new file mode 100755
index 0000000..71bad47
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/query-metadata.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/query-table.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/query-table.png b/modules/control-center-web/src/main/js/public/images/query-table.png
new file mode 100755
index 0000000..b234255
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/query-table.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/summary.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/summary.png b/modules/control-center-web/src/main/js/public/images/summary.png
new file mode 100755
index 0000000..7a1148d
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/summary.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/images/xml.png
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/images/xml.png b/modules/control-center-web/src/main/js/public/images/xml.png
new file mode 100755
index 0000000..029065e
Binary files /dev/null and b/modules/control-center-web/src/main/js/public/images/xml.png differ

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss
index f7e29c4..3b52821 100644
--- a/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss
+++ b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-custom.scss
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Core variables and mixins

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss
index 7b63443..ae0a3d7 100644
--- a/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss
+++ b/modules/control-center-web/src/main/js/public/stylesheets/_bootstrap-variables.scss
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 $bootstrap-sass-asset-helper: false !default;
 //

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/public/stylesheets/style.scss
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/public/stylesheets/style.scss b/modules/control-center-web/src/main/js/public/stylesheets/style.scss
index ba8ff46..7447577 100644
--- a/modules/control-center-web/src/main/js/public/stylesheets/style.scss
+++ b/modules/control-center-web/src/main/js/public/stylesheets/style.scss
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 @import "bootstrap-custom";

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/admin.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/admin.js b/modules/control-center-web/src/main/js/routes/admin.js
index 66bdaab..d554a70 100644
--- a/modules/control-center-web/src/main/js/routes/admin.js
+++ b/modules/control-center-web/src/main/js/routes/admin.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var _ = require('lodash');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/agent.js b/modules/control-center-web/src/main/js/routes/agent.js
index 607de15..1c44eb9 100644
--- a/modules/control-center-web/src/main/js/routes/agent.js
+++ b/modules/control-center-web/src/main/js/routes/agent.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var router = require('express').Router();

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/caches.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/caches.js b/modules/control-center-web/src/main/js/routes/caches.js
index 30a5547..0875be5 100644
--- a/modules/control-center-web/src/main/js/routes/caches.js
+++ b/modules/control-center-web/src/main/js/routes/caches.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var _ = require('lodash');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/clusters.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/clusters.js b/modules/control-center-web/src/main/js/routes/clusters.js
index 59773e0..6d2208c 100644
--- a/modules/control-center-web/src/main/js/routes/clusters.js
+++ b/modules/control-center-web/src/main/js/routes/clusters.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var _ = require('lodash');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/generator/generator-common.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-common.js b/modules/control-center-web/src/main/js/routes/generator/generator-common.js
index ccd11e0..0e41557 100644
--- a/modules/control-center-web/src/main/js/routes/generator/generator-common.js
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-common.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // For server side we should load required libraries.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/generator/generator-docker.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-docker.js b/modules/control-center-web/src/main/js/routes/generator/generator-docker.js
index 676cc94..4e12b53 100644
--- a/modules/control-center-web/src/main/js/routes/generator/generator-docker.js
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-docker.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Docker file generation entry point.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/generator/generator-java.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-java.js b/modules/control-center-web/src/main/js/routes/generator/generator-java.js
index 1e7139d..55aa0a4 100644
--- a/modules/control-center-web/src/main/js/routes/generator/generator-java.js
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-java.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // For server side we should load required libraries.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/generator/generator-properties.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-properties.js b/modules/control-center-web/src/main/js/routes/generator/generator-properties.js
index 8fe6fa3..1aaf735 100644
--- a/modules/control-center-web/src/main/js/routes/generator/generator-properties.js
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-properties.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // For server side we should load required libraries.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/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
index 569c6dd..4221c53 100644
--- 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
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // For server side we should load required libraries.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/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
index b9d9445..f714e12 100644
--- a/modules/control-center-web/src/main/js/routes/metadata.js
+++ b/modules/control-center-web/src/main/js/routes/metadata.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var async = require('async');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/notebooks.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/notebooks.js b/modules/control-center-web/src/main/js/routes/notebooks.js
index 70ccbcd..364d688 100644
--- a/modules/control-center-web/src/main/js/routes/notebooks.js
+++ b/modules/control-center-web/src/main/js/routes/notebooks.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var router = require('express').Router();

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/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
index 76eb5dd..6bfdc48 100644
--- a/modules/control-center-web/src/main/js/routes/presets.js
+++ b/modules/control-center-web/src/main/js/routes/presets.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var router = require('express').Router();

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/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
index 8f9d324..cd3fd5b 100644
--- a/modules/control-center-web/src/main/js/routes/profile.js
+++ b/modules/control-center-web/src/main/js/routes/profile.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var router = require('express').Router();

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/routes/public.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/public.js b/modules/control-center-web/src/main/js/routes/public.js
index 47b5d56..c73204f 100644
--- a/modules/control-center-web/src/main/js/routes/public.js
+++ b/modules/control-center-web/src/main/js/routes/public.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var router = require('express').Router();

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/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
index 306539b..6925eed 100644
--- a/modules/control-center-web/src/main/js/routes/sql.js
+++ b/modules/control-center-web/src/main/js/routes/sql.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var router = require('express').Router();

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/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
index 911b495..53a1286 100644
--- a/modules/control-center-web/src/main/js/routes/summary.js
+++ b/modules/control-center-web/src/main/js/routes/summary.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var db = require('../db');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/test/js/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/test/js/routes/agent.js b/modules/control-center-web/src/test/js/routes/agent.js
index 4b7dfeb..318d4e6 100644
--- a/modules/control-center-web/src/test/js/routes/agent.js
+++ b/modules/control-center-web/src/test/js/routes/agent.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var request = require('supertest'),


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/models/caches.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/models/caches.json b/modules/control-center-web/src/main/js/controllers/models/caches.json
new file mode 100644
index 0000000..087cb1b
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/models/caches.json
@@ -0,0 +1,1027 @@
+{
+  "screenTip": {
+    "workflowTitle": "On This Screen:",
+    "workflowContent": [
+      "Configure Caches",
+      "Link Metadata to Caches",
+      "Link Caches to Clusters",
+      "more-info"
+    ],
+    "whatsNextTitle": "Next Steps:",
+    "whatsNextContent": [
+      "Continue to <a href='/configuration/metadata'>Metadata</a>",
+      "Continue to <a href='/configuration/summary'>Summary</a>",
+      "Back to <a href='/configuration/clusters'>Clusters</a>"
+    ]
+  },
+  "moreInfo": {
+    "title": "Caches page",
+    "content": ["Manage you cluster's caches on current page.",
+      "Caches can be linked with specified <a href='/configuration/clusters'>clusters</a> and <a href='/configuration/metadata'>metadata</a>",
+      "Generated cluster with caches configuration available on <a href='/configuration/summary'>summary</a> page."]
+  },
+  "general": [
+    {
+      "label": "General",
+      "group": "general",
+      "fields": [
+        {
+          "label": "Name",
+          "id": "cacheName",
+          "type": "text",
+          "model": "name",
+          "required": true,
+          "placeholder": "Input name"
+        },
+        {
+          "label": "Clusters",
+          "id": "clusters",
+          "type": "dropdown-multiple",
+          "model": "clusters",
+          "placeholder": "Choose clusters",
+          "placeholderEmpty": "No clusters configured",
+          "items": "clusters",
+          "tip": [
+            "Associate clusters with the current cache"
+          ],
+          "addLink": {
+            "label": "Add cluster(s)",
+            "ref": "/configuration/clusters?new"
+          }
+        },
+        {
+          "label": "Metadata",
+          "id": "metadata",
+          "type": "dropdown-multiple",
+          "model": "metadatas",
+          "placeholder": "Choose metadata",
+          "placeholderEmpty": "No metadata configured",
+          "items": "metadatas",
+          "tip": [
+            "Select cache type metadata to describe types in cache"
+          ],
+          "addLink": {
+            "label": "Add metadata(s)",
+            "ref": "/configuration/metadata?new"
+          }
+        },
+        {
+          "label": "Mode",
+          "id": "cacheMode",
+          "type": "dropdown",
+          "model": "cacheMode",
+          "placeholder": "PARTITIONED",
+          "items": "cacheModes",
+          "tip": [
+            "Cache modes:",
+            "<ul>",
+            "  <li>Partitioned - in this mode the overall key set will be divided into partitions and all partitions will be split equally between participating nodes</li>",
+            "  <li>Replicated - in this mode all the keys are distributed to all participating nodes</li>",
+            "  <li>Local - in this mode caches residing on different grid nodes will not know about each other</li>",
+            "</ul>"
+          ]
+        },
+        {
+          "label": "Atomicity",
+          "id": "atomicityMode",
+          "type": "dropdown",
+          "model": "atomicityMode",
+          "placeholder": "ATOMIC",
+          "items": "atomicities",
+          "tip": [
+            "Atomicity:",
+            "<ul>",
+            "  <li>Transactional - in this mode specified fully ACID-compliant transactional cache behavior</li>",
+            "  <li>Atomic - in this mode distributed transactions and distributed locking are not supported</li>",
+            "</ul>"
+          ]
+        },
+        {
+          "label": "Backups",
+          "id": "backups",
+          "type": "number",
+          "model": "backups",
+          "hide": "backupItem.cacheMode != 'PARTITIONED'",
+          "placeholder": 0,
+          "tip": [
+            "Number of nodes used to back up single partition for partitioned cache"
+          ]
+        },
+        {
+          "label": "Read from backup",
+          "id": "readFromBackup",
+          "type": "check",
+          "model": "readFromBackup",
+          "placeholder": true,
+          "hide": "!backupItem.backups || backupItem.cacheMode == 'LOCAL'",
+          "tip": [
+            "Flag indicating whether data can be read from backup",
+            "If not set then always get data from primary node (never from backup)"
+          ]
+        },
+        {
+          "label": "Copy on read",
+          "id": "copyOnRead",
+          "type": "check",
+          "model": "copyOnRead",
+          "placeholder": true,
+          "tip": [
+            "Flag indicating whether copy of of the value stored in cache should be created for cache operation implying return value",
+            "Also if this flag is set copies are created for values passed to CacheInterceptor and to CacheEntryProcessor"
+          ]
+        },
+        {
+          "label": "Invalidate near cache",
+          "id": "invalidate",
+          "type": "check",
+          "model": "invalidate",
+          "tip": [
+            "Invalidation flag for near cache entries in transaction",
+            "If set then values will be invalidated (nullified) upon commit in near cache"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Memory",
+      "group": "memory",
+      "tip": [
+        "Cache memory settings"
+      ],
+      "fields": [
+        {
+          "label": "Mode",
+          "id": "memoryMode",
+          "type": "dropdown",
+          "model": "memoryMode",
+          "placeholder": "ONHEAP_TIERED",
+          "items": "memoryModes",
+          "tip": [
+            "Memory modes:",
+            "<ul>",
+            "  <li>ONHEAP_TIERED - entries are cached on heap memory first",
+            "    <ul>",
+            "      <li>If offheap memory is enabled and eviction policy evicts an entry from heap memory, entry will be moved to offheap memory. If offheap memory is disabled, then entry is simply discarded</li>",
+            "      <li>If swap space is enabled and offheap memory fills up, then entry will be evicted into swap space. If swap space is disabled, then entry will be discarded. If swap is enabled and offheap memory is disabled, then entry will be evicted directly from heap memory into swap</li>",
+            "    </ul>",
+            "  </li>",
+            "  <li>OFFHEAP_TIERED - works the same as ONHEAP_TIERED, except that entries never end up in heap memory and get stored in offheap memory right away. Entries get cached in offheap memory first and then get evicted to swap, if one is configured</li>",
+            "  <li>OFFHEAP_VALUES - entry keys will be stored on heap memory, and values will be stored in offheap memory. Note that in this mode entries can be evicted only to swap</li>",
+            "</ul>"
+          ]
+        },
+        {
+          "label": "Off-heap max memory",
+          "id": "offHeapMaxMemory",
+          "type": "number",
+          "model": "offHeapMaxMemory",
+          "min": -1,
+          "placeholder": -1,
+          "hide": "backupItem.memoryMode == 'OFFHEAP_VALUES'",
+          "tip": [
+            "Sets maximum amount of memory available to off-heap storage",
+            "Possible values are:",
+            "<ul>",
+            "  <li>-1 - means that off-heap storage is disabled</li>",
+            "  <li>0 - Ignite will not limit off-heap storage (it's up to user to properly add and remove entries from cache to ensure that off-heap storage does not grow infinitely</li>",
+            "  <li>Any positive value specifies the limit of off-heap storage in bytes</li>",
+            "</ul>"
+          ]
+        },
+        {
+          "label": "Eviction policy",
+          "id": "evictionPolicy",
+          "type": "dropdown-details",
+          "settings": true,
+          "path": "evictionPolicy",
+          "model": "kind",
+          "placeholder": "Choose eviction policy",
+          "items": "evictionPolicies",
+          "hide": "backupItem.memoryMode == 'OFFHEAP_TIERED'",
+          "tip": [
+            "Optional cache eviction policy. Must be set for entries to be evicted from on-heap to off-heap or swap"
+          ],
+          "details": {
+            "LRU": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Batch size",
+                  "type": "number",
+                  "path": "evictionPolicy.LRU",
+                  "model": "batchSize",
+                  "placeholder": 1,
+                  "tip": [
+                    "Number of entries to remove on shrink"
+                  ]
+                },
+                {
+                  "label": "Max memory size",
+                  "type": "number",
+                  "path": "evictionPolicy.LRU",
+                  "model": "maxMemorySize",
+                  "placeholder": 0,
+                  "tip": [
+                    "Maximum allowed cache size in bytes"
+                  ]
+                },
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "evictionPolicy.LRU",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            },
+            "RND": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "evictionPolicy.RND",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            },
+            "FIFO": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Batch size",
+                  "type": "number",
+                  "path": "evictionPolicy.FIFO",
+                  "model": "batchSize",
+                  "placeholder": 1,
+                  "tip": [
+                    "Number of entries to remove on shrink"
+                  ]
+                },
+                {
+                  "label": "Max memory size",
+                  "type": "number",
+                  "path": "evictionPolicy.FIFO",
+                  "model": "maxMemorySize",
+                  "placeholder": 0,
+                  "tip": [
+                    "Maximum allowed cache size in bytes"
+                  ]
+                },
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "evictionPolicy.FIFO",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            },
+            "SORTED": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Batch size",
+                  "type": "number",
+                  "path": "evictionPolicy.SORTED",
+                  "model": "batchSize",
+                  "placeholder": 1,
+                  "tip": [
+                    "Number of entries to remove on shrink"
+                  ]
+                },
+                {
+                  "label": "Max memory size",
+                  "type": "number",
+                  "path": "evictionPolicy.SORTED",
+                  "model": "maxMemorySize",
+                  "placeholder": 0,
+                  "tip": [
+                    "Maximum allowed cache size in bytes"
+                  ]
+                },
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "evictionPolicy.SORTED",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            }
+          }
+        },
+        {
+          "label": "Start size",
+          "id": "startSize",
+          "type": "number",
+          "model": "startSize",
+          "placeholder": 1500000,
+          "tip": [
+            "Initial cache size which will be used to pre-create internal hash table after start"
+          ]
+        },
+        {
+          "label": "Swap enabled",
+          "id": "swapEnabled",
+          "type": "check",
+          "model": "swapEnabled",
+          "tip": [
+            "Flag indicating whether swap storage is enabled or not for this cache"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Queries & Indexing",
+      "group": "query",
+      "tip": [
+        "Cache query settings"
+      ],
+      "fields": [
+        {
+          "label": "On-heap cache for off-heap indexes",
+          "id": "sqlOnheapRowCacheSize",
+          "type": "number",
+          "model": "sqlOnheapRowCacheSize",
+          "placeholder": 10240,
+          "tip": [
+            "Number of SQL rows which will be cached onheap to avoid deserialization on each SQL index access"
+          ]
+        },
+        {
+          "label": "Long query timeout",
+          "id": "longQueryWarningTimeout",
+          "type": "number",
+          "model": "longQueryWarningTimeout",
+          "placeholder": 3000,
+          "tip": [
+            "Timeout in milliseconds after which long query warning will be printed"
+          ]
+        },
+        {
+          "ui": "table-pair",
+          "id": "indexedTypes",
+          "type": "indexedTypes",
+          "model": "indexedTypes",
+          "keyName": "keyClass",
+          "valueName": "valueClass",
+          "focusId": "IndexedType",
+          "addTip": "Add new key and value classes to indexed types",
+          "removeTip": "Remove item from indexed types",
+          "tip": [
+            "Collection of types to index"
+          ]
+        },
+        {
+          "label": "SQL functions",
+          "id": "sqlFunctionClasses",
+          "type": "table-simple",
+          "model": "sqlFunctionClasses",
+          "placeholder": "SQL function full class name",
+          "focusId": "SqlFx",
+          "addTip": "Add new user-defined functions for SQL queries",
+          "removeTip": "Remove user-defined function",
+          "tableTip": [
+            "Collections of classes with user-defined functions for SQL queries"
+          ],
+          "tip": [
+            "Class with user-defined functions for SQL queries"
+          ]
+        },
+        {
+          "label": "Escape table and filed names",
+          "id": "sqlEscapeAll",
+          "type": "check",
+          "model": "sqlEscapeAll",
+          "tip": [
+            "If set then all the SQL table and field names will be escaped with double quotes",
+            "This enforces case sensitivity for field names and also allows having special characters in table and field names"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Store",
+      "group": "store",
+      "tip": [
+        "Cache store settings"
+      ],
+      "fields": [
+        {
+          "label": "Store factory",
+          "id": "cacheStoreFactory",
+          "type": "dropdown-details",
+          "settings": true,
+          "path": "cacheStoreFactory",
+          "model": "kind",
+          "placeholder": "Choose store factory",
+          "items": "cacheStoreFactories",
+          "tip": [
+            "Factory for persistent storage for cache data"
+          ],
+          "details": {
+            "CacheJdbcPojoStoreFactory": {
+              "expanded": true,
+              "fields": [
+                {
+                  "label": "Data source bean",
+                  "id": "dataSourceBean",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcPojoStoreFactory",
+                  "model": "dataSourceBean",
+                  "required": true,
+                  "placeholder": "Bean name in Spring context",
+                  "tip": [
+                    "Name of the data source bean in Spring context"
+                  ]
+                },
+                {
+                  "label": "Dialect",
+                  "id": "dialect",
+                  "type": "dropdown",
+                  "path": "cacheStoreFactory.CacheJdbcPojoStoreFactory",
+                  "model": "dialect",
+                  "required": true,
+                  "placeholder": "Choose JDBC dialect",
+                  "items": "cacheStoreJdbcDialects",
+                  "tip": [
+                    "Dialect of SQL implemented by a particular RDBMS:",
+                    "<ul>",
+                    "  <li>Generic JDBC dialect</li>",
+                    "  <li>Oracle database</li>",
+                    "  <li>IBM DB2</li>",
+                    "  <li>Microsoft SQL Server</li>",
+                    "  <li>My SQL</li>",
+                    "  <li>H2 database</li>",
+                    "</ul>"
+                  ]
+                }
+              ]
+            },
+            "CacheJdbcBlobStoreFactory": {
+              "expanded": true,
+              "fields": [
+                {
+                  "label": "User",
+                  "id": "user",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "user",
+                  "required": true,
+                  "tip": [
+                    "User name for database access"
+                  ]
+                },
+                {
+                  "label": "Data source bean",
+                  "id": "dataSourceBean",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "dataSourceBean",
+                  "required": true,
+                  "placeholder": "Bean name in Spring context",
+                  "tip": [
+                    "Name of the data source bean in Spring context"
+                  ]
+                },
+                {
+                  "label": "Init schema",
+                  "type": "check",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "initSchema",
+                  "tip": [
+                    "Flag indicating whether DB schema should be initialized by Ignite (default behaviour) or was explicitly created by user"
+                  ]
+                },
+                {
+                  "label": "Create query",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "createTableQuery",
+                  "placeholder": "SQL for table creation",
+                  "tip": [
+                    "Query for table creation in underlying database",
+                    "Default value: create table if not exists ENTRIES (key binary primary key, val binary)"
+                  ]
+                },
+                {
+                  "label": "Load query",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "loadQuery",
+                  "placeholder": "SQL for load entry",
+                  "tip": [
+                    "Query for entry load from underlying database",
+                    "Default value: select * from ENTRIES where key=?"
+                  ]
+                },
+                {
+                  "label": "Insert query",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "insertQuery",
+                  "placeholder": "SQL for insert entry",
+                  "tip": [
+                    "Query for insert entry into underlying database",
+                    "Default value: insert into ENTRIES (key, val) values (?, ?)"
+                  ]
+                },
+                {
+                  "label": "Update query",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "updateQuery",
+                  "placeholder": "SQL for update entry",
+                  "tip": [
+                    "Query fpr update entry in underlying database",
+                    "Default value: update ENTRIES set val=? where key=?"
+                  ]
+                },
+                {
+                  "label": "Delete query",
+                  "type": "text",
+                  "path": "cacheStoreFactory.CacheJdbcBlobStoreFactory",
+                  "model": "deleteQuery",
+                  "placeholder": "SQL for delete entry",
+                  "tip": [
+                    "Query for delete entry from underlying database",
+                    "Default value: delete from ENTRIES where key=?"
+                  ]
+                }
+              ]
+            },
+            "CacheHibernateBlobStoreFactory": {
+              "expanded": true,
+              "fields": [
+                {
+                  "label": "Hibernate properties",
+                  "id": "hibernateProperties",
+                  "type": "table-simple",
+                  "path": "cacheStoreFactory.CacheHibernateBlobStoreFactory",
+                  "model": "hibernateProperties",
+                  "placeholder": "key=value",
+                  "focusId": "HibProp",
+                  "addTip": "Add new Hibernate property",
+                  "removeTip": "Remove Hibernate property",
+                  "tip": [
+                    "List of Hibernate properties",
+                    "For example: connection.url=jdbc:h2:mem:"
+                  ]
+                }
+              ]
+            }
+          }
+        },
+        {
+          "label": "Load previous value",
+          "id": "loadPreviousValue",
+          "type": "check",
+          "model": "loadPreviousValue",
+          "tip": [
+            "Flag indicating whether value should be loaded from store if it is not in the cache for following cache operations:",
+            "<ul>",
+            "  <li>IgniteCache.putIfAbsent()</li>",
+            "  <li>IgniteCache.replace()</li>",
+            "  <li>IgniteCache.replace()</li>",
+            "  <li>IgniteCache.remove()</li>",
+            "  <li>IgniteCache.getAndPut()</li>",
+            "  <li>IgniteCache.getAndRemove()</li>",
+            "  <li>IgniteCache.getAndReplace()</li>",
+            "  <li>IgniteCache.getAndPutIfAbsent()</li>",
+            "</ul>"
+          ]
+        },
+        {
+          "label": "Read-through",
+          "id": "readThrough",
+          "type": "check",
+          "model": "readThrough",
+          "tip": [
+            "Flag indicating whether read-through caching should be used"
+          ]
+        },
+        {
+          "label": "Write-through",
+          "id": "writeThrough",
+          "type": "check",
+          "model": "writeThrough",
+          "tip": [
+            "Flag indicating whether write-through caching should be used"
+          ]
+        },
+        {
+          "label": "Write behind",
+          "type": "panel-details",
+          "tip": [
+            "Cache write behind settings",
+            "Write-behind is a special mode when updates to cache accumulated and then asynchronously flushed to persistent store as a bulk operation"
+          ],
+          "details": [
+            {
+              "label": "Enabled",
+              "id": "writeBehindEnabled",
+              "type": "check",
+              "model": "writeBehindEnabled",
+              "tip": [
+                "Flag indicating whether Ignite should use write-behind behaviour for the cache store"
+              ]
+            },
+            {
+              "label": "Batch size",
+              "id": "writeBehindBatchSize",
+              "type": "number",
+              "model": "writeBehindBatchSize",
+              "disabled": "!backupItem.writeBehindEnabled",
+              "placeholder": 512,
+              "tip": [
+                "Maximum batch size for write-behind cache store operations",
+                "Store operations (get or remove) are combined in a batch of this size to be passed to cache store"
+              ]
+            },
+            {
+              "label": "Flush size",
+              "id": "writeBehindFlushSize",
+              "type": "number",
+              "model": "writeBehindFlushSize",
+              "disabled": "!backupItem.writeBehindEnabled",
+              "placeholder": 10240,
+              "tip": [
+                "Maximum size of the write-behind cache",
+                "If cache size exceeds this value, all cached items are flushed to the cache store and write cache is cleared"
+              ]
+            },
+            {
+              "label": "Flush frequency",
+              "id": "writeBehindFlushFrequency",
+              "type": "number",
+              "model": "writeBehindFlushFrequency",
+              "disabled": "!backupItem.writeBehindEnabled",
+              "placeholder": 5000,
+              "tip": [
+                "Frequency with which write-behind cache is flushed to the cache store in milliseconds"
+              ]
+            },
+            {
+              "label": "Flush threads count",
+              "id": "writeBehindFlushThreadCount",
+              "type": "number",
+              "model": "writeBehindFlushThreadCount",
+              "disabled": "!backupItem.writeBehindEnabled",
+              "placeholder": 1,
+              "tip": [
+                "Number of threads that will perform cache flushing"
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  ],
+  "advanced": [
+    {
+      "label": "Concurrency control",
+      "group": "concurrency",
+      "tip": [
+        "Cache concurrent usage settings"
+      ],
+      "fields": [
+        {
+          "label": "Max async operations",
+          "id": "maxConcurrentAsyncOperations",
+          "type": "number",
+          "model": "maxConcurrentAsyncOperations",
+          "placeholder": 500,
+          "tip": [
+            "Maximum number of allowed concurrent asynchronous operations",
+            "If 0 then number of concurrent asynchronous operations is unlimited"
+          ]
+        },
+        {
+          "label": "Default lock timeout",
+          "id": "defaultLockTimeout",
+          "type": "number",
+          "model": "defaultLockTimeout",
+          "placeholder": 0,
+          "tip": [
+            "Default lock acquisition timeout",
+            "If 0 then lock acquisition will never timeout"
+          ]
+        },
+        {
+          "label": "Entry versioning",
+          "id": "atomicWriteOrderMode",
+          "type": "dropdown",
+          "model": "atomicWriteOrderMode",
+          "placeholder": "Choose versioning",
+          "items": "atomicWriteOrderModes",
+          "hide": "backupItem.atomicityMode == 'TRANSACTIONAL'",
+          "tip": [
+            "Write ordering mode determines which node assigns the write version, sender or the primary node",
+            "<ul>",
+            "  <li>CLOCK - in this mode write versions are assigned on a sender node which generally leads to better performance</li>",
+            "  <li>PRIMARY - in this mode version is assigned only on primary node. This means that sender will only send write request to primary node, which in turn will assign write version and forward it to backups</li>",
+            "</ul>"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Rebalance",
+      "group": "rebalance",
+      "hide": "backupItem.cacheMode == 'LOCAL'",
+      "tip": [
+        "Cache rebalance settings"
+      ],
+      "fields": [
+        {
+          "label": "Mode",
+          "id": "rebalanceMode",
+          "type": "dropdown",
+          "model": "rebalanceMode",
+          "placeholder": "ASYNC",
+          "items": "rebalanceModes",
+          "tip": [
+            "Rebalance modes:",
+            "<ul>",
+            "  <li>Synchronous - in this mode distributed caches will not start until all necessary data is loaded from other available grid nodes</li>",
+            "  <li>Asynchronous - in this mode distributed caches will start immediately and will load all necessary data from other available grid nodes in the background</li>",
+            "  <li>None - in this mode no rebalancing will take place which means that caches will be either loaded on demand from persistent store whenever data is accessed, or will be populated explicitly</li>",
+            "</ul>"
+          ]
+        },
+        {
+          "label": "Pool size",
+          "id": "rebalanceThreadPoolSize",
+          "type": "number",
+          "model": "rebalanceThreadPoolSize",
+          "placeholder": 2,
+          "tip": [
+            "Size of rebalancing thread pool<br>",
+            "Note that size serves as a hint and implementation may create more threads for rebalancing than specified here (but never less threads)"
+          ]
+        },
+        {
+          "label": "Batch size",
+          "id": "rebalanceBatchSize",
+          "type": "number",
+          "model": "rebalanceBatchSize",
+          "placeholder": "512 * 1024",
+          "tip": [
+            "Size (in bytes) to be loaded within a single rebalance message",
+            "Rebalancing algorithm will split total data set on every node into multiple batches prior to sending data"
+          ]
+        },
+        {
+          "label": "Order",
+          "id": "rebalanceOrder",
+          "type": "number",
+          "model": "rebalanceOrder",
+          "placeholder": 0,
+          "tip": [
+            "If cache rebalance order is positive, rebalancing for this cache will be started only when rebalancing for all caches with smaller rebalance order (except caches with rebalance order 0) will be completed"
+          ]
+        },
+        {
+          "label": "Delay",
+          "id": "rebalanceDelay",
+          "type": "number",
+          "model": "rebalanceDelay",
+          "placeholder": 0,
+          "tip": [
+            "Delay in milliseconds upon a node joining or leaving topology (or crash) after which rebalancing should be started automatically"
+          ]
+        },
+        {
+          "label": "Timeout",
+          "id": "rebalanceTimeout",
+          "type": "number",
+          "model": "rebalanceTimeout",
+          "placeholder": 10000,
+          "tip": [
+            "Rebalance timeout in milliseconds"
+          ]
+        },
+        {
+          "label": "Throttle",
+          "id": "rebalanceThrottle",
+          "type": "number",
+          "model": "rebalanceThrottle",
+          "placeholder": 0,
+          "tip": [
+            "Time in milliseconds to wait between rebalance messages to avoid overloading of CPU or network"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Server near cache",
+      "group": "serverNearCache",
+      "hide": "backupItem.cacheMode != 'PARTITIONED'",
+      "tip": [
+        "Near cache settings",
+        "Near cache is a small local cache that stores most recently or most frequently accessed data",
+        "Should be used in case when it is impossible to send computations to remote nodes"
+      ],
+      "fields": [
+        {
+          "label": "Enabled",
+          "id": "nearCacheEnabled",
+          "type": "check",
+          "model": "nearCacheEnabled",
+          "tip": [
+            "Flag indicating whether to configure near cache"
+          ]
+        },
+        {
+          "label": "Start size",
+          "id": "nearStartSize",
+          "type": "number",
+          "path": "nearConfiguration",
+          "model": "nearStartSize",
+          "hide": "!backupItem.nearCacheEnabled",
+          "placeholder": 375000,
+          "tip": [
+            "Initial cache size for near cache which will be used to pre-create internal hash table after start"
+          ]
+        },
+        {
+          "label": "Eviction policy",
+          "id": "nearCacheEvictionPolicy",
+          "type": "dropdown-details",
+          "settings": true,
+          "path": "nearConfiguration.nearEvictionPolicy",
+          "model": "kind",
+          "placeholder": "Choose eviction policy",
+          "items": "evictionPolicies",
+          "hide": "!backupItem.nearCacheEnabled",
+          "tip": [
+            "Cache expiration policy"
+          ],
+          "details": {
+            "LRU": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Batch size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.LRU",
+                  "model": "batchSize",
+                  "placeholder": 1,
+                  "tip": [
+                    "Number of entries to remove on shrink"
+                  ]
+                },
+                {
+                  "label": "Max memory size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.LRU",
+                  "model": "maxMemorySize",
+                  "placeholder": 0,
+                  "tip": [
+                    "Maximum allowed cache size in bytes"
+                  ]
+                },
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.LRU",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            },
+            "RND": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.RND",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            },
+            "FIFO": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Batch size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.FIFO",
+                  "model": "batchSize",
+                  "placeholder": 1,
+                  "tip": [
+                    "Number of entries to remove on shrink"
+                  ]
+                },
+                {
+                  "label": "Max memory size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.FIFO",
+                  "model": "maxMemorySize",
+                  "placeholder": 0,
+                  "tip": [
+                    "Maximum allowed cache size in bytes"
+                  ]
+                },
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.FIFO",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            },
+            "SORTED": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Batch size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.SORTED",
+                  "model": "batchSize",
+                  "placeholder": 1,
+                  "tip": [
+                    "Number of entries to remove on shrink"
+                  ]
+                },
+                {
+                  "label": "Max memory size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.SORTED",
+                  "model": "maxMemorySize",
+                  "placeholder": 0,
+                  "tip": [
+                    "Maximum allowed cache size in bytes"
+                  ]
+                },
+                {
+                  "label": "Max size",
+                  "type": "number",
+                  "path": "nearConfiguration.nearEvictionPolicy.SORTED",
+                  "model": "maxSize",
+                  "placeholder": 100000,
+                  "tip": [
+                    "Maximum allowed size of cache before entry will start getting evicted"
+                  ]
+                }
+              ]
+            }
+          }
+        }
+      ]
+    },
+    {
+      "label": "Statistics",
+      "group": "statistics",
+      "tip": [
+        "Cache statistics and management settings"
+      ],
+      "fields": [
+        {
+          "label": "Statistics enabled",
+          "id": "statisticsEnabled",
+          "type": "check",
+          "model": "statisticsEnabled",
+          "tip": [
+            "Flag indicating whether statistics gathering is enabled on a cache"
+          ]
+        },
+        {
+          "label": "Management enabled",
+          "id": "managementEnabled",
+          "type": "check",
+          "model": "managementEnabled",
+          "tip": [
+            "Flag indicating whether management is enabled on this cache"
+          ]
+        }
+      ]
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/models/clusters.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/models/clusters.json b/modules/control-center-web/src/main/js/controllers/models/clusters.json
new file mode 100644
index 0000000..a10e0c9
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/models/clusters.json
@@ -0,0 +1,1333 @@
+{
+  "screenTip": {
+    "workflowTitle": "On This Screen:",
+    "workflowContent": [
+      "Configure Clusters",
+      "Link Clusters to Caches",
+      "more-info"
+    ],
+    "whatsNextTitle": "Next Steps:",
+    "whatsNextContent": [
+      "Continue to <a href='/configuration/caches'>Caches</a>",
+      "Continue to <a href='/configuration/summary'>Summary</a>"
+    ]
+  },
+  "moreInfo": {
+    "title": "Clusters page",
+    "content": ["Manage you clusters on current page.",
+      "Generated clusters configuration available on <a href='/configuration/summary'>summary</a> page."]
+  },
+  "general": [
+    {
+      "label": "General",
+      "group": "general",
+      "fields": [
+        {
+          "label": "Name",
+          "id": "clusterName",
+          "type": "text",
+          "model": "name",
+          "required": true,
+          "placeholder": "Input name"
+        },
+        {
+          "label": "Caches",
+          "id": "caches",
+          "type": "dropdown-multiple",
+          "model": "caches",
+          "placeholder": "Choose caches",
+          "placeholderEmpty": "No caches configured",
+          "items": "caches",
+          "tip": [
+            "Select caches to start in cluster or add a new cache"
+          ],
+          "addLink": {
+            "label": "Add cache(s)",
+            "ref": "/configuration/caches?new"
+          }
+        },
+        {
+          "label": "Discovery",
+          "id": "discovery",
+          "type": "dropdown-details",
+          "settings": false,
+          "path": "discovery",
+          "model": "kind",
+          "items": "discoveries",
+          "tip": [
+            "Discovery allows to discover remote nodes in grid"
+          ],
+          "details": {
+            "Vm": {
+              "fields": [
+                {
+                  "label": "Addresses",
+                  "type": "table-simple",
+                  "path": "discovery.Vm",
+                  "model": "addresses",
+                  "reordering": true,
+                  "ipaddress": true,
+                  "placeholder": "IP address:port",
+                  "focusId": "IpAddress",
+                  "addTip": "Add new address.",
+                  "removeTip": "Remove address",
+                  "tableTip": [
+                    "Addresses may be represented as follows:",
+                    "<ul>",
+                    "  <li>IP address (e.g. 127.0.0.1, 9.9.9.9, etc)</li>",
+                    "  <li>IP address and port (e.g. 127.0.0.1:47500, 9.9.9.9:47501, etc)</li>",
+                    "  <li>IP address and port range (e.g. 127.0.0.1:47500..47510, 9.9.9.9:47501..47504, etc)</li>",
+                    "  <li>Hostname (e.g. host1.com, host2, etc)</li>",
+                    "  <li>Hostname and port (e.g. host1.com:47500, host2:47502, etc)</li>",
+                    "  <li>Hostname and port range (e.g. host1.com:47500..47510, host2:47502..47508, etc)</li>",
+                    "</ul>",
+                    "If port is 0 or not provided then default port will be used (depends on discovery SPI configuration)",
+                    "If port range is provided (e.g. host:port1..port2) the following should be considered:",
+                    "<ul>",
+                    "  <li>port1 < port2 should be true</li>",
+                    "  <li>Both port1 and port2 should be greater than 0</li>",
+                    "</ul>"
+                  ]
+                }
+              ]
+            },
+            "Multicast": {
+              "fields": [
+                {
+                  "label": "IP address",
+                  "type": "text",
+                  "path": "discovery.Multicast",
+                  "model": "multicastGroup",
+                  "placeholder": "228.1.2.4",
+                  "tip": [
+                    "IP address of multicast group"
+                  ]
+                },
+                {
+                  "label": "Port number",
+                  "type": "number",
+                  "path": "discovery.Multicast",
+                  "model": "multicastPort",
+                  "max": 65535,
+                  "placeholder": 47400,
+                  "tip": [
+                    "Port number which multicast messages are sent to"
+                  ]
+                },
+                {
+                  "label": "Waits for reply",
+                  "type": "number",
+                  "path": "discovery.Multicast",
+                  "model": "responseWaitTime",
+                  "placeholder": 500,
+                  "tip": [
+                    "Time in milliseconds IP finder waits for reply to multicast address request"
+                  ]
+                },
+                {
+                  "label": "Attempts count",
+                  "type": "number",
+                  "path": "discovery.Multicast",
+                  "model": "addressRequestAttempts",
+                  "placeholder": 2,
+                  "tip": [
+                    "Number of attempts to send multicast address request.",
+                    "IP finder re-sends request only in case if no reply for previous request is received"
+                  ]
+                },
+                {
+                  "label": "Local address",
+                  "type": "text",
+                  "path": "discovery.Multicast",
+                  "model": "localAddress",
+                  "tip": [
+                    "Local host address used by this IP finder",
+                    "If provided address is non-loopback then multicast socket is bound to this interface",
+                    "If local address is not set or is any local address then IP finder creates multicast sockets for all found non-loopback addresses"
+                  ]
+                },
+                {
+                  "label": "Addresses",
+                  "type": "table-simple",
+                  "path": "discovery.Multicast",
+                  "model": "addresses",
+                  "reordering": true,
+                  "ipaddress": true,
+                  "placeholder": "IP address:port",
+                  "focusId": "IpAddress",
+                  "addTip": "Add new address",
+                  "removeTip": "Remove address",
+                  "tableTip": [
+                    "Addresses may be represented as follows:",
+                    "<ul>",
+                    "  <li>IP address (e.g. 127.0.0.1, 9.9.9.9, etc)</li>",
+                    "  <li>IP address and port (e.g. 127.0.0.1:47500, 9.9.9.9:47501, etc)</li>",
+                    "  <li>IP address and port range (e.g. 127.0.0.1:47500..47510, 9.9.9.9:47501..47504, etc)</li>",
+                    "  <li>Hostname (e.g. host1.com, host2, etc)</li>",
+                    "  <li>Hostname and port (e.g. host1.com:47500, host2:47502, etc)</li>",
+                    "  <li>Hostname and port range (e.g. host1.com:47500..47510, host2:47502..47508, etc)</li>",
+                    "</ul>",
+                    "If port is 0 or not provided then default port will be used (depends on discovery SPI configuration)",
+                    "If port range is provided (e.g. host:port1..port2) the following should be considered:",
+                    "<ul>",
+                    "  <li>port1 < port2 should be true</li>",
+                    "  <li>Both port1 and port2 should be greater than 0</li>",
+                    "</ul>"
+                  ]
+                }
+              ]
+            },
+            "S3": {
+              "fields": [
+                {
+                  "label": "Bucket name",
+                  "type": "text",
+                  "required": true,
+                  "path": "discovery.S3",
+                  "model": "bucketName",
+                  "tip": [
+                    "Bucket name for IP finder"
+                  ]
+                },
+                {
+                  "label": "Note, AWS credentials will be generated as stubs.",
+                  "type": "label"
+                }
+              ]
+            },
+            "Cloud": {
+              "fields": [
+                {
+                  "label": "Credential",
+                  "type": "text",
+                  "path": "discovery.Cloud",
+                  "model": "credential",
+                  "tip": [
+                    "Credential that is used during authentication on the cloud",
+                    "Depending on a cloud platform it can be a password or access key"
+                  ]
+                },
+                {
+                  "label": "Path to credential",
+                  "type": "text",
+                  "path": "discovery.Cloud",
+                  "model": "credentialPath",
+                  "tip": [
+                    "Path to a credential that is used during authentication on the cloud",
+                    "Access key or private key should be stored in a plain or PEM file without a passphrase"
+                  ]
+                },
+                {
+                  "label": "Identity",
+                  "type": "text",
+                  "required": true,
+                  "path": "discovery.Cloud",
+                  "model": "identity",
+                  "tip": [
+                    "Identity that is used as a user name during a connection to the cloud",
+                    "Depending on a cloud platform it can be an email address, user name, etc"
+                  ]
+                },
+                {
+                  "label": "Provider",
+                  "type": "text",
+                  "required": true,
+                  "path": "discovery.Cloud",
+                  "model": "provider",
+                  "tip": [
+                    "Cloud provider to use"
+                  ]
+                },
+                {
+                  "label": "Regions",
+                  "type": "table-simple",
+                  "path": "discovery.Cloud",
+                  "model": "regions",
+                  "placeholder": "Region name",
+                  "focusId": "Region",
+                  "addTip": "Add new region",
+                  "removeTip": "Remove region",
+                  "tableTip": [
+                    "List of regions where VMs are located",
+                    "If the regions are not set then every region, that a cloud provider has, will be investigated. This could lead to significant performance degradation",
+                    "Note, that some cloud providers, like Google Compute Engine, doesn't have a notion of a region. For such providers regions are redundant"
+                  ],
+                  "tip": [
+                    "Region where VMs are located"
+                  ]
+                },
+                {
+                  "label": "Zones",
+                  "ui": "table-simple",
+                  "type": "table-simple",
+                  "path": "discovery.Cloud",
+                  "model": "zones",
+                  "placeholder": "Zone name",
+                  "focusId": "Zone",
+                  "addTip": "Add new zone",
+                  "removeTip": "Remove zone",
+                  "tableTip": [
+                    "List of zones where VMs are located",
+                    "If the zones are not set then every zone from specified regions, will be taken into account",
+                    "Note, that some cloud providers, like Rackspace, doesn't have a notion of a zone. For such providers zones are redundant"
+                  ],
+                  "tip": [
+                    "Zone where VMs are located"
+                  ]
+                }
+              ]
+            },
+            "GoogleStorage": {
+              "fields": [
+                {
+                  "label": "Project name",
+                  "type": "text",
+                  "required": true,
+                  "path": "discovery.GoogleStorage",
+                  "model": "projectName",
+                  "tip": [
+                    "Google Cloud Platforms project name",
+                    "Usually this is an auto generated project number (ex. 208709979073) that can be found in 'Overview' section of Google Developer Console"
+                  ]
+                },
+                {
+                  "label": "Bucket name",
+                  "type": "text",
+                  "required": true,
+                  "path": "discovery.GoogleStorage",
+                  "model": "bucketName",
+                  "tip": [
+                    "Google Cloud Storage bucket name",
+                    "If the bucket doesn't exist Ignite will automatically create it",
+                    "However the name must be unique across whole Google Cloud Storage and Service Account Id must be authorized to perform this operation"
+                  ]
+                },
+                {
+                  "label": "Private key path",
+                  "type": "text",
+                  "required": true,
+                  "path": "discovery.GoogleStorage",
+                  "model": "serviceAccountP12FilePath",
+                  "tip": [
+                    "Full path to the private key in PKCS12 format of the Service Account"
+                  ]
+                },
+                {
+                  "label": "Account id",
+                  "type": "text",
+                  "required": true,
+                  "path": "discovery.GoogleStorage",
+                  "model": "serviceAccountId",
+                  "tip": [
+                    "Service account ID (typically an e-mail address)"
+                  ]
+                }
+              ]
+            },
+            "Jdbc": {
+              "fields": [
+                {
+                  "label": "DB schema should be initialized by Ignite",
+                  "type": "check",
+                  "path": "discovery.Jdbc",
+                  "model": "initSchema",
+                  "tip": [
+                    "Flag indicating whether DB schema should be initialized by Ignite or was explicitly created by user"
+                  ]
+                }
+              ]
+            },
+            "SharedFs": {
+              "fields": [
+                {
+                  "label": "File path",
+                  "type": "text",
+                  "path": "discovery.SharedFs",
+                  "model": "path",
+                  "placeholder": "disco/tcp"
+                }
+              ]
+            }
+          }
+        }
+      ]
+    }
+  ],
+  "advanced": [
+    {
+      "label": "Atomic configuration",
+      "group": "atomics",
+      "tip": [
+        "Configuration for atomic data structures",
+        "Atomics are distributed across the cluster, essentially enabling performing atomic operations (such as increment-and-get or compare-and-set) with the same globally-visible value"
+      ],
+      "fields": [
+        {
+          "label": "Cache mode",
+          "id": "cacheMode",
+          "type": "dropdown",
+          "path": "atomicConfiguration",
+          "model": "cacheMode",
+          "placeholder": "PARTITIONED",
+          "items": "cacheModes",
+          "tip": [
+            "Cache modes:",
+            "<ul>",
+            "  <li>Partitioned - in this mode the overall key set will be divided into partitions and all partitions will be split equally between participating nodes</li>",
+            "  <li>Replicated - in this mode all the keys are distributed to all participating nodes</li>",
+            "  <li>Local - in this mode caches residing on different grid nodes will not know about each other</li>",
+            "</ul>"
+          ]
+        },
+        {
+          "label": "Sequence reserve",
+          "id": "atomicSequenceReserveSize",
+          "type": "number",
+          "path": "atomicConfiguration",
+          "model": "atomicSequenceReserveSize",
+          "placeholder": 1000,
+          "tip": [
+            "Default number of sequence values reserved for IgniteAtomicSequence instances",
+            "After a certain number has been reserved, consequent increments of sequence will happen locally, without communication with other nodes, until the next reservation has to be made"
+          ]
+        },
+        {
+          "label": "Backups",
+          "id": "backups",
+          "type": "number",
+          "path": "atomicConfiguration",
+          "model": "backups",
+          "hide": "backupItem.atomicConfiguration &&  backupItem.atomicConfiguration.cacheMode && backupItem.atomicConfiguration.cacheMode != 'PARTITIONED'",
+          "placeholder": 0,
+          "tip": [
+            "Number of backup nodes"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Communication",
+      "group": "communication",
+      "tip": [
+        "Cluster communication network properties"
+      ],
+      "fields": [
+        {
+          "label": "Timeout",
+          "id": "networkTimeout",
+          "type": "number",
+          "model": "networkTimeout",
+          "placeholder": 5000,
+          "tip": [
+            "Maximum timeout in milliseconds for network requests"
+          ]
+        },
+        {
+          "label": "Send retry delay",
+          "id": "networkSendRetryDelay",
+          "type": "number",
+          "model": "networkSendRetryDelay",
+          "placeholder": 1000,
+          "tip": [
+            "Interval in milliseconds between message send retries"
+          ]
+        },
+        {
+          "label": "Send retry count",
+          "id": "networkSendRetryCount",
+          "type": "number",
+          "model": "networkSendRetryCount",
+          "placeholder": 3,
+          "tip": [
+            "Message send retries count"
+          ]
+        },
+        {
+          "label": "Discovery startup delay",
+          "id": "discoveryStartupDelay",
+          "type": "number",
+          "model": "discoveryStartupDelay",
+          "placeholder": 600000,
+          "tip": [
+            "This value is used to expire messages from waiting list whenever node discovery discrepancies happen"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Deployment",
+      "group": "deployment",
+      "previewMinLines": 1,
+      "tip": [
+        "Task and resources deployment in cluster"
+      ],
+      "fields": [
+        {
+          "label": "Mode",
+          "id": "deploymentMode",
+          "type": "dropdown",
+          "model": "deploymentMode",
+          "placeholder": "SHARED",
+          "items": "deploymentModes",
+          "tip": [
+            "Task classes and resources sharing mode",
+            "The following deployment modes are supported:",
+            "<ul>",
+            "  <li>PRIVATE - in this mode deployed classes do not share resources</li>",
+            "  <li>ISOLATED - in this mode tasks or classes deployed within the same class loader will share the same instances of resources</li>",
+            "  <li>SHARED - same as ISOLATED, but now tasks from different master nodes with the same user version and same class loader will share the same class loader on remote nodes</li>",
+            "  <li>CONTINUOUS - same as SHARED deployment mode, but resources will not be undeployed even after all master nodes left grid</li>",
+            "</ul>"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Discovery",
+      "group": "discovery",
+      "tip": [
+        "Discovery properties configuration"
+      ],
+      "fields": [
+        {
+          "label": "Local address",
+          "id": "localAddress",
+          "type": "text",
+          "path": "discovery",
+          "model": "localAddress",
+          "placeholder": "228.1.2.4",
+          "tip": [
+            "Local address"
+          ]
+        },
+        {
+          "label": "Local port",
+          "id": "localPort",
+          "type": "number",
+          "path": "discovery",
+          "model": "localPort",
+          "placeholder": 47500,
+          "tip": [
+            "Local port which node uses"
+          ]
+        },
+        {
+          "label": "Local port range",
+          "id": "localPortRange",
+          "type": "number",
+          "path": "discovery",
+          "model": "localPortRange",
+          "placeholder": 100,
+          "tip": [
+            "Local port range"
+          ]
+        },
+        {
+          "label": "Address resolver",
+          "id": "addressResolver",
+          "type": "text",
+          "path": "discovery",
+          "model": "addressResolver",
+          "tip": [
+            "Class name of resolution between external and internal addresses provider"
+          ]
+        },
+        {
+          "label": "Socket timeout",
+          "id": "socketTimeout",
+          "type": "number",
+          "path": "discovery",
+          "model": "socketTimeout",
+          "tip": [
+            "Socket operations timeout"
+          ]
+        },
+        {
+          "label": "Acknowledgement timeout",
+          "id": "ackTimeout",
+          "type": "number",
+          "path": "discovery",
+          "model": "ackTimeout",
+          "tip": [
+            "Message acknowledgement timeout"
+          ]
+        },
+        {
+          "label": "Max acknowledgement timeout",
+          "id": "maxAckTimeout",
+          "type": "number",
+          "path": "discovery",
+          "model": "maxAckTimeout",
+          "placeholder": 600000,
+          "tip": [
+            "Maximum message acknowledgement timeout"
+          ]
+        },
+        {
+          "label": "Network timeout",
+          "id": "networkTimeout",
+          "type": "number",
+          "path": "discovery",
+          "model": "networkTimeout",
+          "placeholder": 5000,
+          "tip": [
+            "Network timeout"
+          ]
+        },
+        {
+          "label": "Join timeout",
+          "id": "joinTimeout",
+          "type": "number",
+          "path": "discovery",
+          "model": "joinTimeout",
+          "placeholder": 0,
+          "tip": [
+            "Join timeout"
+          ]
+        },
+        {
+          "label": "Thread priority",
+          "id": "threadPriority",
+          "type": "number",
+          "path": "discovery",
+          "model": "threadPriority",
+          "placeholder": 10,
+          "tip": [
+            "Thread priority for all threads started by SPI"
+          ]
+        },
+        {
+          "label": "Heartbeat frequency",
+          "id": "heartbeatFrequency",
+          "type": "number",
+          "path": "discovery",
+          "model": "heartbeatFrequency",
+          "placeholder": 2000,
+          "tip": [
+            "Heartbeat messages issuing frequency"
+          ]
+        },
+        {
+          "label": "Max heartbeats miss w/o init",
+          "id": "maxMissedClientHeartbeats",
+          "type": "number",
+          "path": "discovery",
+          "model": "maxMissedClientHeartbeats",
+          "placeholder": 1,
+          "tip": [
+            "Max heartbeats count node can miss without initiating status check"
+          ]
+        },
+        {
+          "label": "Max heartbeats miss w/o failing client node",
+          "id": "maxMissedClientHbs",
+          "type": "number",
+          "path": "discovery",
+          "model": "maxMissedClientHbs",
+          "placeholder": 5,
+          "tip": [
+            "Max heartbeats count node can miss without failing client node"
+          ]
+        },
+        {
+          "label": "Topology history",
+          "id": "topHistorySize",
+          "type": "number",
+          "path": "discovery",
+          "model": "topHistorySize",
+          "placeholder": 1000,
+          "tip": [
+            "Size of topology snapshots history"
+          ]
+        },
+        {
+          "label": "Discovery listener",
+          "id": "listener",
+          "type": "text",
+          "path": "discovery",
+          "model": "listener",
+          "tip": [
+            "Grid discovery listener"
+          ]
+        },
+        {
+          "label": "Data exchange",
+          "id": "dataExchange",
+          "type": "text",
+          "path": "discovery",
+          "model": "dataExchange",
+          "tip": [
+            "Class name of handler for initial data exchange between Ignite nodes"
+          ]
+        },
+        {
+          "label": "Metrics provider",
+          "id": "metricsProvider",
+          "type": "text",
+          "path": "discovery",
+          "model": "metricsProvider",
+          "tip": [
+            "Class name of metric provider to discovery SPI"
+          ]
+        },
+        {
+          "label": "Reconnect count",
+          "id": "reconnectCount",
+          "type": "number",
+          "path": "discovery",
+          "model": "reconnectCount",
+          "placeholder": 10,
+          "tip": [
+            "Reconnect attempts count"
+          ]
+        },
+        {
+          "label": "Statistics frequency",
+          "id": "statisticsPrintFrequency",
+          "type": "number",
+          "path": "discovery",
+          "model": "statisticsPrintFrequency",
+          "placeholder": 0,
+          "tip": [
+            "Statistics print frequency"
+          ]
+        },
+        {
+          "label": "IP finder clean frequency",
+          "id": "ipFinderCleanFrequency",
+          "type": "number",
+          "path": "discovery",
+          "model": "ipFinderCleanFrequency",
+          "placeholder": 60000,
+          "tip": [
+            "IP finder clean frequency"
+          ]
+        },
+        {
+          "label": "Node authenticator",
+          "id": "authenticator",
+          "type": "text",
+          "path": "discovery",
+          "model": "authenticator",
+          "tip": [
+            "Node authenticator"
+          ]
+        },
+        {
+          "label": "Force server mode",
+          "id": "forceServerMode",
+          "type": "check",
+          "path": "discovery",
+          "model": "forceServerMode",
+          "tip": [
+            "Force server mode"
+          ]
+        },
+        {
+          "label": "Client reconnect disabled",
+          "id": "clientReconnectDisabled",
+          "type": "check",
+          "path": "discovery",
+          "model": "clientReconnectDisabled",
+          "tip": [
+            "Client reconnect disabled"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Events",
+      "group": "events",
+      "tip": [
+        " Grid events are used for notification about what happens within the grid"
+      ],
+      "fields": [
+        {
+          "label": "Include type",
+          "id": "includeEventTypes",
+          "type": "dropdown-multiple",
+          "model": "includeEventTypes",
+          "placeholder": "Choose recorded event types",
+          "items": "events",
+          "tip": [
+            "Array of event types, which will be recorded by GridEventStorageManager#record(Event)",
+            "Note, that either the include event types or the exclude event types can be established"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Marshaller",
+      "group": "marshaller",
+      "tip": [
+        "Marshaller allows to marshal or unmarshal objects in grid",
+        "It provides serialization/deserialization mechanism for all instances that are sent across networks or are otherwise serialized"
+      ],
+      "fields": [
+        {
+          "label": "Marshaller",
+          "model": "marshaller",
+          "type": "dropdown-details",
+          "settings": true,
+          "path": "marshaller",
+          "model": "kind",
+          "placeholder": "Choose marshaller",
+          "items": "marshallers",
+          "tip": [
+            "Instance of marshaller to use in grid. If not provided, OptimizedMarshaller will be used on Java HotSpot VM, and JdkMarshaller will be used on other VMs"
+          ],
+          "details": {
+            "OptimizedMarshaller": {
+              "expanded": false,
+              "fields": [
+                {
+                  "label": "Streams pool size",
+                  "type": "number",
+                  "path": "marshaller.OptimizedMarshaller",
+                  "model": "poolSize",
+                  "placeholder": 0,
+                  "tip": [
+                    "Specifies size of cached object streams used by marshaller",
+                    "Object streams are cached for performance reason to avoid costly recreation for every serialization routine",
+                    "If 0 (default), pool is not used and each thread has its own cached object stream which it keeps reusing",
+                    "Since each stream has an internal buffer, creating a stream for each thread can lead to high memory consumption if many large messages are marshalled or unmarshalled concurrently",
+                    "Consider using pool in this case. This will limit number of streams that can be created and, therefore, decrease memory consumption",
+                    "NOTE: Using streams pool can decrease performance since streams will be shared between different threads which will lead to more frequent context switching"
+                  ]
+                },
+                {
+                  "label": "Require serializable",
+                  "type": "check",
+                  "path": "marshaller.OptimizedMarshaller",
+                  "model": "requireSerializable",
+                  "tip": [
+                    "Whether marshaller should require Serializable interface or not"
+                  ]
+                }
+              ]
+            }
+          }
+        },
+        {
+          "label": "Marshal local jobs",
+          "type": "check",
+          "model": "marshalLocalJobs",
+          "placeholder": "false",
+          "tip": [
+            "If this flag is enabled, jobs mapped to local node will be marshalled as if it was remote node"
+          ]
+        },
+        {
+          "label": "Keep alive time",
+          "type": "number",
+          "model": "marshallerCacheKeepAliveTime",
+          "placeholder": 10000,
+          "tip": [
+            "Keep alive time of thread pool that is in charge of processing marshaller messages"
+          ]
+        },
+        {
+          "label": "Pool size",
+          "type": "number",
+          "model": "marshallerCacheThreadPoolSize",
+          "placeholder": "max(8, availableProcessors) * 2",
+          "tip": [
+            "Default size of thread pool that is in charge of processing marshaller messages"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Metrics",
+      "group": "metrics",
+      "tip": [
+        "Cluster runtime metrics settings"
+      ],
+      "fields": [
+        {
+          "label": "Elapsed time",
+          "id": "metricsExpireTime",
+          "type": "number",
+          "model": "metricsExpireTime",
+          "placeholder": "Long.MAX_VALUE",
+          "min": 1,
+          "tip": [
+            "Time in milliseconds after which a certain metric value is considered expired"
+          ]
+        },
+        {
+          "label": "History size",
+          "id": "metricsHistorySize",
+          "type": "number",
+          "model": "metricsHistorySize",
+          "placeholder": 10000,
+          "min": 1,
+          "tip": [
+            "Number of metrics kept in history to compute totals and averages"
+          ]
+        },
+        {
+          "label": "Log frequency",
+          "id": "metricsLogFrequency",
+          "type": "number",
+          "model": "metricsLogFrequency",
+          "placeholder": 60000,
+          "tip": [
+            "Frequency of metrics log print out. To disable set to 0"
+          ]
+        },
+        {
+          "label": "Update frequency",
+          "id": "metricsUpdateFrequency",
+          "type": "number",
+          "model": "metricsUpdateFrequency",
+          "placeholder": 60000,
+          "tip": [
+            "Job metrics update frequency in milliseconds",
+            "<ul>",
+            "  <li>If set to -1 job metrics are never updated</li>",
+            "  <li>If set to 0 job metrics are updated on each job start and finish</li>",
+            "  <li>Positive value defines the actual update frequency</li>",
+            "</ul>"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Peer class loading",
+      "group": "p2p",
+      "tip": [
+        "Cluster peer class loading settings"
+      ],
+      "fields": [
+        {
+          "label": "Enable peer class loading",
+          "id": "peerClassLoadingEnabled",
+          "type": "check",
+          "model": "peerClassLoadingEnabled",
+          "tip": [
+            "Enables/disables peer class loading"
+          ]
+        },
+        {
+          "label": "Missed resources cache size",
+          "id": "peerClassLoadingMissedResourcesCacheSize",
+          "type": "number",
+          "model": "peerClassLoadingMissedResourcesCacheSize",
+          "disabled": "!backupItem.peerClassLoadingEnabled",
+          "placeholder": 100,
+          "tip": [
+            "If size greater than 0, missed resources will be cached and next resource request ignored",
+            "If size is 0, then request for the resource will be sent to the remote node every time this resource is requested"
+          ]
+        },
+        {
+          "label": "Pool size",
+          "id": "peerClassLoadingThreadPoolSize",
+          "type": "number",
+          "model": "peerClassLoadingThreadPoolSize",
+          "disabled": "!backupItem.peerClassLoadingEnabled",
+          "placeholder": "availableProcessors",
+          "tip": [
+            "Thread pool size to use for peer class loading"
+          ]
+        },
+        {
+          "label": "Local class path exclude",
+          "id": "peerClassLoadingLocalClassPathExclude",
+          "type": "table-simple",
+          "model": "peerClassLoadingLocalClassPathExclude",
+          "disabled": "!backupItem.peerClassLoadingEnabled",
+          "focusId": "PeerClsPathExclude",
+          "addTip": "Add package name",
+          "removeTip": "Remove package name",
+          "tableTip": [
+            "List of packages from the system classpath that need to be peer-to-peer loaded from task originating node",
+            "'*' is supported at the end of the package name which means that all sub-packages and their classes are included like in Java package import clause"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "SSL configuration",
+      "group": "sslConfiguration",
+      "tip": [
+        "Settings for SSL configuration"
+      ],
+      "fields": [
+        {
+          "label": "Enabled",
+          "id": "sslEnabled",
+          "type": "check",
+          "model": "sslEnabled",
+          "tip": [
+            "Flag indicating whether to configure SSL configuration"
+          ]
+        },
+        {
+          "label": "Algorithm to create a key manager",
+          "id": "keyAlgorithm",
+          "type": "typeahead",
+          "path": "sslContextFactory",
+          "model": "keyAlgorithm",
+          "placeholder": "SumX509",
+          "items": "sslKeyAlgorithms",
+          "hide": "!backupItem.sslEnabled",
+          "tip": [
+            "Sets key manager algorithm that will be used to create a key manager",
+            "Notice that in most cased default value suites well, however, on Android platform this value need to be set to X509"
+          ]
+        },
+        {
+          "label": "Key store file",
+          "id": "keyStoreFilePath",
+          "type": "text",
+          "path": "sslContextFactory",
+          "model": "keyStoreFilePath",
+          "required": true,
+          "hide": "!backupItem.sslEnabled",
+          "tip": [
+            "Path to the key store file",
+            "This is a mandatory parameter since ssl context could not be initialized without key manager"
+          ]
+        },
+        {
+          "label": "Key store type",
+          "id": "keyStoreType",
+          "type": "typeahead",
+          "path": "sslContextFactory",
+          "model": "keyStoreType",
+          "placeholder": "JKS",
+          "items": "sslStoreType",
+          "hide": "!backupItem.sslEnabled",
+          "tip": [
+            "Key store type used in context initialization"
+          ]
+        },
+        {
+          "label": "Protocol",
+          "id": "protocol",
+          "type": "typeahead",
+          "path": "sslContextFactory",
+          "model": "protocol",
+          "placeholder": "TSL",
+          "items": "sslProtocols",
+          "hide": "!backupItem.sslEnabled",
+          "tip": [
+            "Protocol for secure transport"
+          ]
+        },
+        {
+          "label": "Trust managers",
+          "type": "table-simple",
+          "path": "sslContextFactory",
+          "model": "trustManagers",
+          "placeholder": "Trust manager",
+          "focusId": "trustManagers",
+          "addTip": "Add new trust manager.",
+          "removeTip": "Remove trust manager",
+          "hide": "!backupItem.sslEnabled",
+          "tableTip": [
+            "Pre-configured trust managers"
+          ]
+        },
+        {
+          "label": "Trust store file",
+          "id": "trustStoreFilePath",
+          "type": "text",
+          "path": "sslContextFactory",
+          "model": "trustStoreFilePath",
+          "hide": "!backupItem.sslEnabled || trustManagersConfigured()",
+          "tip": [
+            "Path to the trust store file"
+          ]
+        },
+        {
+          "label": "Trust store type",
+          "id": "trustStoreType",
+          "type": "typeahead",
+          "path": "sslContextFactory",
+          "model": "trustStoreType",
+          "placeholder": "JKS",
+          "items": "sslStoreType",
+          "hide": "!backupItem.sslEnabled || trustManagersConfigured()",
+          "tip": [
+            "Trust store type used in context initialization"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Swap",
+      "group": "swap",
+      "tip": [
+        "Settings for overflow data to disk if it cannot fit in memory"
+      ],
+      "fields": [
+        {
+          "label": "Swap space SPI",
+          "id": "swapSpaceSpi",
+          "type": "dropdown-details",
+          "settings": true,
+          "path": "swapSpaceSpi",
+          "model": "kind",
+          "items": "swapSpaceSpis",
+          "placeholder": "Choose swap SPI",
+          "tip": [
+            "Provides a mechanism in grid for storing data on disk",
+            "Ignite cache uses swap space to overflow data to disk if it cannot fit in memory"
+          ],
+          "details": {
+            "FileSwapSpaceSpi": {
+              "fields": [
+                {
+                  "label": "Base directory",
+                  "type": "text",
+                  "path": "swapSpaceSpi.FileSwapSpaceSpi",
+                  "model": "baseDirectory",
+                  "placeholder": "swapspace",
+                  "tip": [
+                    "Base directory where to write files"
+                  ]
+                },
+                {
+                  "label": "Read stripe size",
+                  "type": "number",
+                  "path": "swapSpaceSpi.FileSwapSpaceSpi",
+                  "model": "readStripesNumber",
+                  "placeholder": "available CPU cores",
+                  "tip": [
+                    "Read stripe size defines number of file channels to be used concurrently"
+                  ]
+                },
+                {
+                  "label": "Maximum sparsity",
+                  "type": "number",
+                  "path": "swapSpaceSpi.FileSwapSpaceSpi",
+                  "model": "maximumSparsity",
+                  "placeholder": 0.5,
+                  "tip": [
+                    "This property defines maximum acceptable wasted file space to whole file size ratio",
+                    "When this ratio becomes higher than specified number compacting thread starts working"
+                  ]
+                },
+                {
+                  "label": "Max write queue size",
+                  "type": "number",
+                  "path": "swapSpaceSpi.FileSwapSpaceSpi",
+                  "model": "maxWriteQueueSize",
+                  "placeholder": "1024 * 1024",
+                  "tip": [
+                    "Max write queue size in bytes",
+                    "If there are more values are waiting for being written to disk then specified size, SPI will block on store operation"
+                  ]
+                },
+                {
+                  "label": "Write buffer size",
+                  "type": "number",
+                  "path": "swapSpaceSpi.FileSwapSpaceSpi",
+                  "model": "writeBufferSize",
+                  "placeholder": "Available CPU cores",
+                  "tip": [
+                    "Write buffer size in bytes",
+                    "Write to disk occurs only when this buffer is full"
+                  ]
+                }
+              ]
+            }
+          }
+        }
+      ]
+    },
+    {
+      "label": "Time configuration",
+      "group": "time",
+      "tip": [
+        "Time settings for CLOCK write ordering mode"
+      ],
+      "fields": [
+        {
+          "label": "Samples size",
+          "id": "clockSyncSamples",
+          "type": "number",
+          "model": "clockSyncSamples",
+          "placeholder": 8,
+          "tip": [
+            "Number of samples used to synchronize clocks between different nodes",
+            "Clock synchronization is used for cache version assignment in CLOCK order mode"
+          ]
+        },
+        {
+          "label": "Frequency",
+          "id": "clockSyncFrequency",
+          "type": "number",
+          "model": "clockSyncFrequency",
+          "placeholder": 120000,
+          "tip": [
+            "Frequency at which clock is synchronized between nodes, in milliseconds",
+            "Clock synchronization is used for cache version assignment in CLOCK order mode"
+          ]
+        },
+        {
+          "label": "Port base",
+          "id": "timeServerPortBase",
+          "type": "number",
+          "model": "timeServerPortBase",
+          "max": 65535,
+          "placeholder": 31100,
+          "tip": [
+            "Time server provides clock synchronization between nodes",
+            "Base UPD port number for grid time server. Time server will be started on one of free ports in range"
+          ]
+        },
+        {
+          "label": "Port range",
+          "id": "timeServerPortRange",
+          "type": "number",
+          "model": "timeServerPortRange",
+          "placeholder": 100,
+          "tip": [
+            "Time server port range"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Thread pools size",
+      "group": "pools",
+      "tip": [
+        "Settings for node thread pools"
+      ],
+      "fields": [
+        {
+          "label": "Public",
+          "id": "publicThreadPoolSize",
+          "type": "number",
+          "model": "publicThreadPoolSize",
+          "placeholder": "max(8, availableProcessors) * 2",
+          "tip": [
+            "Thread pool that is in charge of processing ComputeJob, GridJobs and user messages sent to node"
+          ]
+        },
+        {
+          "label": "System",
+          "id": "systemThreadPoolSize",
+          "type": "number",
+          "model": "systemThreadPoolSize",
+          "placeholder": "max(8, availableProcessors) * 2",
+          "tip": [
+            "Thread pool that is in charge of processing internal system messages"
+          ]
+        },
+        {
+          "label": "Management",
+          "id": "managementThreadPoolSize",
+          "type": "number",
+          "model": "managementThreadPoolSize",
+          "placeholder": 4,
+          "tip": [
+            "Thread pool that is in charge of processing internal and Visor ComputeJob, GridJobs"
+          ]
+        },
+        {
+          "label": "IGFS",
+          "id": "igfsThreadPoolSize",
+          "type": "number",
+          "model": "igfsThreadPoolSize",
+          "placeholder": "availableProcessors",
+          "tip": [
+            "Thread pool that is in charge of processing outgoing IGFS messages"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Transactions",
+      "group": "transactions",
+      "tip": [
+        "Settings for transactions"
+      ],
+      "fields": [
+        {
+          "label": "Concurrency",
+          "id": "defaultTxConcurrency",
+          "type": "dropdown",
+          "path": "transactionConfiguration",
+          "model": "defaultTxConcurrency",
+          "placeholder": "PESSIMISTIC",
+          "items": "transactionConcurrency",
+          "tip": [
+            "Cache transaction concurrency to use when one is not explicitly specified"
+          ]
+        },
+        {
+          "label": "Isolation",
+          "id": "transactionIsolation",
+          "type": "dropdown",
+          "path": "transactionConfiguration",
+          "model": "transactionIsolation",
+          "placeholder": "REPEATABLE_READ",
+          "items": "transactionIsolation",
+          "tip": [
+            "Default transaction isolation"
+          ]
+        },
+        {
+          "label": "Default timeout",
+          "id": "defaultTxTimeout",
+          "type": "number",
+          "path": "transactionConfiguration",
+          "model": "defaultTxTimeout",
+          "placeholder": 0,
+          "tip": [
+            "Default transaction timeout"
+          ]
+        },
+        {
+          "label": "Pessimistic log cleanup delay",
+          "id": "pessimisticTxLogLinger",
+          "type": "number",
+          "path": "transactionConfiguration",
+          "model": "pessimisticTxLogLinger",
+          "placeholder": 10000,
+          "tip": [
+            "Delay, in milliseconds, after which pessimistic recovery entries will be cleaned up for failed node"
+          ]
+        },
+        {
+          "label": "Pessimistic log size",
+          "id": "pessimisticTxLogSize",
+          "type": "number",
+          "path": "transactionConfiguration",
+          "model": "pessimisticTxLogSize",
+          "placeholder": 0,
+          "tip": [
+            "Size of pessimistic transactions log stored on node in order to recover transaction commit if originating node has left grid before it has sent all messages to transaction nodes"
+          ]
+        },
+        {
+          "label": "Manager lookup",
+          "id": "txManagerLookupClassName",
+          "type": "text",
+          "path": "transactionConfiguration",
+          "model": "txManagerLookupClassName",
+          "tip": [
+            "Class name of transaction manager finder for integration for JEE app servers"
+          ]
+        },
+        {
+          "label": "Enable serializable cache transactions",
+          "id": "txSerializableEnabled",
+          "type": "check",
+          "path": "transactionConfiguration",
+          "model": "txSerializableEnabled",
+          "hide": "true",
+          "tip": [
+            "Flag to enable/disable isolation level for cache transactions",
+            "Serializable level does carry certain overhead and if not used, should be disabled"
+          ]
+        }
+      ]
+    }
+  ]
+}


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/models/metadata.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/models/metadata.json b/modules/control-center-web/src/main/js/controllers/models/metadata.json
new file mode 100644
index 0000000..7ef0e20
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/models/metadata.json
@@ -0,0 +1,279 @@
+{
+  "screenTip": {
+    "workflowTitle": "On This Screen:",
+    "workflowContent": [
+      "Manually enter Metadata",
+      "Load Metadata from Database",
+      "more-info"
+    ],
+    "whatsNextTitle": "Next Steps:",
+    "whatsNextContent": [
+      "Continue to <a href='/configuration/summary'>Summary</a>",
+      "Back to <a href='/configuration/caches'>Caches</a>",
+      "Back to <a href='/configuration/clusters'>Clusters</a>"
+    ]
+  },
+  "moreInfo": {
+    "title": "Metadata page",
+    "content": ["Manage you type metadata on current page.",
+      "Metadata can be assigned to specified <a href='/configuration/caches'>caches</a>.",
+      "Generated cluster with caches with metadata configuration available on <a href='/configuration/summary'>summary</a> page."]
+  },
+  "metadata": [
+    {
+      "label": "Metadata common",
+      "group": "general",
+      "tip": [
+        "Metadata properties common to Query and Store"
+      ],
+      "fields": [
+        {
+          "label": "Caches",
+          "id": "caches",
+          "type": "dropdown-multiple",
+          "model": "caches",
+          "placeholder": "Choose caches",
+          "placeholderEmpty": "No caches configured",
+          "items": "caches",
+          "tip": [
+            "Select caches to associate database with cache"
+          ],
+          "addLink": {
+            "label": "Add cache(s)",
+            "ref": "/configuration/caches?new"
+          }
+        },
+        {
+          "label": "Key type",
+          "id": "keyType",
+          "type": "typeahead",
+          "items": "javaBuildInClasses",
+          "model": "keyType",
+          "required": true,
+          "placeholder": "Full class name for Key",
+          "tip": [
+            "Key class used to store key in cache"
+          ]
+        },
+        {
+          "label": "Value type",
+          "id": "valueType",
+          "type": "text",
+          "model": "valueType",
+          "required": true,
+          "placeholder": "Full class name for Value",
+          "tip": [
+            "Value class used to store value in cache"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Metadata for SQL query",
+      "group": "query",
+      "tip": [
+        "Metadata properties for fields queries"
+      ],
+      "fields": [
+        {
+          "label": "Not indexed fields",
+          "id": "queryFields",
+          "ui": "table-pair",
+          "type": "queryFieldsFirst",
+          "model": "queryFields",
+          "keyName": "name",
+          "valueName": "className",
+          "focusId": "QryField",
+          "addTip": "Add not indexed field to query",
+          "removeTip": "Remove field",
+          "tip": [
+            "Collection of name-to-type mappings to be queried, in addition to indexed fields"
+          ]
+        },
+        {
+          "label": "Ascending indexed fields",
+          "id": "ascendingFields",
+          "ui": "table-pair",
+          "type": "queryFields",
+          "model": "ascendingFields",
+          "keyName": "name",
+          "valueName": "className",
+          "focusId": "AscField",
+          "addTip": "Add field to index in ascending order",
+          "removeTip": "Remove field",
+          "tip": [
+            "Collection of name-to-type mappings to index in ascending order"
+          ]
+        },
+        {
+          "label": "Descending indexed fields",
+          "id": "descendingFields",
+          "ui": "table-pair",
+          "type": "queryFields",
+          "model": "descendingFields",
+          "keyName": "name",
+          "valueName": "className",
+          "focusId": "DescField",
+          "addTip": "Add field to index in descending order",
+          "removeTip": "Remove field",
+          "tip": [
+            "Collection of name-to-type mappings to index in descending order"
+          ]
+        },
+        {
+          "label": "Text indexed fields",
+          "id": "textFields",
+          "type": "table-simple",
+          "model": "textFields",
+          "placeholder": "Field name",
+          "focusId": "TextField",
+          "addTip": "Add field to index as text",
+          "removeTip": "Remove field",
+          "tableTip": [
+            "Fields to index as text"
+          ],
+          "tip": [
+            "Field to index as text"
+          ]
+        },
+        {
+          "label": "Group indexes",
+          "id": "groups",
+          "type": "table-query-groups",
+          "model": "groups",
+          "addTip": "Add new group",
+          "removeTip": "Remove group",
+          "addItemTip": "Add new field to group",
+          "removeItemTip": "Remove field from group",
+          "tip": [
+            "Collection of group indexes"
+          ]
+        }
+      ]
+    },
+    {
+      "label": "Metadata for cache store",
+      "group": "store",
+      "tip": [
+        "Metadata properties for binding database with cache via POJO cache store"
+      ],
+      "fields": [
+        {
+          "label": "Database schema",
+          "id": "databaseSchema",
+          "type": "text",
+          "model": "databaseSchema",
+          "placeholder": "Input DB schema name",
+          "tip": [
+            "Schema name in database"
+          ]
+        },
+        {
+          "label": "Database table",
+          "id": "databaseTable",
+          "type": "text",
+          "model": "databaseTable",
+          "placeholder": "Input DB table name",
+          "tip": [
+            "Table name in database"
+          ]
+        },
+        {
+          "label": "Key fields",
+          "id": "keyFields",
+          "type": "table-db-fields",
+          "model": "keyFields",
+          "keyName": "name",
+          "valueName": "className",
+          "hide": "isJavaBuildInClass()",
+          "focusId": "KeyField",
+          "addTip": "Add key field",
+          "removeTip": "Remove key field",
+          "tip": [
+            "Collection of key fields descriptions for CacheJdbcPojoStore"
+          ]
+        },
+        {
+          "label": "Value fields",
+          "id": "valueFields",
+          "type": "table-db-fields",
+          "model": "valueFields",
+          "keyName": "name",
+          "valueName": "className",
+          "focusId": "ValueField",
+          "addTip": "Add value field",
+          "removeTip": "Remove value field",
+          "tip": [
+            "Collection of value fields descriptions for CacheJdbcPojoStore"
+          ]
+        }
+      ]
+    }
+  ],
+  "metadataDb": [
+    {
+      "label": "Driver JAR",
+      "id": "jdbcDriverJar",
+      "type": "dropdown",
+      "container": "false",
+      "model": "jdbcDriverJar",
+      "items": "jdbcDriverJars",
+      "tip": [
+        "Select appropriate JAR with JDBC driver",
+        "To add another driver you need to place it into '/drivers' folder of Ignite Web Agent",
+        "Refer to Ignite Web Agent README.txt for for more information"
+      ]
+    },
+    {
+      "label": "JDBC Driver",
+      "id": "jdbcDriverClass",
+      "type": "text",
+      "model": "jdbcDriverClass",
+      "placeholder": "Full class name of JDBC driver",
+      "tip": [
+        "Full class name of JDBC driver that will be used to connect to database"
+      ]
+    },
+    {
+      "label": "JDBC URL",
+      "id": "jdbcUrl",
+      "type": "text",
+      "model": "jdbcUrl",
+      "placeholder": "JDBC URL",
+      "tip": [
+        "JDBC URL for connecting to database.",
+        "Refer to your database documentation for details"
+      ]
+    },
+    {
+      "label": "User",
+      "id": "user",
+      "type": "text",
+      "model": "user",
+      "tip": [
+        "User name for connecting to database"
+      ]
+    },
+    {
+      "label": "Password",
+      "id": "password",
+      "type": "password",
+      "model": "password",
+      "onEnter": "loadMetadataNext()",
+      "tip": [
+        "Password for connecting to database",
+        "Note, password would not be saved"
+      ]
+    },
+    {
+      "label": "Tables only",
+      "id": "tablesOnly",
+      "type": "check",
+      "model": "tablesOnly",
+      "tip": [
+        "If selected then only tables metadata will be parsed",
+        "Otherwise table and view metadata will be parsed"
+      ]
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/models/summary.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/models/summary.json b/modules/control-center-web/src/main/js/controllers/models/summary.json
new file mode 100644
index 0000000..88ad6b4
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/models/summary.json
@@ -0,0 +1,172 @@
+{
+  "screenTip": {
+    "workflowTitle": "On This Screen:",
+    "workflowContent": [
+      "Download XML Config",
+      "Download Java Code",
+      "Download Docker File",
+      "more-info"
+    ],
+    "whatsNextTitle": "Next Steps:",
+    "whatsNextContent": [
+      "Deploy Ignite Servers",
+      "Connect Ignite Clients",
+      "Analyze with SQL"
+    ]
+  },
+  "moreInfo": {
+    "title": "Summary page",
+    "content": ["Generated cluster's configuration showed on this page.",
+      "Configurations available in XML, Java and Dockerfile format for Server and Client mode.",
+      "Database table POJO classes for cluster's metadatas available on \"POJO\" tab.",
+      "Use \"Download\" button to receive configurations in ZIP file.",
+      "Go back to change configuration on <a href='/configuration/clusters'>clusters</a>, <a href='/configuration/caches'>caches</a> or <a href='/configuration/metadata'>metadata</a> pages."
+    ]
+  },
+  "clientFields": [
+    {
+      "label": "Near cache start size",
+      "type": "number",
+      "path": "nearConfiguration",
+      "model": "nearStartSize",
+      "placeholder": 375000,
+      "tip": [
+        "Initial cache size for near cache which will be used to pre-create internal hash table after start"
+      ]
+    },
+    {
+      "label": "Near cache eviction policy",
+      "type": "dropdown-details",
+      "settings": true,
+      "path": "nearConfiguration.nearEvictionPolicy",
+      "model": "kind",
+      "placeholder": "Choose eviction policy",
+      "items": "evictionPolicies",
+      "tip": [
+        "Cache expiration policy"
+      ],
+      "details": {
+        "LRU": {
+          "expanded": false,
+          "fields": [
+            {
+              "label": "Batch size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.LRU",
+              "model": "batchSize",
+              "placeholder": 1,
+              "tip": [
+                "Number of entries to remove on shrink"
+              ]
+            },
+            {
+              "label": "Max memory size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.LRU",
+              "model": "maxMemorySize",
+              "placeholder": 0,
+              "tip": [
+                "Maximum allowed cache size in bytes"
+              ]
+            },
+            {
+              "label": "Max size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.LRU",
+              "model": "maxSize",
+              "placeholder": 100000,
+              "tip": [
+                "Maximum allowed size of cache before entry will start getting evicted"
+              ]
+            }
+          ]
+        },
+        "RND": {
+          "expanded": false,
+          "fields": [
+            {
+              "label": "Max size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.RND",
+              "model": "maxSize",
+              "placeholder": 100000,
+              "tip": [
+                "Maximum allowed size of cache before entry will start getting evicted"
+              ]
+            }
+          ]
+        },
+        "FIFO": {
+          "expanded": false,
+          "fields": [
+            {
+              "label": "Batch size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.FIFO",
+              "model": "batchSize",
+              "placeholder": 1,
+              "tip": [
+                "Number of entries to remove on shrink"
+              ]
+            },
+            {
+              "label": "Max memory size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.FIFO",
+              "model": "maxMemorySize",
+              "placeholder": 0,
+              "tip": [
+                "Maximum allowed cache size in bytes"
+              ]
+            },
+            {
+              "label": "Max size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.FIFO",
+              "model": "maxSize",
+              "placeholder": 100000,
+              "tip": [
+                "Maximum allowed size of cache before entry will start getting evicted"
+              ]
+            }
+          ]
+        },
+        "SORTED": {
+          "expanded": false,
+          "fields": [
+            {
+              "label": "Batch size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.SORTED",
+              "model": "batchSize",
+              "placeholder": 1,
+              "tip": [
+                "Number of entries to remove on shrink"
+              ]
+            },
+            {
+              "label": "Max memory size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.SORTED",
+              "model": "maxMemorySize",
+              "placeholder": 0,
+              "tip": [
+                "Maximum allowed cache size in bytes"
+              ]
+            },
+            {
+              "label": "Max size",
+              "type": "number",
+              "path": "nearConfiguration.nearEvictionPolicy.SORTED",
+              "model": "maxSize",
+              "placeholder": 100000,
+              "tip": [
+                "Maximum allowed size of cache before entry will start getting evicted"
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ]
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/profile-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/profile-controller.js b/modules/control-center-web/src/main/js/controllers/profile-controller.js
new file mode 100644
index 0000000..5d4567b
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/profile-controller.js
@@ -0,0 +1,94 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Controller for Profile screen.
+consoleModule.controller('profileController',
+    ['$scope', '$http', '$common', '$focus', '$confirm', function ($scope, $http, $common, $focus, $confirm) {
+    $scope.profileUser = angular.copy($scope.user);
+
+    if ($scope.profileUser && !$scope.profileUser.token)
+        $scope.profileUser.token = 'No security token. Regenerate please.';
+
+    $scope.generateToken = function () {
+        $confirm.confirm('Are you sure you want to change security token?')
+            .then(function () {
+                $scope.profileUser.token = $commonUtils.randomString(20);
+            })
+    };
+
+    $scope.profileChanged = function () {
+        var old = $scope.user;
+        var cur = $scope.profileUser;
+
+        return old.username != cur.username || old.email != cur.email || old.token != cur.token ||
+            (cur.changePassword && !$common.isEmptyString(cur.newPassword));
+    };
+
+    $scope.profileCouldBeSaved = function () {
+        return $scope.profileForm.$valid && $scope.profileChanged();
+    };
+
+    $scope.saveBtnTipText = function () {
+        if (!$scope.profileForm.$valid)
+            return 'Invalid profile settings';
+
+        return $scope.profileChanged() ? 'Save profile' : 'Nothing to save';
+    };
+
+    $scope.saveUser = function () {
+        var profile = $scope.profileUser;
+
+        if (profile) {
+            var userName = profile.username;
+            var changeUsername = userName != $scope.user.username;
+
+            var email = profile.email;
+            var changeEmail = email != $scope.user.email;
+
+            var token = profile.token;
+            var changeToken = token != $scope.user.token;
+
+            if (changeUsername || changeEmail || changeToken || profile.changePassword) {
+                $http.post('/profile/save', {
+                    _id: profile._id,
+                    userName: changeUsername ? userName : undefined,
+                    email: changeEmail ? email : undefined,
+                    token: changeToken ? token : undefined,
+                    newPassword: profile.changePassword ? profile.newPassword : undefined
+                }).success(function (user) {
+                    $common.showInfo('Profile saved.');
+
+                    profile.changePassword = false;
+                    profile.newPassword = null;
+                    profile.confirmPassword = null;
+
+                    if (changeUsername)
+                        $scope.user.username = userName;
+
+                    if (changeEmail)
+                        $scope.user.email = email;
+
+                    $focus('profile-username');
+                }).error(function (err) {
+                    $common.showError('Failed to save profile: ' + $common.errorMessage(err));
+                });
+            }
+        }
+    };
+}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/sql-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/sql-controller.js b/modules/control-center-web/src/main/js/controllers/sql-controller.js
new file mode 100644
index 0000000..72afcc9
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/sql-controller.js
@@ -0,0 +1,1097 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Controller for SQL notebook screen.
+consoleModule.controller('sqlController',
+    ['$scope', '$window','$controller', '$http', '$timeout', '$common', '$confirm', '$interval', '$popover', '$loading',
+    function ($scope, $window, $controller, $http, $timeout, $common, $confirm, $interval, $popover, $loading) {
+    // Initialize the super class and extend it.
+    angular.extend(this, $controller('agent-download', {$scope: $scope}));
+
+    $scope.agentGoal = 'execute sql statements';
+    $scope.agentTestDriveOption = '--test-drive-sql';
+
+    $scope.joinTip = $common.joinTip;
+
+    $scope.caches = [];
+
+    $scope.pageSizes = [50, 100, 200, 400, 800, 1000];
+
+    $scope.modes = $common.mkOptions(['PARTITIONED', 'REPLICATED', 'LOCAL']);
+
+    $scope.timeUnit = [
+        {value: 1000, label: 'seconds', short: 's'},
+        {value: 60000, label: 'minutes', short: 'm'},
+        {value: 3600000, label: 'hours', short: 'h'}
+    ];
+
+    $scope.exportDropdown = [{ 'text': 'Export all', 'click': 'exportAll(paragraph)'}];
+
+    $scope.treeOptions = {
+        nodeChildren: 'children',
+        dirSelectable: true,
+        injectClasses: {
+            iExpanded: 'fa fa-minus-square-o',
+            iCollapsed: 'fa fa-plus-square-o'
+        }
+    };
+
+    var TIME_LINE = {value: -1, type: 'java.sql.Date', label: 'TIME_LINE'};
+
+    var chartHistory = [];
+
+    var HISTORY_LENGTH = 100;
+
+    $scope.chartRemoveKeyColumn = function (paragraph, index) {
+        paragraph.chartKeyCols.splice(index, 1);
+
+        _chartApplySettings(paragraph, true);
+    };
+
+    $scope.chartRemoveValColumn = function (paragraph, index) {
+        paragraph.chartValCols.splice(index, 1);
+
+        _chartApplySettings(paragraph, true);
+    };
+
+    $scope.chartAcceptKeyColumn = function(paragraph, item) {
+        var accepted = !_.includes(paragraph.chartKeyCols, item);
+
+        if (accepted) {
+            paragraph.chartKeyCols = [item];
+
+            _chartApplySettings(paragraph, true);
+        }
+
+        return false;
+    };
+
+    $scope.chartAcceptValColumn = function(paragraph, item) {
+        var accepted = !_.includes(paragraph.chartValCols, item) && item != TIME_LINE && _numberType(item.type);
+
+        if (accepted) {
+            paragraph.chartValCols.push(item);
+
+            _chartApplySettings(paragraph, true);
+        }
+
+        return false;
+    };
+
+    var _hideColumn = function (col) {
+        return !(col.fieldName === '_KEY') && !(col.fieldName == '_VAL');
+    };
+
+    var _allColumn = function () {
+        return true;
+    };
+
+    var paragraphId = 0;
+
+    function enhanceParagraph(paragraph) {
+        paragraph.nonEmpty = function () {
+            return this.rows && this.rows.length > 0;
+        };
+
+        paragraph.chart = function () {
+            return this.result != 'table' && this.result != 'none';
+        };
+
+        paragraph.queryExecute = function () {
+            return this.queryArgs && this.queryArgs.type == 'QUERY';
+        };
+
+        paragraph.table = function () {
+            return this.result == 'table';
+        };
+
+        paragraph.chartColumnsConfigured = function () {
+            return !$common.isEmptyArray(this.chartKeyCols) && !$common.isEmptyArray(this.chartValCols);
+        };
+
+        paragraph.chartTimeLineEnabled = function () {
+            return !$common.isEmptyArray(this.chartKeyCols) && angular.equals(this.chartKeyCols[0], TIME_LINE);
+        };
+
+        paragraph.timeLineSupported = function () {
+            return this.result != 'pie';
+        };
+
+        Object.defineProperty(paragraph, 'gridOptions', { value: {
+            enableColResize: true,
+            columnDefs: [],
+            rowData: null
+        }});
+    }
+
+    $scope.aceInit = function (paragraph) {
+        return function (editor) {
+            editor.setAutoScrollEditorIntoView(true);
+            editor.$blockScrolling = Infinity;
+
+            var renderer = editor.renderer;
+
+            renderer.setHighlightGutterLine(false);
+            renderer.setShowPrintMargin(false);
+            renderer.setOption('fontFamily', 'monospace');
+            renderer.setOption('fontSize', '14px');
+            renderer.setOption('minLines', '5');
+            renderer.setOption('maxLines', '15');
+
+            editor.setTheme('ace/theme/chrome');
+
+            Object.defineProperty(paragraph, 'ace', { value: editor });
+        }
+    };
+
+    var _setActiveCache = function () {
+        if ($scope.caches.length > 0)
+            _.forEach($scope.notebook.paragraphs, function (paragraph) {
+                if (!paragraph.cacheName || !_.find($scope.caches, {name: paragraph.cacheName}))
+                    paragraph.cacheName = $scope.caches[0].name;
+            });
+    };
+
+    var loadNotebook = function () {
+        $http.post('/notebooks/get', {noteId: $scope.noteId})
+            .success(function (notebook) {
+                $scope.notebook = notebook;
+
+                $scope.notebook_name = notebook.name;
+
+                _.forEach(notebook.paragraphs, function (paragraph) {
+                    paragraph.id = paragraphId++;
+
+                    enhanceParagraph(paragraph);
+                });
+
+                if (!notebook.paragraphs || notebook.paragraphs.length == 0)
+                    $scope.addParagraph();
+
+                _setActiveCache();
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+    };
+
+    loadNotebook();
+
+    var _saveNotebook = function (f) {
+        $http.post('/notebooks/save', $scope.notebook)
+            .success(f || function() {})
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+    };
+
+    $scope.renameNotebook = function (name) {
+        if (!name)
+            return;
+
+        if ($scope.notebook.name != name) {
+            $scope.notebook.name = name;
+
+            _saveNotebook(function () {
+                var idx = _.findIndex($scope.$root.notebooks, function (item) {
+                    return item._id == $scope.notebook._id;
+                });
+
+                if (idx >= 0) {
+                    $scope.$root.notebooks[idx].name = name;
+
+                    $scope.$root.rebuildDropdown();
+                }
+
+                $scope.notebook.edit = false;
+            });
+        }
+        else
+            $scope.notebook.edit = false
+    };
+
+    $scope.removeNotebook = function () {
+        $confirm.confirm('Are you sure you want to remove: "' + $scope.notebook.name + '"?')
+            .then(function () {
+                $http.post('/notebooks/remove', {_id: $scope.notebook._id})
+                    .success(function () {
+                        var idx = _.findIndex($scope.$root.notebooks, function (item) {
+                            return item._id == $scope.notebook._id;
+                        });
+
+                        if (idx >= 0) {
+                            $scope.$root.notebooks.splice(idx, 1);
+
+                            if ($scope.$root.notebooks.length > 0)
+                                $window.location = '/sql/' +
+                                    $scope.$root.notebooks[Math.min(idx,  $scope.$root.notebooks.length - 1)]._id;
+                            else
+                                $window.location = '/configuration/clusters';
+                        }
+                    })
+                    .error(function (errMsg) {
+                        $common.showError(errMsg);
+                    });
+            });
+    };
+
+    $scope.renameParagraph = function (paragraph, newName) {
+        if (!newName)
+            return;
+
+        if (paragraph.name != newName) {
+            paragraph.name = newName;
+
+            _saveNotebook(function () { paragraph.edit = false; });
+        }
+        else
+            paragraph.edit = false
+    };
+
+    $scope.addParagraph = function () {
+        if (!$scope.notebook.paragraphs)
+            $scope.notebook.paragraphs = [];
+
+        var sz = $scope.notebook.paragraphs.length;
+
+        var paragraph = {
+            id: paragraphId++,
+            name: 'Query' + (sz ==0 ? '' : sz),
+            editor: true,
+            query: '',
+            pageSize: $scope.pageSizes[0],
+            result: 'none',
+            rate: {
+                value: 1,
+                unit: 60000,
+                installed: false
+            }
+        };
+
+        enhanceParagraph(paragraph);
+
+        if ($scope.caches && $scope.caches.length > 0)
+            paragraph.cacheName = $scope.caches[0].name;
+
+        $scope.notebook.expandedParagraphs.push($scope.notebook.paragraphs.length);
+
+        $scope.notebook.paragraphs.push(paragraph);
+    };
+
+    $scope.setResult = function (paragraph, new_result) {
+        var changed = paragraph.result != new_result;
+
+        paragraph.result = paragraph.result === new_result ? 'none' : new_result;
+
+        if (changed) {
+            if (paragraph.chart())
+                _chartApplySettings(paragraph, changed);
+            else
+                setTimeout(function () {
+                    paragraph.gridOptions.api.sizeColumnsToFit();
+                });
+        }
+    };
+
+    $scope.resultEq = function(paragraph, result) {
+        return (paragraph.result === result);
+    };
+
+    $scope.removeParagraph = function(paragraph) {
+        $confirm.confirm('Are you sure you want to remove: "' + paragraph.name + '"?')
+            .then(function () {
+                    var paragraph_idx = _.findIndex($scope.notebook.paragraphs, function (item) {
+                        return paragraph == item;
+                    });
+
+                    var panel_idx = _.findIndex($scope.notebook.expandedParagraphs, function (item) {
+                        return paragraph_idx == item;
+                    });
+
+                    if (panel_idx >= 0)
+                        $scope.notebook.expandedParagraphs.splice(panel_idx, 1);
+
+                    $scope.notebook.paragraphs.splice(paragraph_idx, 1);
+            });
+    };
+
+    function getTopology(onSuccess, onException) {
+        $http.post('/agent/topology')
+            .success(function (caches) {
+                onSuccess();
+
+                var oldCaches = $scope.caches;
+
+                $scope.caches = _.sortBy(caches, 'name');
+
+                _.forEach(caches, function (cache) {
+                    var old = _.find(oldCaches, { name: cache.name });
+
+                    if (old && old.metadata)
+                        cache.metadata = old.metadata;
+                });
+
+                _setActiveCache();
+            })
+            .error(function (err, status) {
+                onException(err, status);
+            });
+    }
+
+    $scope.checkNodeConnection(getTopology);
+
+    var _columnFilter = function(paragraph) {
+        return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn;
+    };
+
+    var _notObjectType = function(cls) {
+        return $common.isJavaBuildInClass(cls);
+    };
+
+    var _numberClasses = ['java.math.BigDecimal', 'java.lang.Byte', 'java.lang.Double',
+        'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
+
+    var _numberType = function(cls) {
+        return _.contains(_numberClasses, cls);
+    };
+
+    var _rebuildColumns = function (paragraph) {
+        var columnDefs = [];
+
+        _.forEach(paragraph.meta, function (meta, idx) {
+            if (paragraph.columnFilter(meta)) {
+                if (_notObjectType(meta.fieldTypeName))
+                    paragraph.chartColumns.push({value: idx, type: meta.fieldTypeName, label: meta.fieldName});
+
+                // Index for explain, execute and fieldName for scan.
+                var colValue = 'data[' +  (paragraph.queryArgs.query ? idx : '"' + meta.fieldName + '"') + ']';
+
+                columnDefs.push({
+                    headerName: meta.fieldName,
+                    valueGetter: $common.isJavaBuildInClass(meta.fieldTypeName) ? colValue : 'JSON.stringify(' + colValue + ')'
+                });
+            }
+        });
+
+        paragraph.gridOptions.api.setColumnDefs(columnDefs);
+
+        if (paragraph.chartColumns.length > 0)
+            paragraph.chartColumns.push(TIME_LINE);
+
+        // We could accept onl not object columns for X axis.
+        paragraph.chartKeyCols = _retainColumns(paragraph.chartColumns, paragraph.chartKeyCols, _notObjectType);
+
+        // We could accept only numeric columns for Y axis.
+        paragraph.chartValCols = _retainColumns(paragraph.chartColumns, paragraph.chartValCols, _numberType, paragraph.chartKeyCols[0]);
+    };
+
+    $scope.toggleSystemColumns = function (paragraph) {
+        if (paragraph.disabledSystemColumns)
+            return;
+
+        paragraph.systemColumns = !paragraph.systemColumns;
+
+        paragraph.columnFilter = _columnFilter(paragraph);
+
+        paragraph.chartColumns = [];
+
+        _rebuildColumns(paragraph);
+
+        setTimeout(function () {
+            paragraph.gridOptions.api.sizeColumnsToFit();
+        });
+    };
+
+    function _retainColumns(allCols, curCols, acceptableType, dfltCol) {
+        var retainedCols = [];
+
+        var allColsLen = allCols.length;
+
+        if (allColsLen > 0) {
+            curCols.forEach(function (curCol) {
+                var col = _.find(allCols, {label: curCol.label});
+
+                if (col && acceptableType(col.type))
+                    retainedCols.push(col);
+            });
+
+            if ($common.isEmptyArray(retainedCols))
+                for (idx = 0; idx < allColsLen; idx++) {
+                    var col = allCols[idx];
+
+                    if (acceptableType(col.type) && col != dfltCol) {
+                        retainedCols.push(col);
+
+                        break;
+                    }
+                }
+
+            if ($common.isEmptyArray(retainedCols) && dfltCol && acceptableType(dfltCol.type))
+                retainedCols.push(dfltCol);
+        }
+
+        return retainedCols;
+    }
+
+    var _processQueryResult = function (paragraph, refreshMode) {
+        return function (res) {
+            if (res.meta && !refreshMode) {
+                paragraph.meta = [];
+
+                paragraph.chartColumns = [];
+
+                if (!$common.isDefined(paragraph.chartKeyCols))
+                    paragraph.chartKeyCols = [];
+
+                if (!$common.isDefined(paragraph.chartValCols ))
+                    paragraph.chartValCols = [];
+
+                if (res.meta.length <= 2) {
+                    var _key = _.find(res.meta, {fieldName: '_KEY'});
+                    var _val = _.find(res.meta, {fieldName: '_VAL'});
+
+                    paragraph.disabledSystemColumns = (res.meta.length == 2 && _key && _val) ||
+                        (res.meta.length == 1 && (_key || _val));
+                }
+
+                paragraph.columnFilter = _columnFilter(paragraph);
+
+                paragraph.meta = res.meta;
+
+                _rebuildColumns(paragraph);
+            }
+
+            paragraph.page = 1;
+
+            paragraph.total = 0;
+
+            paragraph.queryId = res.queryId;
+
+            delete paragraph.errMsg;
+
+            // Prepare explain results for display in table.
+            if (paragraph.queryArgs.type == "EXPLAIN" && res.rows) {
+                paragraph.rows = [];
+
+                res.rows.forEach(function (row, i) {
+                    var line = res.rows.length - 1 == i ? row[0] : row[0] + '\n';
+
+                    line.replace(/\"/g, '').split('\n').forEach(function (line) {
+                        paragraph.rows.push([line]);
+                    });
+                });
+            }
+            else
+                paragraph.rows = res.rows;
+
+            paragraph.gridOptions.api.setRowData(paragraph.rows);
+
+            if (!refreshMode)
+                setTimeout(function () { paragraph.gridOptions.api.sizeColumnsToFit(); });
+
+            // Add results to history.
+            chartHistory.push({tm: new Date(), rows: paragraph.rows});
+
+            if (chartHistory.length > HISTORY_LENGTH)
+                chartHistory.shift();
+
+            _showLoading(paragraph, false);
+
+            if (paragraph.result == 'none' || paragraph.queryArgs.type != "QUERY")
+                paragraph.result = 'table';
+            else if (paragraph.chart())
+                _chartApplySettings(paragraph);
+        }
+    };
+
+    var _executeRefresh = function (paragraph) {
+        $http.post('/agent/query', paragraph.queryArgs)
+            .success(_processQueryResult(paragraph, true))
+            .error(function (errMsg) {
+                paragraph.errMsg = errMsg;
+            });
+    };
+
+    var _showLoading = function (paragraph, enable) {
+        if (paragraph.table())
+            paragraph.gridOptions.api.showLoading(enable);
+
+        paragraph.loading = enable;
+    };
+
+    $scope.execute = function (paragraph) {
+        _saveNotebook();
+
+        paragraph.queryArgs = { type: "QUERY", query: paragraph.query, pageSize: paragraph.pageSize, cacheName: paragraph.cacheName };
+
+        _showLoading(paragraph, true);
+
+        $http.post('/agent/query', paragraph.queryArgs)
+            .success(function (res) {
+                _processQueryResult(paragraph)(res);
+
+                _tryStartRefresh(paragraph);
+            })
+            .error(function (errMsg) {
+                paragraph.errMsg = errMsg;
+
+                _showLoading(paragraph, false);
+
+                $scope.stopRefresh(paragraph);
+            });
+
+        paragraph.ace.focus();
+    };
+
+    $scope.explain = function (paragraph) {
+        _saveNotebook();
+
+        _cancelRefresh(paragraph);
+
+        paragraph.queryArgs = { type: "EXPLAIN", query: 'EXPLAIN ' + paragraph.query, pageSize: paragraph.pageSize, cacheName: paragraph.cacheName };
+
+        _showLoading(paragraph, true);
+
+        $http.post('/agent/query', paragraph.queryArgs)
+            .success(_processQueryResult(paragraph))
+            .error(function (errMsg) {
+                paragraph.errMsg = errMsg;
+
+                _showLoading(paragraph, false);
+            });
+
+        paragraph.ace.focus();
+    };
+
+    $scope.scan = function (paragraph) {
+        _saveNotebook();
+
+        _cancelRefresh(paragraph);
+
+        paragraph.queryArgs = { type: "SCAN", pageSize: paragraph.pageSize, cacheName: paragraph.cacheName };
+
+        _showLoading(paragraph, true);
+
+        $http.post('/agent/scan', paragraph.queryArgs)
+            .success(_processQueryResult(paragraph))
+            .error(function (errMsg) {
+                paragraph.errMsg = errMsg;
+
+                _showLoading(paragraph, false);
+            });
+
+        paragraph.ace.focus();
+    };
+
+    $scope.nextPage = function(paragraph) {
+        _showLoading(paragraph, true);
+
+        $http.post('/agent/query/fetch', {queryId: paragraph.queryId, pageSize: paragraph.pageSize, cacheName: paragraph.queryArgs.cacheName})
+            .success(function (res) {
+                paragraph.page++;
+
+                paragraph.total += paragraph.rows.length;
+
+                paragraph.rows = res.rows;
+
+                paragraph.gridOptions.api.setRowData(res.rows);
+
+                _showLoading(paragraph, false);
+
+                if (res.last)
+                    delete paragraph.queryId;
+            })
+            .error(function (errMsg) {
+                paragraph.errMsg = errMsg;
+
+                _showLoading(paragraph, false);
+            });
+    };
+
+    var _export = function(fileName, meta, rows) {
+        var csvContent = '';
+
+        if (meta) {
+            csvContent += meta.map(function (col) {
+                var res = [];
+
+                if (col.schemaName)
+                    res.push(col.schemaName);
+                if (col.typeName)
+                    res.push(col.typeName);
+
+                res.push(col.fieldName);
+
+                return res.join('.');
+            }).join(',') + '\n';
+        }
+
+        rows.forEach(function (row) {
+            if (Array.isArray(row)) {
+                csvContent += row.map(function (elem) {
+                    return elem ? JSON.stringify(elem) : '';
+                }).join(',');
+            }
+            else {
+                var first = true;
+
+                meta.forEach(function (prop) {
+                    if (first)
+                        first = false;
+                    else
+                        csvContent += ',';
+
+                    var elem = row[prop.fieldName];
+
+                    csvContent += elem ? JSON.stringify(elem) : '';
+                });
+            }
+
+            csvContent += '\n';
+        });
+
+        $common.download('application/octet-stream;charset=utf-8', fileName, escape(csvContent));
+    };
+
+    $scope.exportPage = function(paragraph) {
+        _export(paragraph.name + '.csv', paragraph.meta, paragraph.rows);
+    };
+
+    $scope.exportAll = function(paragraph) {
+        $http.post('/agent/query/getAll', {query: paragraph.query, cacheName: paragraph.cacheName})
+            .success(function (item) {
+                _export(paragraph.name + '-all.csv', item.meta, item.rows);
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            });
+    };
+
+    $scope.rateAsString = function (paragraph) {
+        if (paragraph.rate && paragraph.rate.installed) {
+            var idx = _.findIndex($scope.timeUnit, function (unit) {
+                return unit.value == paragraph.rate.unit;
+            });
+
+            if (idx >= 0)
+                return ' ' + paragraph.rate.value + $scope.timeUnit[idx].short;
+
+            paragraph.rate.installed = false;
+        }
+
+        return '';
+    };
+
+    var _cancelRefresh = function (paragraph) {
+        if (paragraph.rate && paragraph.rate.stopTime) {
+            delete paragraph.queryArgs;
+
+            paragraph.rate.installed = false;
+
+            $interval.cancel(paragraph.rate.stopTime);
+
+            delete paragraph.rate.stopTime;
+        }
+    };
+
+    var _tryStopRefresh = function (paragraph) {
+        if (paragraph.rate && paragraph.rate.stopTime) {
+            $interval.cancel(paragraph.rate.stopTime);
+
+            delete paragraph.rate.stopTime;
+        }
+    };
+
+    var _tryStartRefresh = function (paragraph) {
+        _tryStopRefresh(paragraph);
+
+        if (paragraph.rate && paragraph.rate.installed && paragraph.queryArgs) {
+            $scope.chartAcceptKeyColumn(paragraph, TIME_LINE);
+
+            _executeRefresh(paragraph);
+
+            var delay = paragraph.rate.value * paragraph.rate.unit;
+
+            paragraph.rate.stopTime = $interval(_executeRefresh, delay, 0, false, paragraph);
+        }
+    };
+
+    $scope.startRefresh = function (paragraph, value, unit) {
+        paragraph.rate.value = value;
+        paragraph.rate.unit = unit;
+        paragraph.rate.installed = true;
+
+        _tryStartRefresh(paragraph);
+    };
+
+    $scope.stopRefresh = function (paragraph) {
+        paragraph.rate.installed = false;
+
+        _tryStopRefresh(paragraph);
+    };
+
+    function _chartNumber(arr, idx, dflt) {
+        if (arr && arr.length > idx && _.isNumber(arr[idx])) {
+            return arr[idx];
+        }
+
+        return dflt;
+    }
+
+    function _chartLabel(arr, idx, dflt) {
+        if (arr && arr.length > idx && _.isString(arr[idx]))
+            return arr[idx];
+
+        return dflt;
+    }
+
+    function _chartDatum(paragraph) {
+        var datum = [];
+
+        if (paragraph.chartColumnsConfigured()) {
+            paragraph.chartValCols.forEach(function (valCol) {
+                var index = 0;
+                var values = [];
+
+                if (paragraph.chartTimeLineEnabled()) {
+                    if (paragraph.charts && paragraph.charts.length == 1)
+                        datum = paragraph.charts[0].data;
+
+                    var chartData = _.find(datum, {key: valCol.label});
+
+                    if (chartData) {
+                        var history = _.last(chartHistory);
+
+                        chartData.values.push({
+                            x: history.tm,
+                            y: _chartNumber(history.rows[0], valCol.value, index++)
+                        });
+
+                        if (chartData.length > HISTORY_LENGTH)
+                            chartData.shift();
+                    }
+                    else {
+                        values = _.map(chartHistory, function (history) {
+                            return {
+                                x: history.tm,
+                                y: _chartNumber(history.rows[0], valCol.value, index++)
+                            }
+                        });
+
+                        datum.push({key: valCol.label, values: values});
+                    }
+                }
+                else {
+                    values = _.map(paragraph.rows, function (row) {
+                        return {
+                            x: _chartNumber(row, paragraph.chartKeyCols[0].value, index),
+                            xLbl: _chartLabel(row, paragraph.chartKeyCols[0].value, undefined),
+                            y: _chartNumber(row, valCol.value, index++)
+                        }
+                    });
+
+                    datum.push({key: valCol.label, values: values});
+                }
+            });
+        }
+
+        return datum;
+    }
+
+    function _pieChartDatum(paragraph) {
+        var datum = [];
+
+        if (paragraph.chartColumnsConfigured() && !paragraph.chartTimeLineEnabled()) {
+            paragraph.chartValCols.forEach(function (valCol) {
+                var index = 0;
+
+                var values = _.map(paragraph.rows, function (row) {
+                    return {
+                        x: row[paragraph.chartKeyCols[0].value],
+                        y: _chartNumber(row, valCol.value, index++)
+                    }
+                });
+
+                datum.push({key: valCol.label, values: values});
+            });
+        }
+
+        return datum;
+    }
+
+    function _chartApplySettings(paragraph, resetCharts) {
+        if (resetCharts)
+            paragraph.charts = [];
+
+        if (paragraph.chart() && paragraph.nonEmpty()) {
+            switch (paragraph.result) {
+                case 'bar':
+                    _barChart(paragraph);
+                    break;
+
+                case 'pie':
+                    _pieChart(paragraph);
+                    break;
+
+                case 'line':
+                    _lineChart(paragraph);
+                    break;
+
+                case 'area':
+                    _areaChart(paragraph);
+                    break;
+            }
+        }
+    }
+
+    function _colLabel(col) {
+        return col.label;
+    }
+
+    function _chartAxisLabel(cols, dflt) {
+        return $common.isEmptyArray(cols) ? dflt : _.map(cols, _colLabel).join(', ');
+    }
+
+    function _xX(d) {
+        return d.x;
+    }
+
+    function _yY(d) {
+        return d.y;
+    }
+
+    function _xAxisTimeFormat(d) {
+        return d3.time.format('%X')(new Date(d));
+    }
+
+    var _xAxisWithLabelFormat = function(values) {
+        return function (d) {
+            var dx = values[d];
+
+            if (!dx)
+                return d3.format(',.2f')(d);
+
+            var lbl = values[d]['xLbl'];
+
+            return lbl ? lbl : d3.format(',.2f')(d);
+        }
+    };
+
+    function _barChart(paragraph) {
+        var datum = _chartDatum(paragraph);
+
+        if ($common.isEmptyArray(paragraph.charts)) {
+            var options = {
+                chart: {
+                    type: 'multiBarChart',
+                    height: 400,
+                    margin: {left: 70},
+                    duration: 0,
+                    x: _xX,
+                    y: _yY,
+                    xAxis: {
+                        axisLabel: _chartAxisLabel(paragraph.chartKeyCols, 'X'),
+                        tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(datum[0].values),
+                        showMaxMin: false
+                    },
+                    yAxis: {
+                        axisLabel:  _chartAxisLabel(paragraph.chartValCols, 'Y'),
+                        tickFormat: d3.format(',.2f')
+                    },
+                    showControls: true
+                }
+            };
+
+            paragraph.charts = [{options: options, data: datum}];
+
+            $timeout(function () {
+                paragraph.charts[0].api.update();
+            }, 100);
+        }
+        else
+            $timeout(function () {
+                if (paragraph.chartTimeLineEnabled())
+                    paragraph.charts[0].api.update();
+                else
+                    paragraph.charts[0].api.updateWithData(datum);
+            });
+    }
+
+    function _pieChart(paragraph) {
+        var datum = _pieChartDatum(paragraph);
+
+        if (datum.length == 0)
+            datum = [{values: []}];
+
+        paragraph.charts = _.map(datum, function (data) {
+            return {
+                options: {
+                    chart: {
+                        type: 'pieChart',
+                        height: 400,
+                        duration: 0,
+                        x: _xX,
+                        y: _yY,
+                        showLabels: true,
+                        labelThreshold: 0.05,
+                        labelType: 'percent',
+                        donut: true,
+                        donutRatio: 0.35
+                    },
+                    title: {
+                        enable: true,
+                        text: data.key
+                    }
+                },
+                data: data.values
+            }
+        });
+
+        $timeout(function () {
+            paragraph.charts[0].api.update();
+        }, 100);
+    }
+
+    function _lineChart(paragraph) {
+        var datum = _chartDatum(paragraph);
+
+        if ($common.isEmptyArray(paragraph.charts)) {
+            var options = {
+                chart: {
+                    type: 'lineChart',
+                    height: 400,
+                    margin: { left: 70 },
+                    duration: 0,
+                    x: _xX,
+                    y: _yY,
+                    xAxis: {
+                        axisLabel: _chartAxisLabel(paragraph.chartKeyCols, 'X'),
+                        tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(datum[0].values),
+                        showMaxMin: false
+                    },
+                    yAxis: {
+                        axisLabel:  _chartAxisLabel(paragraph.chartValCols, 'Y'),
+                        tickFormat: d3.format(',.2f')
+                    },
+                    useInteractiveGuideline: true
+                }
+            };
+
+            paragraph.charts = [{options: options, data: datum}];
+
+            $timeout(function () {
+                paragraph.charts[0].api.update();
+            }, 100);
+        }
+        else
+            $timeout(function () {
+                if (paragraph.chartTimeLineEnabled())
+                    paragraph.charts[0].api.update();
+                else
+                    paragraph.charts[0].api.updateWithData(datum);
+            });
+    }
+
+    function _areaChart(paragraph) {
+        var datum = _chartDatum(paragraph);
+
+        if ($common.isEmptyArray(paragraph.charts)) {
+            var options = {
+                chart: {
+                    type: 'stackedAreaChart',
+                    height: 400,
+                    margin: {left: 70},
+                    duration: 0,
+                    x: _xX,
+                    y: _yY,
+                    xAxis: {
+                        axisLabel:  _chartAxisLabel(paragraph.chartKeyCols, 'X'),
+                        tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(datum[0].values),
+                        showMaxMin: false
+                    },
+                    yAxis: {
+                        axisLabel:  _chartAxisLabel(paragraph.chartValCols, 'Y'),
+                        tickFormat: d3.format(',.2f')
+                    }
+                }
+            };
+
+            paragraph.charts = [{options: options, data: datum}];
+
+            $timeout(function () {
+                paragraph.charts[0].api.update();
+            }, 100);
+        }
+        else
+            $timeout(function () {
+                if (paragraph.chartTimeLineEnabled())
+                    paragraph.charts[0].api.update();
+                else
+                    paragraph.charts[0].api.updateWithData(datum);
+            });
+    }
+
+    $scope.actionAvailable = function (paragraph, needQuery) {
+        return $scope.caches.length > 0 && paragraph.cacheName && (!needQuery || paragraph.query) && !paragraph.loading;
+    };
+
+    $scope.actionTooltip = function (paragraph, action, needQuery) {
+        if ($scope.actionAvailable(paragraph, needQuery))
+            return;
+
+        if (paragraph.loading)
+            return 'Wating for server response';
+
+        return 'To ' + action + ' query select cache' + (needQuery ? ' and input query' : '');
+    };
+
+    $scope.clickableMetadata = function (node) {
+        return node.type.slice(0, 5) != 'index';
+    };
+
+    $scope.dblclickMetadata = function (paragraph, node) {
+        paragraph.ace.insert(node.name);
+        var position = paragraph.ace.selection.getCursor();
+
+        paragraph.query = paragraph.ace.getValue();
+
+        setTimeout(function () {
+            paragraph.ace.selection.moveCursorToPosition(position);
+
+            paragraph.ace.focus();
+        }, 1);
+    };
+
+    $scope.tryLoadMetadata = function (cache) {
+        if (!cache.metadata) {
+            $loading.start('loadingCacheMetadata');
+
+            $http.post('/agent/cache/metadata', {cacheName: cache.name})
+                .success(function (metadata) {
+                    cache.metadata = _.sortBy(metadata, 'name');
+                })
+                .error(function (errMsg) {
+                    $common.showError(errMsg);
+                })
+                .finally(function() {
+                    $loading.finish('loadingCacheMetadata');
+                });
+        }
+    }
+}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/summary-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/summary-controller.js b/modules/control-center-web/src/main/js/controllers/summary-controller.js
new file mode 100644
index 0000000..a3d2dd1
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/summary-controller.js
@@ -0,0 +1,233 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Controller for Summary screen.
+consoleModule.controller('summaryController', [
+    '$scope', '$http', '$common', '$loading', '$message',
+    function ($scope, $http, $common, $loading, $message) {
+    $scope.joinTip = $common.joinTip;
+    $scope.getModel = $common.getModel;
+
+    $scope.showMoreInfo = $message.message;
+
+    $scope.javaClassItems = [
+        {label: 'snippet', value: 1},
+        {label: 'factory class', value: 2}
+    ];
+
+    $scope.evictionPolicies = [
+        {value: 'LRU', label: 'LRU'},
+        {value: 'RND', label: 'Random'},
+        {value: 'FIFO', label: 'FIFO'},
+        {value: 'SORTED', label: 'Sorted'},
+        {value: undefined, label: 'Not set'}
+    ];
+
+    $scope.tabsServer = { activeTab: 0 };
+    $scope.tabsClient = { activeTab: 0 };
+
+    $scope.pojoClasses = function() {
+        var classes = [];
+
+        _.forEach($generatorJava.metadatas, function(meta) {
+            classes.push(meta.keyType);
+            classes.push(meta.valueType);
+        });
+
+        return classes;
+    };
+
+    $scope.oss = ['debian:8', 'ubuntu:14.10'];
+
+    $scope.configServer = {javaClassServer: 1, os: undefined};
+    $scope.configClient = {};
+
+    $scope.backupItem = {javaClassClient: 1};
+
+    $http.get('/models/summary.json')
+        .success(function (data) {
+            $scope.screenTip = data.screenTip;
+            $scope.moreInfo = data.moreInfo;
+            $scope.clientFields = data.clientFields;
+        })
+        .error(function (errMsg) {
+            $common.showError(errMsg);
+        });
+
+    $scope.clusters = [];
+
+    $scope.aceInit = function (editor) {
+        editor.setReadOnly(true);
+        editor.setOption('highlightActiveLine', false);
+        editor.setAutoScrollEditorIntoView(true);
+        editor.$blockScrolling = Infinity;
+
+        var renderer = editor.renderer;
+
+        renderer.setHighlightGutterLine(false);
+        renderer.setShowPrintMargin(false);
+        renderer.setOption('fontSize', '14px');
+        renderer.setOption('minLines', '3');
+        renderer.setOption('maxLines', '50');
+
+        editor.setTheme('ace/theme/chrome');
+    };
+
+    $scope.generateJavaServer = function () {
+        $scope.javaServer = $generatorJava.cluster($scope.selectedItem, $scope.configServer.javaClassServer === 2);
+    };
+
+    function selectPojoClass(config) {
+        _.forEach($generatorJava.metadatas, function(meta) {
+            if (meta.keyType == config.pojoClass)
+                return config.pojoClassBody = meta.keyClass;
+
+            if (meta.valueType == config.pojoClass)
+                return config.pojoClassBody = meta.valueClass;
+        });
+    }
+
+    function pojoClsListener(config) {
+        return function () {
+            selectPojoClass(config);
+        };
+    }
+
+    $scope.updatePojos = function() {
+        if ($common.isDefined($scope.selectedItem)) {
+            var curServCls = $scope.configServer.pojoClass;
+            var curCliCls = $scope.configClient.pojoClass;
+
+            $generatorJava.pojos($scope.selectedItem.caches, $scope.configServer.useConstructor, $scope.configServer.includeKeyFields);
+
+            function restoreSelected(selected, config, tabs) {
+                if (!$common.isDefined(selected) || _.findIndex($generatorJava.metadatas, function (meta) {
+                        return meta.keyType == selected || meta.valueType == selected;
+                    }) < 0) {
+                    if ($generatorJava.metadatas.length > 0) {
+                        if ($common.isDefined($generatorJava.metadatas[0].keyType))
+                            config.pojoClass = $generatorJava.metadatas[0].keyType;
+                        else
+                            config.pojoClass = $generatorJava.metadatas[0].valueType;
+                    }
+                    else {
+                        config.pojoClass = undefined;
+
+                        if (tabs.activeTab == 2)
+                            tabs.activeTab = 0;
+                    }
+                }
+                else
+                    config.pojoClass = selected;
+
+                selectPojoClass(config);
+            }
+
+            restoreSelected(curServCls, $scope.configServer, $scope.tabsServer);
+            restoreSelected(curCliCls, $scope.configClient, $scope.tabsClient);
+        }
+    };
+
+    $scope.$watch('configServer.javaClassServer', $scope.generateJavaServer, true);
+
+    $scope.$watch('configServer.pojoClass', pojoClsListener($scope.configServer), true);
+    $scope.$watch('configClient.pojoClass', pojoClsListener($scope.configClient), true);
+
+    $scope.$watch('configServer.useConstructor', $scope.updatePojos, true);
+
+    $scope.$watch('configServer.includeKeyFields', $scope.updatePojos, true);
+
+    $scope.generateDockerServer = function() {
+        var os = $scope.configServer.os ? $scope.configServer.os : $scope.oss[0];
+
+        $scope.dockerServer = $generatorDocker.clusterDocker($scope.selectedItem, os);
+    };
+
+    $scope.$watch('configServer.os', $scope.generateDockerServer, true);
+
+    $scope.generateClient = function () {
+        $scope.xmlClient = $generatorXml.cluster($scope.selectedItem, $scope.backupItem.nearConfiguration);
+        $scope.javaClient = $generatorJava.cluster($scope.selectedItem, $scope.backupItem.javaClassClient === 2,
+            $scope.backupItem.nearConfiguration, $scope.configServer.useConstructor);
+    };
+
+    $scope.$watch('backupItem', $scope.generateClient, true);
+
+    $scope.selectItem = function (cluster) {
+        if (!cluster)
+            return;
+
+        $scope.selectedItem = cluster;
+
+        $scope.xmlServer = $generatorXml.cluster(cluster);
+
+        $scope.generateJavaServer();
+
+        $scope.generateDockerServer();
+
+        $scope.generateClient();
+
+        $scope.updatePojos();
+    };
+
+    $scope.pojoAvailable = function() {
+        return $common.isDefined($generatorJava.metadatas) && $generatorJava.metadatas.length > 0;
+    };
+
+    $loading.start('loadingSummaryScreen');
+
+    $http.post('clusters/list')
+        .success(function (data) {
+            $scope.clusters = data.clusters;
+
+            if ($scope.clusters.length > 0) {
+                // Populate clusters with caches.
+                _.forEach($scope.clusters, function (cluster) {
+                    cluster.caches = _.filter(data.caches, function (cache) {
+                        return _.contains(cluster.caches, cache._id);
+                    });
+                });
+
+                var restoredId = sessionStorage.summarySelectedId;
+
+                var selectIdx = 0;
+
+                if (restoredId) {
+                    var idx = _.findIndex($scope.clusters, function (cluster) {
+                        return cluster._id == restoredId;
+                    });
+
+                    if (idx >= 0)
+                        selectIdx = idx;
+                    else
+                        delete sessionStorage.summarySelectedId;
+                }
+
+                $scope.selectItem($scope.clusters[selectIdx]);
+
+                $scope.$watch('selectedItem', function (val) {
+                    if (val)
+                        sessionStorage.summarySelectedId = val._id;
+                }, true);
+            }
+        })
+        .finally(function () {
+            $loading.finish('loadingSummaryScreen');
+        });
+}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/db.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/db.js b/modules/control-center-web/src/main/js/db.js
new file mode 100644
index 0000000..c9c6b39
--- /dev/null
+++ b/modules/control-center-web/src/main/js/db.js
@@ -0,0 +1,431 @@
+/*
+ *
+ *  * 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 config = require('./helpers/configuration-loader.js');
+
+// Mongoose for mongodb.
+var mongoose = require('mongoose'),
+    Schema = mongoose.Schema,
+    ObjectId = mongoose.Schema.Types.ObjectId,
+    passportLocalMongoose = require('passport-local-mongoose');
+
+var deepPopulate = require('mongoose-deep-populate')( mongoose);
+
+// Connect to mongoDB database.
+mongoose.connect(config.get('mongoDB:url'), {server: {poolSize: 4}});
+
+// Define Account schema.
+var AccountSchema = new Schema({
+    username: String,
+    email: String,
+    lastLogin: Date,
+    admin: Boolean,
+    token: String,
+    resetPasswordToken: String
+});
+
+// Install passport plugin.
+AccountSchema.plugin(passportLocalMongoose, {usernameField: 'email', limitAttempts: true, lastLoginField: 'lastLogin',
+    usernameLowerCase: true});
+
+// Configure transformation to JSON.
+AccountSchema.set('toJSON', {
+    transform: function(doc, ret) {
+        return {
+            _id: ret._id,
+            email: ret.email,
+            username: ret.username,
+            admin: ret.admin,
+            token: ret.token,
+            lastLogin: ret.lastLogin
+        };
+    }
+});
+
+// Define Account model.
+exports.Account = mongoose.model('Account', AccountSchema);
+
+// Define Space model.
+exports.Space = mongoose.model('Space', new Schema({
+    name: String,
+    owner: {type: ObjectId, ref: 'Account'},
+    usedBy: [{
+        permission: {type: String, enum: ['VIEW', 'FULL']},
+        account: {type: ObjectId, ref: 'Account'}
+    }]
+}));
+
+// Define Cache type metadata schema.
+var CacheTypeMetadataSchema = new Schema({
+    space: {type: ObjectId, ref: 'Space'},
+    caches: [{type: ObjectId, ref: 'Cache'}],
+    kind: {type: String, enum: ['query', 'store', 'both']},
+    databaseSchema: String,
+    databaseTable: String,
+    keyType: String,
+    valueType: String,
+    keyFields: [{databaseName: String, databaseType: String, javaName: String, javaType: String}],
+    valueFields: [{databaseName: String, databaseType: String, javaName: String, javaType: String}],
+    queryFields: [{name: String, className: String}],
+    ascendingFields: [{name: String, className: String}],
+    descendingFields:  [{name: String, className: String}],
+    textFields: [String],
+    groups: [{name: String, fields: [{name: String, className: String, direction: Boolean}]}]
+});
+
+// Define Cache type metadata model.
+exports.CacheTypeMetadata = mongoose.model('CacheTypeMetadata', CacheTypeMetadataSchema);
+
+// Define Cache schema.
+var CacheSchema = new Schema({
+    space: {type: ObjectId, ref: 'Space'},
+    name: String,
+    clusters: [{type: ObjectId, ref: 'Cluster'}],
+    metadatas: [{type: ObjectId, ref: 'CacheTypeMetadata'}],
+    cacheMode: {type: String, enum: ['PARTITIONED', 'REPLICATED', 'LOCAL']},
+    atomicityMode: {type: String, enum: ['ATOMIC', 'TRANSACTIONAL']},
+
+    backups: Number,
+    memoryMode: {type: String, enum: ['ONHEAP_TIERED', 'OFFHEAP_TIERED', 'OFFHEAP_VALUES']},
+    offHeapMaxMemory: Number,
+    startSize: Number,
+    swapEnabled: Boolean,
+
+    evictionPolicy: {
+        kind: {type: String, enum: ['LRU', 'RND', 'FIFO', 'Sorted']},
+        LRU: {
+            batchSize: Number,
+            maxMemorySize: Number,
+            maxSize: Number
+        },
+        RND: {
+            maxSize: Number
+        },
+        FIFO: {
+            batchSize: Number,
+            maxMemorySize: Number,
+            maxSize: Number
+        },
+        SORTED: {
+            batchSize: Number,
+            maxMemorySize: Number,
+            maxSize: Number
+        }
+    },
+
+    rebalanceMode: {type: String, enum: ['SYNC', 'ASYNC', 'NONE']},
+    rebalanceThreadPoolSize: Number,
+    rebalanceBatchSize: Number,
+    rebalanceOrder: Number,
+    rebalanceDelay: Number,
+    rebalanceTimeout: Number,
+    rebalanceThrottle: Number,
+
+    cacheStoreFactory: {
+        kind: {
+            type: String,
+            enum: ['CacheJdbcPojoStoreFactory', 'CacheJdbcBlobStoreFactory', 'CacheHibernateBlobStoreFactory']
+        },
+        CacheJdbcPojoStoreFactory: {
+            dataSourceBean: String,
+            dialect: {
+                type: String,
+                enum: ['Oracle', 'DB2', 'SQLServer', 'MySQL', 'PosgreSQL', 'H2']
+            }
+        },
+        CacheJdbcBlobStoreFactory: {
+            user: String,
+            dataSourceBean: String,
+            initSchema: Boolean,
+            createTableQuery: String,
+            loadQuery: String,
+            insertQuery: String,
+            updateQuery: String,
+            deleteQuery: String
+        },
+        CacheHibernateBlobStoreFactory: {
+            hibernateProperties: [String]
+        }
+    },
+    loadPreviousValue: Boolean,
+    readThrough: Boolean,
+    writeThrough: Boolean,
+
+    writeBehindEnabled: Boolean,
+    writeBehindBatchSize: Number,
+    writeBehindFlushSize: Number,
+    writeBehindFlushFrequency: Number,
+    writeBehindFlushThreadCount: Number,
+
+    invalidate: Boolean,
+    defaultLockTimeout: Number,
+
+    sqlEscapeAll: Boolean,
+    sqlOnheapRowCacheSize: Number,
+    longQueryWarningTimeout: Number,
+    indexedTypes: [{keyClass: String, valueClass: String}],
+    sqlFunctionClasses: [String],
+    statisticsEnabled: Boolean,
+    managementEnabled: Boolean,
+    readFromBackup: Boolean,
+    copyOnRead: Boolean,
+    maxConcurrentAsyncOperations: Number,
+    nearCacheEnabled: Boolean,
+    nearConfiguration: {
+        nearStartSize: Number,
+        nearEvictionPolicy: {
+            kind: {type: String, enum: ['LRU', 'RND', 'FIFO', 'Sorted']},
+            LRU: {
+                batchSize: Number,
+                maxMemorySize: Number,
+                maxSize: Number
+            },
+            RND: {
+                maxSize: Number
+            },
+            FIFO: {
+                batchSize: Number,
+                maxMemorySize: Number,
+                maxSize: Number
+            },
+            SORTED: {
+                batchSize: Number,
+                maxMemorySize: Number,
+                maxSize: Number
+            }
+        }
+    }
+});
+
+// Install deep populate plugin.
+CacheSchema.plugin(deepPopulate, {
+    whitelist: ['metadatas']
+});
+
+// Define Cache model.
+exports.Cache = mongoose.model('Cache', CacheSchema);
+
+// Define Cluster schema.
+var ClusterSchema = new Schema({
+    space: {type: ObjectId, ref: 'Space'},
+    name: String,
+    discovery: {
+        localAddress: String,
+        localPort: Number,
+        localPortRange: Number,
+        addressResolver: String,
+        socketTimeout: Number,
+        ackTimeout: Number,
+        maxAckTimeout: Number,
+        networkTimeout: Number,
+        joinTimeout: Number,
+        threadPriority: Number,
+        heartbeatFrequency: Number,
+        maxMissedHeartbeats: Number,
+        maxMissedClientHeartbeats: Number,
+        topHistorySize: Number,
+        listener: String,
+        dataExchange: String,
+        metricsProvider: String,
+        reconnectCount: Number,
+        statisticsPrintFrequency: Number,
+        ipFinderCleanFrequency: Number,
+        authenticator: String,
+        forceServerMode: Boolean,
+        clientReconnectDisabled: Boolean,
+        kind: {type: String, enum: ['Vm', 'Multicast', 'S3', 'Cloud', 'GoogleStorage', 'Jdbc', 'SharedFs']},
+        Vm: {
+            addresses: [String]
+        },
+        Multicast: {
+            multicastGroup: String,
+            multicastPort: Number,
+            responseWaitTime: Number,
+            addressRequestAttempts: Number,
+            localAddress: String,
+            addresses: [String]
+        },
+        S3: {
+            bucketName: String
+        },
+        Cloud: {
+            credential: String,
+            credentialPath: String,
+            identity: String,
+            provider: String,
+            regions: [String],
+            zones:  [String]
+        },
+        GoogleStorage: {
+            projectName: String,
+            bucketName: String,
+            serviceAccountP12FilePath: String,
+            serviceAccountId: String,
+            addrReqAttempts: String
+        },
+        Jdbc: {
+            initSchema: Boolean
+        },
+        SharedFs: {
+            path: String
+        }
+    },
+    atomicConfiguration: {
+        backups: Number,
+        cacheMode: {type: String, enum: ['LOCAL', 'REPLICATED', 'PARTITIONED']},
+        atomicSequenceReserveSize: Number
+    },
+    caches: [{type: ObjectId, ref: 'Cache'}],
+    clockSyncSamples: Number,
+    clockSyncFrequency: Number,
+    deploymentMode: {type: String, enum: ['PRIVATE', 'ISOLATED', 'SHARED', 'CONTINUOUS']},
+    discoveryStartupDelay: Number,
+    igfsThreadPoolSize: Number,
+    includeEventTypes: [{
+        type: String, enum: ['EVTS_CHECKPOINT', 'EVTS_DEPLOYMENT', 'EVTS_ERROR', 'EVTS_DISCOVERY',
+            'EVTS_JOB_EXECUTION', 'EVTS_TASK_EXECUTION', 'EVTS_CACHE', 'EVTS_CACHE_REBALANCE', 'EVTS_CACHE_LIFECYCLE',
+            'EVTS_CACHE_QUERY', 'EVTS_SWAPSPACE', 'EVTS_IGFS']
+    }],
+    managementThreadPoolSize: Number,
+    marshaller: {
+        kind: {type: String, enum: ['OptimizedMarshaller', 'JdkMarshaller']},
+        OptimizedMarshaller: {
+            poolSize: Number,
+            requireSerializable: Boolean
+        }
+    },
+    marshalLocalJobs: Boolean,
+    marshallerCacheKeepAliveTime: Number,
+    marshallerCacheThreadPoolSize: Number,
+    metricsExpireTime: Number,
+    metricsHistorySize: Number,
+    metricsLogFrequency: Number,
+    metricsUpdateFrequency: Number,
+    networkTimeout: Number,
+    networkSendRetryDelay: Number,
+    networkSendRetryCount: Number,
+    peerClassLoadingEnabled: Boolean,
+    peerClassLoadingLocalClassPathExclude: [String],
+    peerClassLoadingMissedResourcesCacheSize: Number,
+    peerClassLoadingThreadPoolSize: Number,
+    publicThreadPoolSize: Number,
+    swapSpaceSpi: {
+        kind: {type: String, enum: ['FileSwapSpaceSpi']},
+        FileSwapSpaceSpi: {
+            baseDirectory: String,
+            readStripesNumber: Number,
+            maximumSparsity: Number,
+            maxWriteQueueSize: Number,
+            writeBufferSize: Number
+        }
+    },
+    systemThreadPoolSize: Number,
+    timeServerPortBase: Number,
+    timeServerPortRange: Number,
+    transactionConfiguration: {
+        defaultTxConcurrency: {type: String, enum: ['OPTIMISTIC', 'PESSIMISTIC']},
+        transactionIsolation: {type: String, enum: ['READ_COMMITTED', 'REPEATABLE_READ', 'SERIALIZABLE']},
+        defaultTxTimeout: Number,
+        pessimisticTxLogLinger: Number,
+        pessimisticTxLogSize: Number,
+        txSerializableEnabled: Boolean,
+        txManagerLookupClassName: String
+    },
+    sslEnabled: Boolean,
+    sslContextFactory: {
+        keyAlgorithm: String,
+        keyStoreFilePath: String,
+        keyStoreType: String,
+        protocol: String,
+        trustStoreFilePath: String,
+        trustStoreType: String,
+        trustManagers: [String]
+    }
+});
+
+// Install deep populate plugin.
+ClusterSchema.plugin(deepPopulate, {
+    whitelist: [
+        'caches',
+        'caches.metadatas'
+    ]
+});
+
+// Define Cluster model.
+exports.Cluster = mongoose.model('Cluster', ClusterSchema);
+
+// Define Notebook schema.
+var NotebookSchema = new Schema({
+    space: {type: ObjectId, ref: 'Space'},
+    name: String,
+    expandedParagraphs: [Number],
+    paragraphs: [{
+        name: String,
+        query: String,
+        editor: Boolean,
+        result: {type: String, enum: ['none', 'table', 'bar', 'pie', 'line', 'area']},
+        pageSize: Number,
+        hideSystemColumns: Boolean,
+        cacheName: String,
+        rate: {
+            value: Number,
+            unit: Number
+        }
+    }]
+});
+
+// Define Notebook model.
+exports.Notebook = mongoose.model('Notebook', NotebookSchema);
+
+// Define Database preset schema.
+var DatabasePresetSchema = new Schema({
+    space: {type: ObjectId, ref: 'Space'},
+    jdbcDriverJar: String,
+    jdbcDriverClass: String,
+    jdbcUrl: String,
+    user: String,
+    packageName: String
+});
+
+// Define Database preset model.
+exports.DatabasePreset = mongoose.model('DatabasePreset', DatabasePresetSchema);
+
+exports.upsert = function (model, data, cb) {
+    if (data._id) {
+        var id = data._id;
+
+        delete data._id;
+
+        model.findOneAndUpdate({_id: id}, data, cb);
+    }
+    else
+        new model(data).save(cb);
+};
+
+exports.processed = function(err, res) {
+    if (err) {
+        res.status(500).send(err);
+
+        return false;
+    }
+
+    return true;
+};
+
+exports.mongoose = mongoose;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/helpers/common-utils.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/helpers/common-utils.js b/modules/control-center-web/src/main/js/helpers/common-utils.js
new file mode 100644
index 0000000..598cdf8
--- /dev/null
+++ b/modules/control-center-web/src/main/js/helpers/common-utils.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.
+ *
+ */
+
+// Entry point for common utils.
+$commonUtils = {};
+
+/**
+ * @param v Value to check.
+ * @returns {boolean} 'true' if value defined.
+ */
+$commonUtils.isDefined = function (v) {
+    return !(v === undefined || v === null);
+};
+
+/**
+ * @param v Value to check.
+ * @returns {boolean} 'true' if value defined and not empty string.
+ */
+$commonUtils.isDefinedAndNotEmpty = function (v) {
+    var defined = $commonUtils.isDefined(v);
+
+    if (defined && (typeof(v) == 'string' ||
+        Object.prototype.toString.call(v) === '[object Array]'))
+        defined = v.length > 0;
+
+    return defined;
+};
+
+/**
+ * @param obj Object to check.
+ * @param props Properties names.
+ * @returns {boolean} 'true' if object contains at least one from specified properties.
+ */
+$commonUtils.hasProperty = function (obj, props) {
+    for (var propName in props) {
+        if (props.hasOwnProperty(propName)) {
+            if (obj[propName])
+                return true;
+        }
+    }
+
+    return false;
+};
+
+/**
+ * @param obj Object to check.
+ * @param props Array of properties names.
+ * @returns {boolean} 'true' if
+ */
+$commonUtils.hasAtLeastOneProperty = function (obj, props) {
+    if (obj && props) {
+        return _.findIndex(props, function (prop) {
+                return $commonUtils.isDefined(obj[prop]);
+            }) >= 0;
+    }
+
+    return false;
+};
+
+/**
+ * Convert some name to valid java name.
+ *
+ * @param prefix To append to java name.
+ * @param name to convert.
+ * @returns {string} Valid java name.
+ */
+$commonUtils.toJavaName = function (prefix, name) {
+    var javaName = name ? name.replace(/[^A-Za-z_0-9]+/, '_') : 'dflt';
+
+    return prefix + javaName.charAt(0).toLocaleUpperCase() + javaName.slice(1);
+};
+
+$commonUtils.randomString = function (len) {
+    var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+    var possibleLen = possible.length;
+
+    var res = '';
+
+    for (var i = 0; i < len; i++)
+        res += possible.charAt(Math.floor(Math.random() * possibleLen));
+
+    return res;
+};
+
+// For server side we should export Java code generation entry point.
+if (typeof window === 'undefined') {
+    module.exports = $commonUtils;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/helpers/configuration-loader.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/helpers/configuration-loader.js b/modules/control-center-web/src/main/js/helpers/configuration-loader.js
new file mode 100644
index 0000000..45616aa
--- /dev/null
+++ b/modules/control-center-web/src/main/js/helpers/configuration-loader.js
@@ -0,0 +1,43 @@
+/*
+ *
+ *  * 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 config = require('nconf');
+
+config.file({'file': 'config/default.json'});
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+config.normalizePort = function (val) {
+    var port = parseInt(val, 10);
+
+    if (isNaN(port)) {
+        // named pipe
+        return val;
+    }
+
+    if (port >= 0) {
+        // port number
+        return port;
+    }
+
+    return false;
+};
+
+module.exports = config;


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/sql/notebook-new.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/sql/notebook-new.jade b/modules/control-center-web/src/main/js/views/sql/notebook-new.jade
new file mode 100644
index 0000000..15db4dc
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/sql/notebook-new.jade
@@ -0,0 +1,31 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.modal(tabindex='-1' role='dialog')
+    .modal-dialog
+        .modal-content
+            .modal-header
+                button.close(ng-click='$hide()') &times;
+                h4.modal-title New SQL notebook
+            form.form-horizontal(name='ui.inputForm' novalidate)
+                .modal-body.row
+                    .col-sm-9.login.col-sm-offset-1
+                        label.required.labelFormField Name:&nbsp;
+                        .col-sm-9
+                            input.form-control(id='create-notebook' type='text' ng-model='name' required on-enter='ui.inputForm.$valid && createNewNotebook(name)' auto-focus)
+            .modal-footer
+                button.btn.btn-default(id='copy-btn-cancel' ng-click='$hide()') Cancel
+                button.btn.btn-primary(id='copy-btn-confirm' ng-disabled='ui.inputForm.$invalid' ng-click='createNewNotebook(name)') Create

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/sql/paragraph-rate.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/sql/paragraph-rate.jade b/modules/control-center-web/src/main/js/views/sql/paragraph-rate.jade
new file mode 100644
index 0000000..689fd24
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/sql/paragraph-rate.jade
@@ -0,0 +1,31 @@
+//-
+    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.
+
+.popover.settings(tabindex='-1' style='width: 200px')
+    .arrow
+    h3.popover-title(style='color: black') Refresh rate
+    button.close(id='paragraph-rate-close' ng-click='$hide()') &times;
+    .popover-content
+        form(name='popoverForm')
+            .form-group(style='padding: 5px')
+                .col-sm-4
+                    input.form-control(id='paragraph-rate' ng-init='value = paragraph.rate.value' ng-model='value' type='number' min='1' required auto-focus)
+                .col-sm-8(style='padding-left: 5px')
+                    button.form-control.select-toggle(id='paragraph-unit' ng-init='unit = paragraph.rate.unit' ng-model='unit' required placeholder='Time unit' bs-select bs-options='item.value as item.label for item in timeUnit' data-container='false' tabindex='0')
+            .form-actions(style='margin-top: 30px; padding: 5px')
+                button.btn.btn-primary(id='paragraph-rate-start' ng-disabled='popoverForm.$invalid' ng-click='startRefresh(paragraph, value, unit); $hide()') Start
+                button.btn.btn-primary.btn-default(id='paragraph-rate-stop' ng-click='stopRefresh(paragraph); $hide()' ng-disabled='!paragraph.rate.installed') Stop
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/sql/sql.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/sql/sql.jade b/modules/control-center-web/src/main/js/views/sql/sql.jade
new file mode 100644
index 0000000..de58686
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/sql/sql.jade
@@ -0,0 +1,173 @@
+//-
+    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
+
+append css
+    link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.1/nv.d3.css')
+append scripts
+    script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-sql.js')
+    script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ext-language_tools.js')
+
+    script(src='//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js')
+    script(src='//cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.1/nv.d3.min.js')
+
+    script(src='/sql-controller.js')
+
+mixin btn-toolbar(btn, click, tip)
+    i.btn.btn-default.fa(class=btn ng-click=click bs-tooltip='' data-title=tip data-trigger='hover' data-placement='bottom')
+
+mixin btn-toolbar-data(btn, kind, tip)
+    i.btn.btn-default.fa(class=btn ng-click='setResult(paragraph, "#{kind}")' ng-class='{active: resultEq(paragraph, "#{kind}")}' bs-tooltip='' data-title=tip data-trigger='hover' data-placement='bottom')
+
+block container
+    .row
+        .col-sm-12(ng-init='noteId = "#{noteId}"')
+            .docs-content(ng-controller='sqlController' )
+                div(bs-affix)
+                    .docs-header.notebook-header
+                        h1.col-sm-6(ng-hide='notebook.edit')
+                            label {{notebook.name}}
+                            .btn-group
+                                +btn-toolbar('fa-pencil', 'notebook.edit = true;notebook.editName = notebook.name', 'Rename notebook')
+                                +btn-toolbar('fa-trash', 'removeNotebook()', 'Remove notebook')
+                        h1.col-sm-6(ng-show='notebook.edit')
+                            input.sql-name-input(ng-model='notebook.editName' required on-enter='renameNotebook(notebook.editName)' on-escape='notebook.edit = false;')
+                            i.tipLabel.fa.fa-floppy-o(ng-show='notebook.editName' ng-click='renameNotebook(notebook.editName)' bs-tooltip data-title='Save notebook name' data-trigger='hover')
+                        .pull-right
+                            +btn-toolbar('fa-plus', 'addParagraph()', 'Add new query')
+                .block-callout-parent
+                    table
+                        tbody
+                            tr
+                                td.block-callout-left(width='50%')
+                                    i.fa.fa-check-square.block-callout-header-left
+                                    label.block-callout-header-left With SQL Notebook you can:
+                                    ul
+                                        li Create any number of queries
+                                        li Execute and explain SQL queries
+                                        li Execute scan queries
+                                        li View data in tabular form and as charts
+                                td.block-callout-right(width='50%')
+                                    i.fa.fa-check-square.block-callout-header-right
+                                    label.block-callout-header-right To execute SQL you need:
+                                    ul
+                                        li Start Apache Ignite Cluster with caches
+                                        li Populate caches with data
+                                        li Start Apache Ignite Web Agent
+                                        li Create query, enter some SQL and execute it
+                hr
+                .docs-body.paragraphs
+                    .panel-group(bs-collapse ng-model='notebook.expandedParagraphs' data-allow-multiple='true' data-start-collapsed='false')
+                        .panel.panel-default(ng-repeat='paragraph in notebook.paragraphs')
+                            .panel-heading(bs-collapse-toggle)
+                                div(ng-hide='paragraph.edit')
+                                    a {{paragraph.name}}
+
+                                    .btn-group(ng-hide='notebook.paragraphs.length > 1')
+                                        +btn-toolbar('fa-pencil', 'paragraph.edit = true; paragraph.editName = paragraph.name; $event.stopPropagation();', 'Rename query')
+
+                                    .btn-group(ng-show='notebook.paragraphs.length > 1' ng-click='$event.stopPropagation();')
+                                        +btn-toolbar('fa-pencil', 'paragraph.edit = true; paragraph.editName = paragraph.name;', 'Rename query')
+                                        +btn-toolbar('fa-remove', 'removeParagraph(paragraph)', 'Remove query')
+
+                                    .pull-right
+                                        .btn-group(ng-model='paragraph.result' ng-click='$event.stopPropagation()' style='float: right')
+                                            +btn-toolbar-data('fa-table', 'table', 'Show data in tabular form.')
+                                            +btn-toolbar-data('fa-bar-chart', 'bar', 'Show bar chart.<br/>By default first column - X values, second column - Y values.<br/>In case of one column it will be treated as Y values.')
+                                            +btn-toolbar-data('fa-pie-chart', 'pie', 'Show pie chart.<br/>By default first column - pie labels, second column - pie values.<br/>In case of one column it will be treated as pie values.')
+                                            +btn-toolbar-data('fa-line-chart', 'line', 'Show line chart.<br/>By default first column - X values, second column - Y values.<br/>In case of one column it will be treated as Y values.')
+                                            +btn-toolbar-data('fa-area-chart', 'area', 'Show area chart.<br/>By default first column - X values, second column - Y values.<br/>In case of one column it will be treated as Y values.')
+                                div(ng-show='paragraph.edit')
+                                    input.sql-name-input(ng-model='paragraph.editName' required ng-click='$event.stopPropagation();' on-enter='renameParagraph(paragraph, paragraph.editName)' on-escape='paragraph.edit = false')
+                                    i.tipLabel.fa.fa-floppy-o(ng-show='paragraph.editName' ng-click='renameParagraph(paragraph, paragraph.editName); $event.stopPropagation();' bs-tooltip data-title='Save paragraph name' data-trigger='hover')
+                            .panel-collapse(role='tabpanel' bs-collapse-target)
+                                .col-sm-12(ng-show='paragraph.editor')
+                                    .col-xs-8.col-sm-9(style='border-right: 1px solid #eee')
+                                        .sql-editor(ui-ace='{onLoad: aceInit(paragraph), theme: "chrome", mode: "sql", require: ["ace/ext/language_tools"],' +
+                                            'advanced: {enableSnippets: false, enableBasicAutocompletion: true, enableLiveAutocompletion: true}}'
+                                        ng-model='paragraph.query')
+                                    .col-xs-4.col-sm-3
+                                        div(ng-show='caches.length > 0' style='padding: 5px 10px' st-table='displayedCaches' st-safe-src='caches')
+                                            lable.labelField.labelFormField Caches:
+                                            .input-tip
+                                                input.form-control(type='text' st-search placeholder='Filter caches...')
+                                            table.links
+                                                tbody.scrollable-y(style='max-height: 15em;display:block;' ng-model='paragraph.cacheName' bs-radio-group)
+                                                    tr(ng-repeat='cache in displayedCaches track by cache.name')
+                                                        td(style='width: 100%')
+                                                            input.labelField(type='radio' value='{{cache.name}}')
+                                                            a(bs-popover data-template-url='cache-metadata', data-placement='bottom', data-trigger='click') {{cache.name}}
+                                        .empty-caches(ng-show='displayedCaches.length == 0 && caches.length != 0')
+                                            label Wrong caches filter
+                                        .empty-caches(ng-show='caches.length == 0')
+                                            label No caches
+                                .col-sm-12
+                                    hr(style='margin: 0')
+                                .col-sm-12
+                                    .details-row
+                                        a.btn.btn-primary(ng-disabled='!actionAvailable(paragraph, true)' ng-click='actionAvailable(paragraph, true) ? explain(paragraph) : ""' data-placement='bottom' bs-tooltip data-title='{{actionTooltip(paragraph, "explain", true)}}') Explain
+                                        a.btn.btn-primary(ng-disabled='!actionAvailable(paragraph, true)' ng-click='actionAvailable(paragraph, true) ? execute(paragraph) : ""' data-placement='bottom' bs-tooltip data-title='{{actionTooltip(paragraph, "execute", true)}}') Execute
+                                        a.btn.btn-primary(ng-disabled='!actionAvailable(paragraph, false)' ng-click='actionAvailable(paragraph, false) ? scan(paragraph): ""' data-placement='bottom' bs-tooltip data-title='{{actionTooltip(paragraph, "execute scan", false)}}') Scan
+                                        .pull-right
+                                            labelHide System columns:
+                                            a.btn.btn-default.fa.fa-bars.tipLabel(ng-class='{"btn-info": paragraph.systemColumns}' ng-click='toggleSystemColumns(paragraph)' ng-disabled='paragraph.disabledSystemColumns')
+                                            label.tipLabel Refresh rate:
+                                            button.btn.btn-default.fa.fa-clock-o.tipLabel(ng-class='{"btn-info": paragraph.rate && paragraph.rate.installed}' bs-popover data-template-url='rate' data-placement='left' data-auto-close='1' data-trigger='click') {{rateAsString(paragraph)}}
+                                            label.tipLabel Page size:
+                                            button.select-toggle.fieldButton.btn.btn-default(ng-model='paragraph.pageSize' bs-options='item for item in pageSizes' bs-select)
+                                .col-sm-12(ng-show='paragraph.errMsg')
+                                    hr(style='margin-top: 0; margin-bottom: 10px')
+                                    .sql-error-result(ng-show='paragraph.errMsg') Error: {{paragraph.errMsg}}
+                                .col-sm-12(ng-show='!paragraph.errMsg && paragraph.result != "none"')
+                                    hr(style='margin-top: 0; margin-bottom: 10px')
+                                    .sql-empty-result(ng-show='!paragraph.nonEmpty()') Result set is empty
+                                    div(ng-show='paragraph.table() && paragraph.nonEmpty()')
+                                        .sql-table-total
+                                            label Page #&nbsp;
+                                            b {{paragraph.page}}&nbsp;&nbsp;&nbsp;
+                                            label Results so far:&nbsp;
+                                            b {{paragraph.rows.length + paragraph.total}}
+                                            .pull-right
+                                                .btn-group(ng-disabled='paragraph.loading')
+                                                    button.btn.btn-primary.fieldButton(ng-click='exportPage(paragraph)' bs-tooltip data-title='{{actionTooltip(paragraph, "export", false)}}') Export
+                                                    button.btn.btn-primary(id='export-item-dropdown' data-toggle='dropdown' data-container='body' bs-dropdown='exportDropdown' data-placement='bottom-right')
+                                                        span.caret
+                                        .sql-table.ag-bootstrap(ag-grid='paragraph.gridOptions')
+                                    div(ng-show='paragraph.chart() && paragraph.nonEmpty()')
+                                        div(ng-show='paragraph.queryExecute()')
+                                            div(ng-hide='paragraph.chartColumnsConfigured()')
+                                                .chart-settings-link
+                                                    i.fa.fa-chevron-circle-down
+                                                    a(ng-show='paragraph.chart' ng-click='$event.stopPropagation()' bs-popover data-template-url='chart-settings' data-placement='bottom' data-auto-close='1' data-trigger='click') Chart settings
+                                                .sql-empty-result Can't display chart. Need configure axis using&nbsp
+                                                    b Chart settings
+                                            div(ng-show='paragraph.chartColumnsConfigured()')
+                                                div(ng-show='paragraph.timeLineSupported() || !paragraph.chartTimeLineEnabled()')
+                                                    .chart-settings-link
+                                                        i.fa.fa-chevron-circle-down
+                                                        a(ng-show='paragraph.chart' ng-click='$event.stopPropagation()' bs-popover data-template-url='chart-settings' data-placement='bottom' data-auto-close='1' data-trigger='click') Chart settings
+                                                    div(ng-repeat='chart in paragraph.charts')
+                                                        nvd3(options='chart.options' data='chart.data' api='chart.api')
+                                                .sql-empty-result(ng-show='!paragraph.timeLineSupported() && paragraph.chartTimeLineEnabled()') Pie chart does not support 'TIME_LINE' column for X-axis
+                                        .sql-empty-result(ng-hide='paragraph.queryExecute()')
+                                            | Charts do not support&nbsp
+                                            b Explain
+                                            | &nbspand&nbsp
+                                            b Scan
+                                            | &nbspquery
+                                    div(ng-show='paragraph.queryId && (paragraph.table() || paragraph.chart() && paragraph.queryExecute())')
+                                        hr(style='margin-top: 0; margin-bottom: 5px')
+                                        i.fa.fa-chevron-circle-right(style='float: right;margin-right: 10px;' ng-click='nextPage(paragraph)')
+                                        a(style='float: right; margin-bottom: 5px;margin-right: 5px;' ng-click='nextPage(paragraph)') Next

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/agent-download.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/agent-download.jade b/modules/control-center-web/src/main/js/views/templates/agent-download.jade
new file mode 100644
index 0000000..47b0940
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/agent-download.jade
@@ -0,0 +1,49 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.modal.center(tabindex='-1' role='dialog')
+    .modal-dialog
+        .modal-content
+            #errors-container.modal-header.header
+                button.close(ng-if='!checkConnection' ng-click='$hide()') &times;
+                h4.modal-title Connection to Ignite Web Agent is not established
+            .agent-download
+                p Please download and run&nbsp;
+                    a(href='javascript:void(0)' ng-click='downloadAgent()') ignite-web-agent
+                    | &nbsp;in order to {{::agentGoal}}.
+                p For installation:
+                ul
+                    li Download and unzip&nbsp;
+                        b ignite-web-agent
+                        | .
+                    li For list of options, run&nbsp;
+                        b ignite-web-agent.{sh|bat} --help
+                        | .
+                    li To test drive, run&nbsp;
+                        b ignite-web-agent.{sh|bat} {{::agentTestDriveOption}}
+                        | .
+                    li Refer to&nbsp;
+                        b README.txt
+                        | &nbsp;for more information.
+                .modal-advanced-options
+                    i.fa.fa-chevron-circle-up(ng-show='agentLoad.showToken' ng-click='agentLoad.showToken = ! agentLoad.showToken')
+                    i.fa.fa-chevron-circle-down(ng-show='!agentLoad.showToken' ng-click='agentLoad.showToken = ! agentLoad.showToken')
+                    a(ng-click='agentLoad.showToken = ! agentLoad.showToken') {{agentLoad.showToken ? 'Hide security token...' : 'Show security token...'}}
+                .details-row(ng-show='agentLoad.showToken')
+                    label.labelField Security token: {{user.token}}
+                    i.tipLabel.fa.fa-clipboard(ng-click-copy='{{user.token}}' bs-tooltip='' data-title='Copy security token to clipboard')
+                    i.tipLabel.fa.fa-question-circle(ng-if=lines bs-tooltip='' data-title='The security token is used for authorization of web agent')
+            .modal-footer
+                button.btn.btn-default(ng-if='checkConnection' ng-click='goHome()') Back to Configuration
+                button.btn.btn-primary(ng-click='downloadAgent()') Download zip

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/batch-confirm.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/batch-confirm.jade b/modules/control-center-web/src/main/js/views/templates/batch-confirm.jade
new file mode 100644
index 0000000..a4d7681
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/batch-confirm.jade
@@ -0,0 +1,32 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.modal(tabindex='-1' role='dialog')
+    .modal-dialog
+        .modal-content
+            .modal-header
+                button.close(ng-click='cancel()' aria-hidden='true') &times;
+                h4.modal-title Confirmation
+            .modal-body(ng-show='batchConfirm.content')
+                p(ng-bind-html='batchConfirm.content' style='text-align: center')
+            .modal-footer
+                .checkbox.labelField
+                    label
+                        input(type='checkbox' ng-model='batchConfirm.applyToAll')
+                        | Apply to all
+                button.btn.btn-default(id='batch-confirm-btn-cancel' ng-click='batchConfirm.cancel()') Cancel
+                button.btn.btn-default(id='batch-confirm-btn-cancel' ng-click='batchConfirm.skip()') Skip
+                button.btn.btn-primary(id='batch-confirm-btn-overwrite' ng-click='batchConfirm.overwrite()') Overwrite

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/clone.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/clone.jade b/modules/control-center-web/src/main/js/views/templates/clone.jade
new file mode 100644
index 0000000..be33821
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/clone.jade
@@ -0,0 +1,32 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.modal(tabindex='-1' role='dialog')
+    .modal-dialog
+        .modal-content
+            .modal-header
+                button.close(ng-click='$hide()') &times;
+                h4.modal-title Clone
+            form.form-horizontal(name='ui.inputForm' novalidate)
+                .modal-body.row
+                    .login
+                        .col-sm-3
+                            label.required.labelFormField New name:&nbsp;
+                        .col-sm-9
+                            input.form-control(id='copy-new-name' type='text' ng-model='newName' required auto-focus)
+            .modal-footer
+                button.btn.btn-default(id='copy-btn-cancel' ng-click='$hide()') Cancel
+                button.btn.btn-primary(id='copy-btn-confirm' ng-disabled='ui.inputForm.$invalid' ng-click='ok(newName)') Confirm

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/confirm.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/confirm.jade b/modules/control-center-web/src/main/js/views/templates/confirm.jade
new file mode 100644
index 0000000..b0acc1d
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/confirm.jade
@@ -0,0 +1,27 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.modal(tabindex='-1' role='dialog')
+    .modal-dialog
+        .modal-content
+            .modal-header
+                button.close(ng-click='confirmCancel()' aria-hidden='true') &times;
+                h4.modal-title Confirmation
+            .modal-body(ng-show='content')
+                p(ng-bind-html='content' style='text-align: center;')
+            .modal-footer
+                button.btn.btn-default(id='confirm-btn-cancel' ng-click='confirmCancel()') Cancel
+                button.btn.btn-primary(id='confirm-btn-confirm' ng-click='confirmOk()') Confirm

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/layout.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/layout.jade b/modules/control-center-web/src/main/js/views/templates/layout.jade
new file mode 100644
index 0000000..c6c0e18
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/layout.jade
@@ -0,0 +1,82 @@
+//-
+    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.
+
+doctype html
+html(ng-app='ignite-web-console' ng-init='user = #{JSON.stringify(user)}; becomeUsed = #{becomeUsed}')
+    head
+        title=title
+
+        block css
+            // Font Awesome Icons
+            link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.css')
+
+            // Font
+            link(rel='stylesheet', href='//fonts.googleapis.com/css?family=Roboto+Slab:700:serif|Roboto+Slab:400:serif')
+            link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/angular-motion/0.4.2/angular-motion.min.css')
+            link(rel='stylesheet', href='//cdn.rawgit.com/wix/angular-tree-control/master/css/tree-control.css')
+            link(rel='stylesheet', href='//cdn.rawgit.com/wix/angular-tree-control/master/css/tree-control-attribute.css')
+            link(rel='stylesheet'  href='//rawgithub.com/darthwade/angular-loading/master/angular-loading.css')
+            link(rel='stylesheet'  href='//cdn.rawgit.com/ceolter/ag-grid/master/dist/ag-grid.css')
+            link(rel='stylesheet', href='/stylesheets/style.css')
+
+        block scripts
+            script(src='/common-utils.js')
+
+            script(src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js')
+
+            script(src='//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js')
+
+            script(src='//ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.min.js')
+            script(src='//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular-sanitize.min.js')
+            script(src='//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular-animate.min.js')
+
+            // script(src='//cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.2/angular-strap.js')
+            // script(src='/js/angular-strap-2.3.2.js')
+            // TODO IGNITE-843: Forked Angular Strap with fix for https://github.com/mgcrea/angular-strap/issues/1852
+            script(src='//cdn.rawgit.com/akuznetsov-gridgain/angular-strap/fix-1852/dist/angular-strap.min.js')
+            script(src='//cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.2/angular-strap.tpl.min.js')
+
+            script(src='//cdnjs.cloudflare.com/ajax/libs/angular-smart-table/2.1.3/smart-table.js')
+
+            script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ace.js')
+            script(src='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/theme-chrome.js')
+            script(src='//angular-ui.github.io/ui-ace/dist/ui-ace.min.js')
+
+            script(src='//cdn.rawgit.com/wix/angular-tree-control/master/angular-tree-control.js')
+
+            script(src='//cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js')
+            script(src='//rawgithub.com/darthwade/angular-loading/master/angular-loading.min.js')
+
+            script(src='//cdn.rawgit.com/ceolter/ag-grid/master/dist/ag-grid.js')
+
+            script(src='//cdnjs.cloudflare.com/ajax/libs/angular-drag-and-drop-lists/1.3.0/angular-drag-and-drop-lists.min.js')
+
+            script(src='//cdn.rawgit.com/krispo/angular-nvd3/master/dist/angular-nvd3.min.js')
+
+            script(src='/common-module.js')
+            script(src='/data-structures.js')
+
+    body.theme-line.body-overlap.greedy
+        .wrapper
+            block body
+                include ../includes/header
+
+                block main-container
+                    .container.body-container
+                        .main-content
+                            block container
+
+                include ../includes/footer

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/message.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/message.jade b/modules/control-center-web/src/main/js/views/templates/message.jade
new file mode 100644
index 0000000..0043709
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/message.jade
@@ -0,0 +1,26 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+.modal(tabindex='-1' role='dialog')
+    .modal-dialog
+        .modal-content
+            .modal-header
+                button.close(ng-click='$hide()' aria-hidden='true') &times;
+                h4.modal-title {{title}}
+            .modal-body(ng-show='content')
+                p(ng-bind-html='content' style='text-align: left;')
+            .modal-footer
+                button.btn.btn-primary(id='confirm-btn-confirm' ng-click='$hide()') Ok

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/select.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/select.jade b/modules/control-center-web/src/main/js/views/templates/select.jade
new file mode 100644
index 0000000..3ff8d21
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/select.jade
@@ -0,0 +1,26 @@
+//-
+    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.
+
+ul.select.dropdown-menu(tabindex='-1' ng-show='$isVisible()' role='select')
+    li(ng-if='$showAllNoneButtons || ($isMultiple && $matches.length > 2)')
+        a(id='li-dropdown-select-all' ng-click='$selectAllAtOnce()') {{$allText}} ({{$matches.length}})
+        a(id='li-dropdown-select-none' ng-click='$selectNoneAtOnce()') {{$noneText}}
+        hr(style='margin: 5px 0')
+    li(role='presentation' ng-repeat='match in $matches')
+        hr(ng-if='match.value == undefined' style='margin: 5px 0')
+        a(id='li-dropdown-item-{{$index}}'  role='menuitem' tabindex='-1' ng-class='{active: $isActive($index)}' ng-click='$select($index, $event)')
+            i(class='{{$iconCheckmark}}' ng-if='$isActive($index)' ng-class='{active: $isActive($index)}')
+            span(ng-bind='match.label')

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/templates/validation-error.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/templates/validation-error.jade b/modules/control-center-web/src/main/js/views/templates/validation-error.jade
new file mode 100644
index 0000000..13deb9b
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/templates/validation-error.jade
@@ -0,0 +1,25 @@
+//-
+    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.
+
+.popover.validation-error
+    .arrow
+    .popover-content
+        table
+            tr
+                td
+                    label {{content}}&nbsp&nbsp
+                td
+                    button.close(id='popover-btn-close' ng-click='$hide()') &times;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/test/js/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/test/js/routes/agent.js b/modules/control-center-web/src/test/js/routes/agent.js
new file mode 100644
index 0000000..4b7dfeb
--- /dev/null
+++ b/modules/control-center-web/src/test/js/routes/agent.js
@@ -0,0 +1,96 @@
+/*
+ *
+ *  * 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 request = require('supertest'),
+    should = require('should'),
+    app = require('../../app'),
+    fs = require('fs'),
+    https = require('https'),
+    config = require('../../helpers/configuration-loader.js'),
+    agentManager = require('../../agents/agent-manager');
+
+/**
+ * Create HTTP server.
+ */
+/**
+ * Start agent server.
+ */
+var agentServer = https.createServer({
+    key: fs.readFileSync(config.get('monitor:server:key')),
+    cert: fs.readFileSync(config.get('monitor:server:cert')),
+    passphrase: config.get('monitor:server:keyPassphrase')
+});
+
+agentServer.listen(config.get('monitor:server:port'));
+
+agentManager.createManager(agentServer);
+
+describe('request from agent', function() {
+    var agent = request.agent(app);
+
+    before(function (done) {
+        this.timeout(10000);
+
+        agent
+            .post('/login')
+            .send({email: 'test@test.com', password: 'test'})
+            .expect(302)
+            .end(function (err) {
+                if (err)
+                    throw err;
+
+                setTimeout(done, 5000);
+            });
+    });
+
+    it('should return topology snapshot', function(done){
+        agent
+            .post('/agent/topology')
+            .send({})
+            .end(function(err, nodes) {
+                if (err) {
+                    console.log(err.response.text);
+
+                    throw err;
+                }
+
+                console.log(nodes);
+
+                done();
+            });
+    });
+
+    //it('should query result', function(done){
+    //    agent
+    //        .post('/agent/query')
+    //        .send({
+    //            username: 'nva',
+    //            password: 'nva.141',
+    //            host: 'localhost',
+    //            port: '5432',
+    //            dbName: 'ggmonitor'
+    //        })
+    //        .end(function(err, res) {
+    //            if (err)
+    //                throw err;
+    //
+    //            done();
+    //        });
+    //});
+});


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/metadata-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/metadata-controller.js b/modules/control-center-web/src/main/js/controllers/metadata-controller.js
new file mode 100644
index 0000000..e274e44
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/metadata-controller.js
@@ -0,0 +1,1252 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Controller for Metadata screen.
+consoleModule.controller('metadataController', [
+    '$scope', '$controller', '$filter', '$http', '$modal', '$common', '$timeout', '$focus', '$confirm', '$confirmBatch', '$message', '$clone', '$table', '$preview', '$loading', '$unsavedChangesGuard',
+    function ($scope, $controller, $filter, $http, $modal, $common, $timeout, $focus, $confirm, $confirmBatch, $message, $clone, $table, $preview, $loading, $unsavedChangesGuard) {
+            $unsavedChangesGuard.install($scope);
+
+            // Initialize the super class and extend it.
+            angular.extend(this, $controller('save-remove', {$scope: $scope}));
+
+            // Initialize the super class and extend it.
+            angular.extend(this, $controller('agent-download', {$scope: $scope}));
+
+            $scope.ui = $common.formUI();
+
+            $scope.showMoreInfo = $message.message;
+
+            $scope.agentGoal = 'load metadata from database schema';
+            $scope.agentTestDriveOption = '--test-drive-metadata';
+
+            $scope.joinTip = $common.joinTip;
+            $scope.getModel = $common.getModel;
+            $scope.javaBuildInClasses = $common.javaBuildInClasses;
+            $scope.compactJavaName = $common.compactJavaName;
+            $scope.saveBtnTipText = $common.saveBtnTipText;
+
+            $scope.tableReset = $table.tableReset;
+            $scope.tableNewItem = $table.tableNewItem;
+            $scope.tableNewItemActive = $table.tableNewItemActive;
+            $scope.tableEditing = $table.tableEditing;
+            $scope.tableStartEdit = $table.tableStartEdit;
+            $scope.tableRemove = function (item, field, index) {
+                $table.tableRemove(item, field, index);
+            };
+
+            $scope.tableSimpleSave = $table.tableSimpleSave;
+            $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+            $scope.tableSimpleUp = $table.tableSimpleUp;
+            $scope.tableSimpleDown = $table.tableSimpleDown;
+            $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
+
+            $scope.tablePairStartEdit = $table.tablePairStartEdit;
+            $scope.tablePairSave = $table.tablePairSave;
+            $scope.tablePairSaveVisible = $table.tablePairSaveVisible;
+
+            var INFO_CONNECT_TO_DB = 'Configure connection to database';
+            var INFO_SELECT_SCHEMAS = 'Select schemas to load tables from';
+            var INFO_SELECT_TABLES = 'Select tables to import as cache type metadata';
+
+            var previews = [];
+
+            $scope.previewInit = function (preview) {
+                previews.push(preview);
+
+                $preview.previewInit(preview);
+            };
+
+            $scope.previewChanged = $preview.previewChanged;
+
+            $scope.hidePopover = $common.hidePopover;
+
+            var showPopoverMessage = $common.showPopoverMessage;
+
+            $scope.preview = {
+                general: {xml: '', java: '', allDefaults: true},
+                query: {xml: '', java: '', allDefaults: true},
+                store: {xml: '', java: '', allDefaults: true}
+            };
+
+            var presets = [
+                {
+                    db: 'oracle',
+                    jdbcDriverClass: 'oracle.jdbc.OracleDriver',
+                    jdbcUrl: 'jdbc:oracle:thin:@[host]:[port]:[database]',
+                    user: 'system'
+                },
+                {
+                    db: 'db2',
+                    jdbcDriverClass: 'com.ibm.db2.jcc.DB2Driver',
+                    jdbcUrl: 'jdbc:db2://[host]:[port]/[database]',
+                    user: 'db2admin'
+                },
+                {
+                    db: 'mssql',
+                    jdbcDriverClass: 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
+                    jdbcUrl: 'jdbc:sqlserver://[host]:[port][;databaseName=database]',
+                    user: 'sa'
+                },
+                {
+                    db: 'postgre',
+                    jdbcDriverClass: 'org.postgresql.Driver',
+                    jdbcUrl: 'jdbc:postgresql://[host]:[port]/[database]',
+                    user: 'sa'
+                },
+                {
+                    db: 'mysql',
+                    jdbcDriverClass: 'com.mysql.jdbc.Driver',
+                    jdbcUrl: 'jdbc:mysql://[host]:[port]/[database]',
+                    user: 'root'
+                },
+                {
+                    db: 'h2',
+                    jdbcDriverClass: 'org.h2.Driver',
+                    jdbcUrl: 'jdbc:h2:tcp://[host]/[database]',
+                    user: 'sa'
+                }
+            ];
+
+            $scope.preset = {
+                db: 'unknown',
+                jdbcDriverClass: '',
+                jdbcDriverJar: '',
+                jdbcUrl: 'jdbc:[database]',
+                user: 'sa',
+                tablesOnly: true
+            };
+
+            $scope.ui.showValid = true;
+
+            var jdbcDrivers = [];
+
+            function _findPreset(jdbcDriverJar) {
+                var idx = _.findIndex(jdbcDrivers, function (jdbcDriver) {
+                    return  jdbcDriver.jdbcDriverJar == jdbcDriverJar;
+                });
+
+                if (idx >= 0) {
+                    var jdbcDriverClass = jdbcDrivers[idx].jdbcDriverClass;
+
+                    idx = _.findIndex(presets, function (preset) {
+                        return preset.jdbcDriverClass == jdbcDriverClass;
+                    });
+
+                    if (idx >= 0)
+                        return presets[idx];
+                }
+
+                return {
+                    db: 'unknown',
+                    jdbcDriverClass: '',
+                    jdbcDriverJar: '',
+                    jdbcUrl: 'jdbc:[database]',
+                    user: 'sa'
+                }
+            }
+
+            $scope.$watch('preset.jdbcDriverJar', function (jdbcDriverJar) {
+                if (jdbcDriverJar) {
+                    var newPreset = _findPreset(jdbcDriverJar);
+
+                    $scope.preset.db = newPreset.db;
+                    $scope.preset.jdbcDriverClass = newPreset.jdbcDriverClass;
+                    $scope.preset.jdbcUrl = newPreset.jdbcUrl;
+                    $scope.preset.user = newPreset.user;
+                }
+            }, true);
+
+            $scope.supportedJdbcTypes = $common.mkOptions($common.SUPPORTED_JDBC_TYPES);
+
+            $scope.supportedJavaTypes = $common.mkOptions($common.javaBuildInClasses);
+
+            $scope.sortDirections = [
+                {value: false, label: 'ASC'},
+                {value: true, label: 'DESC'}
+            ];
+
+            $scope.panels = {activePanels: [0, 1]};
+
+            $scope.metadatas = [];
+
+            $scope.isJavaBuildInClass = function () {
+                var item = $scope.backupItem;
+
+                if (item && item.keyType)
+                    return $common.isJavaBuildInClass(item.keyType);
+
+                return false;
+            };
+
+            $scope.selectAllSchemas = function () {
+                var allSelected = $scope.loadMeta.allSchemasSelected;
+
+                _.forEach($scope.loadMeta.displayedSchemas, function (schema) {
+                    schema.use = allSelected;
+                });
+            };
+
+            $scope.selectSchema = function () {
+                $scope.loadMeta.allSchemasSelected = _.every($scope.loadMeta.schemas, 'use', true);
+            };
+
+            $scope.selectAllTables = function () {
+                var allSelected = $scope.loadMeta.allTablesSelected;
+
+                _.forEach($scope.loadMeta.displayedTables, function (table) {
+                    table.use = allSelected;
+                });
+            };
+
+            $scope.selectTable = function () {
+                $scope.loadMeta.allTablesSelected = _.every($scope.loadMeta.tables, 'use', true);
+            };
+
+            // Pre-fetch modal dialogs.
+            var loadMetaModal = $modal({scope: $scope, templateUrl: 'metadata/metadata-load', show: false});
+
+            // Show load metadata modal.
+            $scope.showLoadMetadataModal = function () {
+                $scope.loadMeta = {
+                    action: 'connect',
+                    schemas: [],
+                    allSchemasSelected: false,
+                    tables: [],
+                    allTablesSelected: false,
+                    button: 'Next',
+                    info: 'Configure connection to database'
+                };
+
+                function getDrivers(onSuccess, onException) {
+                    // Get available JDBC drivers via agent.
+                    $http.post('/agent/drivers')
+                        .success(function (drivers) {
+                            onSuccess();
+
+                            if (drivers && drivers.length > 0) {
+                                $scope.jdbcDriverJars = _.map(drivers, function (driver) {
+                                    return {value: driver.jdbcDriverJar, label: driver.jdbcDriverJar};
+                                });
+
+                                jdbcDrivers = drivers;
+
+                                $scope.preset.jdbcDriverJar = drivers[0].jdbcDriverJar;
+
+                                function openLoadMetadataModal() {
+                                    loadMetaModal.$promise.then(function () {
+                                        $scope.loadMeta.action = 'connect';
+                                        $scope.loadMeta.tables = [];
+
+                                        loadMetaModal.show();
+
+                                        $focus('jdbcUrl');
+                                    });
+                                }
+
+                                $common.confirmUnsavedChanges($scope.ui.isDirty(), openLoadMetadataModal);
+                            }
+                            else
+                                $common.showError('JDBC drivers not found!');
+                        })
+                        .error(function (errMsg, status) {
+                            onException(errMsg, status);
+                        });
+                }
+
+                $scope.awaitAgent(getDrivers);
+            };
+
+            function _loadSchemas() {
+                $loading.start('loadingMetadataFromDb');
+
+                $http.post('/agent/schemas', $scope.preset)
+                    .success(function (schemas) {
+                        $scope.loadMeta.schemas = _.map(schemas, function (schema) { return {use: false, name: schema}});
+                        $scope.loadMeta.action = 'schemas';
+                    })
+                    .error(function (errMsg) {
+                        $common.showError(errMsg);
+                    })
+                    .finally(function() {
+                        $scope.loadMeta.info = INFO_SELECT_SCHEMAS;
+
+                        $loading.finish('loadingMetadataFromDb');
+                    });
+            }
+
+            function _loadMetadata() {
+                $loading.start('loadingMetadataFromDb');
+
+                $scope.loadMeta.allTablesSelected = false;
+                $scope.preset.schemas = [];
+
+                _.forEach($scope.loadMeta.schemas, function (schema) {
+                    if (schema.use)
+                        $scope.preset.schemas.push(schema.name);
+                });
+
+                $http.post('/agent/metadata', $scope.preset)
+                    .success(function (tables) {
+                        $scope.loadMeta.tables = tables;
+                        $scope.loadMeta.action = 'tables';
+                        $scope.loadMeta.button = 'Save';
+
+                        _.forEach(tables, function (tbl) {
+                            tbl.use = $common.isDefined(_.find(tbl.cols, function (col) {
+                                return col.key;
+                            }));
+                        })
+                    })
+                    .error(function (errMsg) {
+                        $common.showError(errMsg);
+                    })
+                    .finally(function() {
+                        $scope.loadMeta.info = INFO_SELECT_TABLES;
+
+                        $loading.finish('loadingMetadataFromDb');
+                    });
+            }
+
+            function toJavaClassName(name) {
+                var len = name.length;
+
+                var buf = '';
+
+                var capitalizeNext = true;
+
+                for (var i = 0; i < len; i++) {
+                    var ch = name.charAt(i);
+
+                    if (ch == ' ' ||  ch == '_')
+                        capitalizeNext = true;
+                    else if (capitalizeNext) {
+                        buf += ch.toLocaleUpperCase();
+
+                        capitalizeNext = false;
+                    }
+                    else
+                        buf += ch.toLocaleLowerCase();
+                }
+
+                return buf;
+            }
+
+            function toJavaName(dbName) {
+                var javaName = toJavaClassName(dbName);
+
+                return javaName.charAt(0).toLocaleLowerCase() + javaName.slice(1);
+            }
+
+            $scope.ui.packageName = $scope.user.email.replace('@', '.').split('.').reverse().join('.');
+
+            function _saveBatch(batch) {
+                if (batch && batch.length > 0) {
+                    $loading.start('loadingMetadataFromDb');
+
+                    $http.post('metadata/save/batch', batch)
+                        .success(function (savedBatch) {
+                            var lastItem = undefined;
+                            var newItems = [];
+
+                            _.forEach(savedBatch, function (savedItem) {
+                                var idx = _.findIndex($scope.metadatas, function (meta) {
+                                    return meta._id == savedItem._id;
+                                });
+
+                                if (idx >= 0)
+                                    $scope.metadatas[idx] = savedItem;
+                                else
+                                    newItems.push(savedItem);
+
+                                lastItem = savedItem;
+                            });
+
+                            _.forEach(newItems, function (item) {
+                                $scope.metadatas.push(item);
+                            });
+
+                            if (!lastItem && $scope.metadatas.length > 0)
+                                lastItem = $scope.metadatas[0];
+
+                            $scope.selectItem(lastItem);
+
+                            $common.showInfo('Cache type metadata loaded from database.');
+
+                            $scope.panels.activePanels = [0, 1, 2];
+                        })
+                        .error(function (errMsg) {
+                            $common.showError(errMsg);
+                        })
+                        .finally(function() {
+                            $loading.finish('loadingMetadataFromDb');
+
+                            loadMetaModal.hide();
+                        });
+                }
+                else
+                    loadMetaModal.hide();
+            }
+
+            function _saveMetadata() {
+                if ($common.isEmptyString($scope.ui.packageName))
+                    return $common.showPopoverMessage(undefined, undefined, 'metadataLoadPackage',
+                        'Package should be not empty');
+
+                if (!$common.isValidJavaClass('Package', $scope.ui.packageName, false, 'metadataLoadPackage', true))
+                    return false;
+
+                $scope.preset.space = $scope.spaces[0];
+
+                $http.post('presets/save', $scope.preset)
+                    .error(function (errMsg) {
+                        $common.showError(errMsg);
+                    });
+
+                var batch = [];
+                var tables = [];
+                var dupCnt = 0;
+
+                var containKey = true;
+
+                _.forEach($scope.loadMeta.tables, function (table) {
+                    if (table.use) {
+                        var qryFields = [];
+                        var ascFields = [];
+                        var descFields = [];
+                        var groups = [];
+                        var keyFields = [];
+                        var valFields = [];
+
+                        var tableName = table.tbl;
+
+                        var dup = tables.indexOf(tableName) >= 0;
+
+                        if (dup)
+                            dupCnt++;
+
+                        var valType = $scope.ui.packageName + '.' + toJavaClassName(tableName);
+
+                        function queryField(name, jdbcType) {
+                            return {name: toJavaName(name), className: jdbcType.javaType}
+                        }
+
+                        function dbField(name, jdbcType) {
+                            return {databaseName: name, databaseType: jdbcType.dbName,
+                                javaName: toJavaName(name), javaType: jdbcType.javaType}
+                        }
+
+                        function colType(colName) {
+                            var col = _.find(table.cols, function (col) {
+                                return col.name == colName;
+                            });
+
+                            if (col)
+                                return $common.findJdbcType(col.type).javaType;
+
+                            return 'Unknown';
+                        }
+
+                        var _containKey = false;
+
+                        _.forEach(table.cols, function (col) {
+                            var colName = col.name;
+                            var jdbcType = $common.findJdbcType(col.type);
+
+                            if (!fieldIndexed(colName, table))
+                                qryFields.push(queryField(colName, jdbcType));
+
+                            if (_.includes(table.ascCols, colName))
+                                ascFields.push(queryField(colName, jdbcType));
+
+                            if (_.includes(table.descCols, colName))
+                                descFields.push(queryField(colName, jdbcType));
+
+                            if (col.key) {
+                                keyFields.push(dbField(colName, jdbcType));
+
+                                _containKey = true;
+                            }
+                            else
+                                valFields.push(dbField(colName, jdbcType));
+                        });
+
+                        containKey &= _containKey;
+
+                        var idxs = table.idxs;
+
+                        if (table.idxs) {
+                            var indexes = Object.keys(idxs);
+
+                            _.forEach(indexes, function (indexName) {
+                                var index = idxs[indexName];
+
+                                var fields = Object.keys(index);
+
+                                if (fields.length > 1)
+                                    groups.push(
+                                        {name: indexName, fields: _.map(fields, function (fieldName) {
+                                            return {
+                                                name: fieldName,
+                                                className: colType(fieldName),
+                                                direction: index[fieldName]
+                                            };
+                                        })});
+                            });
+                        }
+
+                        var metaFound = _.find($scope.metadatas, function (meta) {
+                            return meta.valueType == valType;
+                        });
+
+                        var meta = {
+                            confirm: false,
+                            skip: false,
+                            space: $scope.spaces[0],
+                            caches: []
+                        };
+
+                        if ($common.isDefined(metaFound)) {
+                            meta._id = metaFound._id;
+                            meta.caches = metaFound.caches;
+                            meta.confirm = true;
+                        }
+
+                        var dupSfx = (dup ? '_' + dupCnt : '');
+
+                        meta.keyType = valType + 'Key' + dupSfx;
+                        meta.valueType = valType + dupSfx;
+                        meta.databaseSchema = table.schema;
+                        meta.databaseTable = tableName;
+                        meta.queryFields = qryFields;
+                        meta.ascendingFields = ascFields;
+                        meta.descendingFields = descFields;
+                        meta.groups = groups;
+                        meta.keyFields = keyFields;
+                        meta.valueFields = valFields;
+
+                        batch.push(meta);
+                        tables.push(tableName);
+                    }
+                });
+
+                /**
+                 * Check field with specified name is indexed.
+                 *
+                 * @param name Field name.
+                 * @param table Table to check indexed fields.
+                 * @returns {boolean} <code>True</code> when field is indexed of <code>false</code> otherwise.
+                 */
+                function fieldIndexed(name, table) {
+                    // Check if in asc or desc fields.
+                    if (_.includes(table.ascCols, name) || _.includes(table.descCols, name) || !table.idxs)
+                        return true;
+
+                    return _.findKey(table.idxs, function(fields) { return _.includes(Object.keys(fields), name); }) != undefined;
+                }
+
+                /**
+                 * Generate message to show on confirm dialog.
+                 *
+                 * @param meta Object to confirm.
+                 * @returns {string} Generated message.
+                 */
+                function overwriteMessage(meta) {
+                    return '<span>' +
+                        'Metadata with name &quot;' + meta.databaseTable + '&quot; already exist.<br/><br/>' +
+                        'Are you sure you want to overwrite it?' +
+                        '</span>';
+                }
+
+                var itemsToConfirm = _.filter(batch, function (item) { return item.confirm; });
+
+                function checkOverwrite() {
+                    if (itemsToConfirm.length > 0)
+                        $confirmBatch.confirm(overwriteMessage, itemsToConfirm)
+                            .then(function () {
+                                _saveBatch(_.filter(batch, function (item) {
+                                    return !item.skip
+                                }));
+                            }, function () {
+                                $common.showError('Cache type metadata loading interrupted by user.');
+                            });
+                    else
+                        _saveBatch(batch);
+                }
+
+                if (containKey)
+                    checkOverwrite();
+                else
+                    $confirm.confirm('Imported tables contain tables without primary key.<br/>' +
+                        'You should manually configure key type and key fields for these metadata types.')
+                        .then(function () { checkOverwrite(); })
+            }
+
+            $scope.loadMetadataNext = function () {
+                if ($scope.loadMeta.action == 'connect')
+                    _loadSchemas();
+                else if ($scope.loadMeta.action == 'schemas')
+                    _loadMetadata();
+                else if ($scope.loadMeta.action == 'tables' && $scope.nextAvailable())
+                    _saveMetadata();
+            };
+
+            $scope.nextTooltipText = function () {
+                if ($scope.loadMeta.action == 'tables' && !$scope.nextAvailable())
+                    return 'Select tables to continue';
+
+                return undefined;
+            };
+
+            $scope.nextAvailable = function () {
+                return $scope.loadMeta.action != 'tables' || $('#metadataTableData').find(':checked').length > 0;
+            };
+
+            $scope.loadMetadataPrev = function () {
+                if  ($scope.loadMeta.action == 'tables') {
+                    $scope.loadMeta.action = 'schemas';
+                    $scope.loadMeta.button = 'Next';
+                    $scope.loadMeta.info = INFO_SELECT_SCHEMAS;
+                }
+                else if  ($scope.loadMeta.action == 'schemas') {
+                    $scope.loadMeta.action = 'connect';
+                    $scope.loadMeta.info = INFO_CONNECT_TO_DB;
+                }
+            };
+
+            $scope.metadataTitle = function () {
+                var validFilter = $filter('metadatasValidation');
+
+                if ($scope.ui.showValid || validFilter($scope.displayedRows, false, true).length == 0)
+                    return 'Types metadata:';
+
+                return 'Type metadata without key fields:';
+            };
+
+            function selectFirstItem() {
+                if ($scope.metadatas.length > 0)
+                    $scope.selectItem($scope.metadatas[0]);
+            }
+
+            // When landing on the page, get metadatas and show them.
+            $loading.start('loadingMetadataScreen');
+
+            $http.post('metadata/list')
+                .success(function (data) {
+                    $scope.spaces = data.spaces;
+                    $scope.caches = data.caches;
+                    $scope.metadatas = data.metadatas;
+
+                    // Load page descriptor.
+                    $http.get('/models/metadata.json')
+                        .success(function (data) {
+                            $scope.screenTip = data.screenTip;
+                            $scope.moreInfo = data.moreInfo;
+                            $scope.metadata = data.metadata;
+                            $scope.metadataDb = data.metadataDb;
+
+                            $scope.ui.groups = data.metadata;
+
+                            if ($common.getQueryVariable('new'))
+                                $scope.createItem();
+                            else {
+                                var lastSelectedMetadata = angular.fromJson(sessionStorage.lastSelectedMetadata);
+
+                                if (lastSelectedMetadata) {
+                                    var idx = _.findIndex($scope.metadatas, function (metadata) {
+                                        return metadata._id == lastSelectedMetadata;
+                                    });
+
+                                    if (idx >= 0)
+                                        $scope.selectItem($scope.metadatas[idx]);
+                                    else {
+                                        sessionStorage.removeItem('lastSelectedMetadata');
+
+                                        selectFirstItem();
+                                    }
+                                }
+                                else
+                                    selectFirstItem();
+                            }
+
+                            $timeout(function () {
+                                $scope.$apply();
+                            });
+
+                            $scope.$watch('backupItem', function (val) {
+                                if (val) {
+                                    var srcItem = $scope.selectedItem ? $scope.selectedItem : prepareNewItem();
+
+                                    $scope.ui.checkDirty(val, srcItem);
+
+                                    $scope.preview.general.xml = $generatorXml.metadataGeneral(val).asString();
+                                    $scope.preview.general.java = $generatorJava.metadataGeneral(val).asString();
+                                    $scope.preview.general.allDefaults = $common.isEmptyString($scope.preview.general.xml);
+
+                                    $scope.preview.query.xml = $generatorXml.metadataQuery(val).asString();
+                                    $scope.preview.query.java = $generatorJava.metadataQuery(val).asString();
+                                    $scope.preview.query.allDefaults = $common.isEmptyString($scope.preview.query.xml);
+
+                                    $scope.preview.store.xml = $generatorXml.metadataStore(val).asString();
+                                    $scope.preview.store.java = $generatorJava.metadataStore(val).asString();
+                                    $scope.preview.store.allDefaults = $common.isEmptyString($scope.preview.store.xml);
+                                }
+                            }, true);
+                        })
+                        .error(function (errMsg) {
+                            $common.showError(errMsg);
+                        });
+                })
+                .error(function (errMsg) {
+                    $common.showError(errMsg);
+                })
+                .finally(function() {
+                    $scope.ui.ready = true;
+                    $loading.finish('loadingMetadataScreen');
+                });
+
+            $http.post('presets/list')
+                .success(function (data) {
+                    _.forEach(data.presets, function (restoredPreset) {
+                        var preset = _.find(presets, function (dfltPreset) {
+                            return dfltPreset.jdbcDriverClass == restoredPreset.jdbcDriverClass;
+                        });
+
+                        if (preset) {
+                            preset.jdbcUrl = restoredPreset.jdbcUrl;
+                            preset.user = restoredPreset.user;
+                        }
+                    });
+                })
+                .error(function (errMsg) {
+                    $common.showError(errMsg);
+                });
+
+            $scope.selectItem = function (item, backup) {
+                function selectItem() {
+                    $table.tableReset();
+
+                    $scope.selectedItem = angular.copy(item);
+
+                    try {
+                        if (item && item._id)
+                            sessionStorage.lastSelectedMetadata = angular.toJson(item._id);
+                        else
+                            sessionStorage.removeItem('lastSelectedMetadata');
+                    }
+                    catch (error) { }
+
+                    _.forEach(previews, function(preview) {
+                        preview.attractAttention = false;
+                    });
+
+                    if (backup)
+                        $scope.backupItem = backup;
+                    else if (item)
+                        $scope.backupItem = angular.copy(item);
+                    else
+                        $scope.backupItem = undefined;
+                }
+
+                $common.confirmUnsavedChanges($scope.ui.isDirty(), selectItem);
+
+                $scope.ui.formTitle = $common.isDefined($scope.backupItem) && $scope.backupItem._id
+                    ? 'Selected metadata: ' + $scope.backupItem.valueType
+                    : 'New metadata';
+            };
+
+            function prepareNewItem() {
+                return {space: $scope.spaces[0]._id, caches: []};
+            }
+
+            // Add new metadata.
+            $scope.createItem = function () {
+                $table.tableReset();
+
+                $timeout(function () {
+                    $common.ensureActivePanel($scope.panels, 'query');
+                    $common.ensureActivePanel($scope.panels, 'general', 'keyType');
+                });
+
+                $scope.selectItem(undefined, prepareNewItem());
+            };
+
+            // Check metadata logical consistency.
+            function validate(item) {
+                if ($common.isEmptyString(item.keyType))
+                    return showPopoverMessage($scope.panels, 'general', 'keyType', 'Key type should not be empty');
+                else if (!$common.isValidJavaClass('Key type', item.keyType, true, 'keyType'))
+                    return false;
+
+                if ($common.isEmptyString(item.valueType))
+                    return showPopoverMessage($scope.panels, 'general', 'valueType', 'Value type should not be empty');
+                else if (!$common.isValidJavaClass('Value type', item.valueType, false, 'valueType'))
+                    return false;
+
+                var qry = $common.metadataForQueryConfigured(item);
+
+                if (qry) {
+                    var groups = item.groups;
+
+                    if (groups && groups.length > 0) {
+                        for (var i = 0; i < groups.length; i++) {
+                            var group = groups[i];
+                            var fields = group.fields;
+
+                            if ($common.isEmptyArray(fields))
+                                return showPopoverMessage($scope.panels, 'query', 'groups' + i, 'Group fields are not specified');
+
+                            if (fields.length == 1) {
+                                return showPopoverMessage($scope.panels, 'query', 'groups' + i, 'Group has only one field. Consider to use ascending or descending fields.');
+                            }
+                        }
+                    }
+                }
+
+                var str = $common.metadataForStoreConfigured(item);
+
+                if (str) {
+                    if ($common.isEmptyString(item.databaseSchema))
+                        return showPopoverMessage($scope.panels, 'store', 'databaseSchema', 'Database schema should not be empty');
+
+                    if ($common.isEmptyString(item.databaseTable))
+                        return showPopoverMessage($scope.panels, 'store', 'databaseTable', 'Database table should not be empty');
+
+                    if ($common.isEmptyArray(item.keyFields) && !$common.isJavaBuildInClass(item.keyType))
+                        return showPopoverMessage($scope.panels, 'store', 'keyFields-add', 'Key fields are not specified');
+
+                    if ($common.isEmptyArray(item.valueFields))
+                        return showPopoverMessage($scope.panels, 'store', 'valueFields-add', 'Value fields are not specified');
+                }
+                else if (!qry) {
+                    return showPopoverMessage($scope.panels, 'query', 'query-title', 'SQL query metadata should be configured');
+                }
+
+                return true;
+            }
+
+            // Save cache type metadata into database.
+            function save(item) {
+                var qry = $common.metadataForQueryConfigured(item);
+                var str = $common.metadataForStoreConfigured(item);
+
+                item.kind = 'query';
+
+                if (qry && str)
+                    item.kind = 'both';
+                else if (str)
+                    item.kind = 'store';
+
+                $http.post('metadata/save', item)
+                    .success(function (res) {
+                        $scope.ui.markPristine();
+
+                        var savedMeta = res[0];
+
+                        var idx = _.findIndex($scope.metadatas, function (metadata) {
+                            return metadata._id == savedMeta._id;
+                        });
+
+                        if (idx >= 0)
+                            angular.extend($scope.metadatas[idx], savedMeta);
+                        else
+                            $scope.metadatas.push(savedMeta);
+
+                        $scope.selectItem(savedMeta);
+
+                        $common.showInfo('Cache type metadata"' + item.valueType + '" saved.');
+                    })
+                    .error(function (errMsg) {
+                        $common.showError(errMsg);
+                    });
+            }
+
+            // Save cache type metadata.
+            $scope.saveItem = function () {
+                $table.tableReset();
+
+                var item = $scope.backupItem;
+
+                if (validate(item))
+                    save(item);
+            };
+
+            // Save cache type metadata with new name.
+            $scope.cloneItem = function () {
+                $table.tableReset();
+
+                if (validate($scope.backupItem))
+                    $clone.confirm($scope.backupItem.valueType).then(function (newName) {
+                        var item = angular.copy($scope.backupItem);
+
+                        item._id = undefined;
+                        item.valueType = newName;
+
+                        save(item);
+                    });
+            };
+
+            // Remove metadata from db.
+            $scope.removeItem = function () {
+                $table.tableReset();
+
+                var selectedItem = $scope.selectedItem;
+
+                $confirm.confirm('Are you sure you want to remove cache type metadata: "' + selectedItem.valueType + '"?')
+                    .then(function () {
+                            var _id = selectedItem._id;
+
+                            $http.post('metadata/remove', {_id: _id})
+                                .success(function () {
+                                    $common.showInfo('Cache type metadata has been removed: ' + selectedItem.valueType);
+
+                                    var metadatas = $scope.metadatas;
+
+                                    var idx = _.findIndex(metadatas, function (metadata) {
+                                        return metadata._id == _id;
+                                    });
+
+                                    if (idx >= 0) {
+                                        metadatas.splice(idx, 1);
+
+                                        if (metadatas.length > 0)
+                                            $scope.selectItem(metadatas[0]);
+                                        else
+                                            $scope.selectItem(undefined, undefined);
+                                    }
+                                })
+                                .error(function (errMsg) {
+                                    $common.showError(errMsg);
+                                });
+                    });
+            };
+
+            // Remove all metadata from db.
+            $scope.removeAllItems = function () {
+                $table.tableReset();
+
+                $confirm.confirm('Are you sure you want to remove all metadata?')
+                    .then(function () {
+                            $http.post('metadata/remove/all')
+                                .success(function () {
+                                    $common.showInfo('All metadata have been removed');
+
+                                    $scope.metadatas = [];
+
+                                    $scope.selectItem(undefined, undefined);
+                                })
+                                .error(function (errMsg) {
+                                    $common.showError(errMsg);
+                                });
+                    });
+            };
+
+            $scope.tableSimpleValid = function (item, field, name, index) {
+                var model = item[field.model];
+
+                if ($common.isDefined(model)) {
+                    var idx = _.indexOf(model, name);
+
+                    // Found duplicate.
+                    if (idx >= 0 && idx != index)
+                        return showPopoverMessage(null, null, $table.tableFieldId(index, 'TextField'), 'Field with such name already exists!');
+                }
+
+                return true;
+            };
+
+            var pairFields = {
+                queryFields: {msg: 'Query field class', id: 'QryField'},
+                ascendingFields: {msg: 'Ascending field class', id: 'AscField'},
+                descendingFields: {msg: 'Descending field class', id: 'DescField'}
+            };
+
+            $scope.tablePairValid = function (item, field, index) {
+                var pairField = pairFields[field.model];
+
+                var pairValue = $table.tablePairValue(field, index);
+
+                if (pairField) {
+                    if (!$common.isValidJavaClass(pairField.msg, pairValue.value, true, $table.tableFieldId(index, 'Value' + pairField.id)))
+                        return $table.tableFocusInvalidField(index, 'Value' + pairField.id);
+
+                    var model = item[field.model];
+
+                    if ($common.isDefined(model)) {
+                        var idx = _.findIndex(model, function (pair) {
+                            return pair.name == pairValue.key
+                        });
+
+                        // Found duplicate.
+                        if (idx >= 0 && idx != index)
+                            return showPopoverMessage(null, null, $table.tableFieldId(index, 'Key' + pairField.id), 'Field with such name already exists!');
+                    }
+                }
+
+                return true;
+            };
+
+            function tableDbFieldValue(field, index) {
+                return index < 0
+                    ? {databaseName: field.newDatabaseName, databaseType: field.newDatabaseType, javaName: field.newJavaName, javaType: field.newJavaType}
+                    : {databaseName: field.curDatabaseName, databaseType: field.curDatabaseType, javaName: field.curJavaName, javaType: field.curJavaType}
+            }
+
+            $scope.tableDbFieldSaveVisible = function (field, index) {
+                var dbFieldValue = tableDbFieldValue(field, index);
+
+                return !$common.isEmptyString(dbFieldValue.databaseName) && $common.isDefined(dbFieldValue.databaseType) &&
+                    !$common.isEmptyString(dbFieldValue.javaName) && $common.isDefined(dbFieldValue.javaType);
+            };
+
+            var dbFieldTables = {
+                keyFields: {msg: 'Key field', id: 'KeyField'},
+                valueFields: {msg: 'Value field', id: 'ValueField'}
+            };
+
+            $scope.tableDbFieldSave = function (field, index) {
+                var dbFieldTable = dbFieldTables[field.model];
+
+                if (dbFieldTable) {
+                    var dbFieldValue = tableDbFieldValue(field, index);
+
+                    var item = $scope.backupItem;
+
+                    var model = item[field.model];
+
+                    if (!$common.isValidJavaIdentifier(dbFieldTable.msg + ' java name', dbFieldValue.javaName, $table.tableFieldId(index, 'JavaName' + dbFieldTable.id)))
+                        return false;
+
+                    if ($common.isDefined(model)) {
+                        var idx = _.findIndex(model, function (dbMeta) {
+                            return dbMeta.databaseName == dbFieldValue.databaseName;
+                        });
+
+                        // Found duplicate.
+                        if (idx >= 0 && index != idx)
+                            return showPopoverMessage(null, null, $table.tableFieldId(index, 'DatabaseName' + dbFieldTable.id), 'Field with such database name already exists!');
+
+                        idx = _.findIndex(model, function (dbMeta) {
+                            return dbMeta.javaName == dbFieldValue.javaName;
+                        });
+
+                        // Found duplicate.
+                        if (idx >= 0 && index != idx)
+                            return showPopoverMessage(null, null, $table.tableFieldId(index, 'JavaName' + dbFieldTable.id), 'Field with such java name already exists!');
+
+                        if (index < 0) {
+                            model.push(dbFieldValue);
+                        }
+                        else {
+                            var dbField = model[index];
+
+                            dbField.databaseName = dbFieldValue.databaseName;
+                            dbField.databaseType = dbFieldValue.databaseType;
+                            dbField.javaName = dbFieldValue.javaName;
+                            dbField.javaType = dbFieldValue.javaType;
+                        }
+                    }
+                    else {
+                        model = [dbFieldValue];
+
+                        item[field.model] = model;
+                    }
+
+                    if (index < 0)
+                        $table.tableNewItem(field);
+                    else  if (index < model.length - 1)
+                        $table.tableStartEdit(item, field, index + 1);
+                    else
+                        $table.tableNewItem(field);
+                }
+            };
+
+            function tableGroupValue(field, index) {
+                return index < 0 ? field.newGroupName : field.curGroupName;
+            }
+
+            $scope.tableGroupSaveVisible = function (field, index) {
+                return !$common.isEmptyString(tableGroupValue(field, index));
+            };
+
+            $scope.tableGroupSave = function (field, index) {
+                var groupName = tableGroupValue(field, index);
+
+                var groups = $scope.backupItem.groups;
+
+                if ($common.isDefined(groups)) {
+                    var idx = _.findIndex(groups, function (group) {
+                        return group.name == groupName;
+                    });
+
+                    // Found duplicate.
+                    if (idx >= 0 && idx != index)
+                        return showPopoverMessage(null, null, $table.tableFieldId(index, 'GroupName'), 'Group with such name already exists!');
+                }
+
+                var item = $scope.backupItem;
+
+                if (index < 0) {
+                    var newGroup = {name: groupName};
+
+                    if (item.groups)
+                        item.groups.push(newGroup);
+                    else
+                        item.groups = [newGroup];
+                }
+                else
+                    item.groups[index].name = groupName;
+
+                if (index < 0)
+                    $scope.tableGroupNewItem(field, item.groups.length - 1);
+                else {
+                    var group = item.groups[index];
+
+                    if (group.fields || group.fields.length > 0)
+                        $scope.tableGroupItemStartEdit(field, index, 0);
+                    else
+                        $scope.tableGroupNewItem(field, index);
+                }
+            };
+
+            $scope.tableGroupNewItem = function (field, groupIndex) {
+                var groupName = $scope.backupItem.groups[groupIndex].name;
+
+                $table.tableNewItem({ui: 'table-query-group-fields', model: groupName});
+
+                field.newFieldName = null;
+                field.newClassName = null;
+                field.newDirection = false;
+            };
+
+            $scope.tableGroupNewItemActive = function (groupIndex) {
+                var groups = $scope.backupItem.groups;
+
+                if (groups) {
+                    var group = groups[groupIndex];
+
+                    if (group) {
+                        var groupName = group.name;
+
+                        return $table.tableNewItemActive({model: groupName});
+                    }
+                }
+
+                return false;
+            };
+
+            $scope.tableGroupItemEditing = function (groupIndex, index) {
+                var groups = $scope.backupItem.groups;
+
+                if (groups) {
+                    var group = groups[groupIndex];
+
+                    if (group)
+                        return $table.tableEditing({model: group.name}, index);
+                }
+
+                return false;
+            };
+
+            function tableGroupItemValue(field, index) {
+                return index < 0
+                    ? {name: field.newFieldName, className: field.newClassName, direction: field.newDirection}
+                    : {name: field.curFieldName, className: field.curClassName, direction: field.curDirection};
+            }
+
+            $scope.tableGroupItemStartEdit = function (field, groupIndex, index) {
+                var groups = $scope.backupItem.groups;
+
+                var group = groups[groupIndex];
+
+                $table.tableState(group.name, index);
+
+                var groupItem = group.fields[index];
+
+                field.curFieldName = groupItem.name;
+                field.curClassName = groupItem.className;
+                field.curDirection = groupItem.direction;
+
+                $focus('curFieldName');
+            };
+
+            $scope.tableGroupItemSaveVisible = function (field, index) {
+                var groupItemValue = tableGroupItemValue(field, index);
+
+                return !$common.isEmptyString(groupItemValue.name) && !$common.isEmptyString(groupItemValue.className);
+            };
+
+            $scope.tableGroupItemSave = function (field, groupIndex, index) {
+                var groupItemValue = tableGroupItemValue(field, index);
+
+                if (!$common.isValidJavaClass('Group field', groupItemValue.className, true, $table.tableFieldId(index, 'ClassName')))
+                    return $table.tableFocusInvalidField(index, 'ClassName');
+
+                var fields = $scope.backupItem.groups[groupIndex].fields;
+
+                if ($common.isDefined(fields)) {
+                    var idx = _.findIndex(fields, function (field) {
+                        return field.name == groupItemValue.name;
+                    });
+
+                    // Found duplicate.
+                    if (idx >= 0 && idx != index)
+                        return showPopoverMessage(null, null, $table.tableFieldId(index, 'FieldName'), 'Field with such name already exists in group!');
+                }
+
+                var group = $scope.backupItem.groups[groupIndex];
+
+                if (index < 0) {
+                    if (group.fields)
+                        group.fields.push(groupItemValue);
+                    else
+                        group.fields = [groupItemValue];
+
+                    $scope.tableGroupNewItem(field, groupIndex);
+                }
+                else {
+                    var groupItem = group.fields[index];
+
+                    groupItem.name = groupItemValue.name;
+                    groupItem.className = groupItemValue.className;
+                    groupItem.direction = groupItemValue.direction;
+
+                    if (index < group.fields.length - 1)
+                        $scope.tableGroupItemStartEdit(field, groupIndex, index + 1);
+                    else
+                        $scope.tableGroupNewItem(field, groupIndex);
+                }
+            };
+
+            $scope.tableRemoveGroupItem = function (group, index) {
+                $table.tableReset();
+
+                group.fields.splice(index, 1);
+            };
+
+            $scope.resetItem = function (group) {
+                var resetTo = $scope.selectedItem;
+
+                if (!$common.isDefined(resetTo))
+                    resetTo = prepareNewItem();
+
+                $common.resetItem($scope.backupItem, resetTo, $scope.metadata, group);
+            }
+        }]
+);


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

Posted by an...@apache.org.
IGNITE-843 Web console initial commit.


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

Branch: refs/heads/ignite-843-rc1
Commit: bce0deb71f5a70868725ea696644f9b1f96cf2b1
Parents: 81962c4
Author: Andrey <an...@gridgain.com>
Authored: Tue Oct 13 10:06:27 2015 +0700
Committer: Andrey <an...@gridgain.com>
Committed: Tue Oct 13 10:06:27 2015 +0700

----------------------------------------------------------------------
 .../control-center-web/licenses/apache-2.0.txt  |  202 ++
 modules/control-center-web/pom.xml              |   71 +
 .../control-center-web/src/main/js/.gitignore   |    4 +
 .../control-center-web/src/main/js/DEVNOTES.txt |   21 +
 .../src/main/js/agents/agent-manager.js         |  312 +++
 .../src/main/js/agents/agent-server.js          |   98 +
 modules/control-center-web/src/main/js/app.js   |  190 ++
 modules/control-center-web/src/main/js/bin/www  |  126 ++
 .../src/main/js/config/default.json             |   25 +
 .../src/main/js/controllers/admin-controller.js |   81 +
 .../main/js/controllers/caches-controller.js    |  594 ++++++
 .../main/js/controllers/clusters-controller.js  |  560 +++++
 .../src/main/js/controllers/common-module.js    | 2017 ++++++++++++++++++
 .../main/js/controllers/metadata-controller.js  | 1252 +++++++++++
 .../src/main/js/controllers/models/caches.json  | 1027 +++++++++
 .../main/js/controllers/models/clusters.json    | 1333 ++++++++++++
 .../main/js/controllers/models/metadata.json    |  279 +++
 .../src/main/js/controllers/models/summary.json |  172 ++
 .../main/js/controllers/profile-controller.js   |   94 +
 .../src/main/js/controllers/sql-controller.js   | 1097 ++++++++++
 .../main/js/controllers/summary-controller.js   |  233 ++
 modules/control-center-web/src/main/js/db.js    |  431 ++++
 .../src/main/js/helpers/common-utils.js         |  104 +
 .../src/main/js/helpers/configuration-loader.js |   43 +
 .../src/main/js/helpers/data-structures.js      |  113 +
 .../src/main/js/keys/test.crt                   |   13 +
 .../src/main/js/keys/test.key                   |   18 +
 .../control-center-web/src/main/js/package.json |   53 +
 .../public/stylesheets/_bootstrap-custom.scss   |   67 +
 .../stylesheets/_bootstrap-variables.scss       |  890 ++++++++
 .../src/main/js/public/stylesheets/style.scss   | 1838 ++++++++++++++++
 .../src/main/js/routes/admin.js                 |  127 ++
 .../src/main/js/routes/agent.js                 |  261 +++
 .../src/main/js/routes/caches.js                |  171 ++
 .../src/main/js/routes/clusters.js              |  145 ++
 .../js/routes/generator/generator-common.js     |  353 +++
 .../js/routes/generator/generator-docker.js     |   60 +
 .../main/js/routes/generator/generator-java.js  | 1581 ++++++++++++++
 .../js/routes/generator/generator-properties.js |  112 +
 .../main/js/routes/generator/generator-xml.js   | 1202 +++++++++++
 .../src/main/js/routes/metadata.js              |  192 ++
 .../src/main/js/routes/notebooks.js             |  157 ++
 .../src/main/js/routes/presets.js               |   70 +
 .../src/main/js/routes/profile.js               |  105 +
 .../src/main/js/routes/public.js                |  266 +++
 .../src/main/js/routes/sql.js                   |   39 +
 .../src/main/js/routes/summary.js               |  104 +
 .../src/main/js/views/configuration/caches.jade |   46 +
 .../main/js/views/configuration/clusters.jade   |   46 +
 .../js/views/configuration/metadata-load.jade   |   89 +
 .../main/js/views/configuration/metadata.jade   |   65 +
 .../main/js/views/configuration/sidebar.jade    |   48 +
 .../js/views/configuration/summary-tabs.jade    |   24 +
 .../main/js/views/configuration/summary.jade    |  124 ++
 .../src/main/js/views/error.jade                |   22 +
 .../src/main/js/views/includes/controls.jade    |  515 +++++
 .../src/main/js/views/includes/footer.jade      |   22 +
 .../src/main/js/views/includes/header.jade      |   40 +
 .../src/main/js/views/index.jade                |  141 ++
 .../src/main/js/views/reset.jade                |   38 +
 .../src/main/js/views/settings/admin.jade       |   57 +
 .../src/main/js/views/settings/profile.jade     |   68 +
 .../src/main/js/views/sql/cache-metadata.jade   |   26 +
 .../src/main/js/views/sql/chart-settings.jade   |   38 +
 .../src/main/js/views/sql/notebook-new.jade     |   31 +
 .../src/main/js/views/sql/paragraph-rate.jade   |   31 +
 .../src/main/js/views/sql/sql.jade              |  173 ++
 .../main/js/views/templates/agent-download.jade |   49 +
 .../main/js/views/templates/batch-confirm.jade  |   32 +
 .../src/main/js/views/templates/clone.jade      |   32 +
 .../src/main/js/views/templates/confirm.jade    |   27 +
 .../src/main/js/views/templates/layout.jade     |   82 +
 .../src/main/js/views/templates/message.jade    |   26 +
 .../src/main/js/views/templates/select.jade     |   26 +
 .../js/views/templates/validation-error.jade    |   25 +
 .../src/test/js/routes/agent.js                 |   96 +
 76 files changed, 20342 insertions(+)
----------------------------------------------------------------------


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

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/pom.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-web/pom.xml b/modules/control-center-web/pom.xml
new file mode 100644
index 0000000..fcd9b91
--- /dev/null
+++ b/modules/control-center-web/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ /*
+  ~  * 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.
+  ~  */
+  -->
+
+<!--
+    POM file.
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent</relativePath>
+    </parent>
+
+    <artifactId>ignite-control-center-web</artifactId>
+    <version>1.5.0-SNAPSHOT</version>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.github.eirslett</groupId>
+                <artifactId>frontend-maven-plugin</artifactId>
+                <version>0.0.23</version>
+
+                <configuration>
+                    <workingDirectory>src/main/js</workingDirectory>
+                </configuration>
+
+                <executions>
+                    <execution>
+                        <id>install node and npm</id>
+                        <goals>
+                            <goal>install-node-and-npm</goal>
+                        </goals>
+                        <configuration>
+                            <nodeVersion>v0.12.7</nodeVersion>
+                            <npmVersion>2.13.2</npmVersion>
+                        </configuration>
+                    </execution>
+
+                    <execution>
+                        <id>npm install</id>
+                        <goals>
+                            <goal>npm</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/.gitignore
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/.gitignore b/modules/control-center-web/src/main/js/.gitignore
new file mode 100644
index 0000000..96db808
--- /dev/null
+++ b/modules/control-center-web/src/main/js/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+*.idea
+*.log
+*.css

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/agents/agent-manager.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/agents/agent-manager.js b/modules/control-center-web/src/main/js/agents/agent-manager.js
new file mode 100644
index 0000000..582cb11
--- /dev/null
+++ b/modules/control-center-web/src/main/js/agents/agent-manager.js
@@ -0,0 +1,312 @@
+/*
+ *
+ *  * 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 WebSocketServer = require('ws').Server;
+
+var apacheIgnite = require('apache-ignite');
+
+var db = require('../db');
+
+var AgentServer = require('./agent-server').AgentServer;
+
+/**
+ * @constructor
+ */
+function AgentManager(srv) {
+    this._clients = {};
+
+    this._server = srv;
+
+    this._wss = new WebSocketServer({ server: this._server });
+
+    var self = this;
+
+    this._wss.on('connection', function(ws) {
+        new Client(ws, self);
+    });
+}
+
+/**
+ * @param userId
+ * @param {Client} client
+ */
+AgentManager.prototype._removeClient = function(userId, client) {
+    var connections = this._clients[userId];
+
+    if (connections) {
+        removeFromArray(connections, client);
+
+        if (connections.length == 0)
+            delete this._clients[userId];
+    }
+};
+
+/**
+ * @param userId
+ * @param {Client} client
+ */
+AgentManager.prototype._addClient = function(userId, client) {
+    var existingConnections = this._clients[userId];
+
+    if (!existingConnections) {
+        existingConnections = [];
+
+        this._clients[userId] = existingConnections;
+    }
+
+    existingConnections.push(client);
+};
+
+/**
+ * @param userId
+ * @return {Client}
+ */
+AgentManager.prototype.findClient = function(userId) {
+    var clientsList = this._clients[userId];
+
+    if (!clientsList || clientsList.length == 0)
+        return null;
+
+    return clientsList[0];
+};
+
+/**
+ * @constructor
+ * @param {AgentManager} manager
+ * @param {WebSocket} ws
+ */
+function Client(ws, manager) {
+    var self = this;
+
+    this._manager = manager;
+    this._ws = ws;
+
+    ws.on('close', function() {
+        if (self._user) {
+            self._manager._removeClient(self._user._id, self);
+        }
+    });
+
+    ws.on('message', function (msgStr) {
+        var msg = JSON.parse(msgStr);
+
+        self['_rmt' + msg.type](msg);
+    });
+
+    this._reqCounter = 0;
+
+    this._cbMap = {};
+}
+
+/**
+ * @param {String} path
+ * @param {Object} params
+ * @param {String} [method]
+ * @param {Object} [headers]
+ * @param {String} [body]
+ * @param {Function} [cb] Callback. Take 3 arguments: {String} error, {number} httpCode, {string} response.
+ */
+Client.prototype.executeRest = function(path, params, method, headers, body, cb) {
+    if (typeof(params) != 'object')
+        throw '"params" argument must be an object';
+
+    if (typeof(cb) != 'function')
+        throw 'callback must be a function';
+
+    if (body && typeof(body) != 'string')
+        throw 'body must be a string';
+
+    if (headers && typeof(headers) != 'object')
+        throw 'headers must be an object';
+
+    if (!method)
+        method = 'GET';
+    else
+        method = method.toUpperCase();
+
+    if (method != 'GET' && method != 'POST')
+        throw 'Unknown HTTP method: ' + method;
+
+    var newArgs = argsToArray(arguments);
+
+    newArgs[5] = function(ex, res) {
+        if (ex)
+            cb(ex.message);
+        else
+            cb(null, res.code, res.message)
+    };
+
+    this._invokeRmtMethod('executeRest', newArgs);
+};
+
+/**
+ * @param {string} error
+ */
+Client.prototype.authResult = function(error) {
+    this._invokeRmtMethod('authResult', arguments)
+};
+
+/**
+ * @param {String} jdbcDriverJarPath
+ * @param {String} jdbcDriverClass
+ * @param {String} jdbcUrl
+ * @param {Object} jdbcInfo
+ * @param {Function} cb Callback. Take two arguments: {Object} exception, {Object} result.
+ * @return {Array} List of tables (see org.apache.ignite.schema.parser.DbTable java class)
+ */
+Client.prototype.metadataSchemas = function(jdbcDriverJarPath, jdbcDriverClass, jdbcUrl, jdbcInfo, cb) {
+    this._invokeRmtMethod('schemas', arguments)
+};
+
+/**
+ * @param {String} jdbcDriverJarPath
+ * @param {String} jdbcDriverClass
+ * @param {String} jdbcUrl
+ * @param {Object} jdbcInfo
+ * @param {Array} schemas
+ * @param {Boolean} tablesOnly
+ * @param {Function} cb Callback. Take two arguments: {Object} exception, {Object} result.
+ * @return {Array} List of tables (see org.apache.ignite.schema.parser.DbTable java class)
+ */
+Client.prototype.metadataTables = function(jdbcDriverJarPath, jdbcDriverClass, jdbcUrl, jdbcInfo, schemas, tablesOnly, cb) {
+    this._invokeRmtMethod('metadata', arguments)
+};
+
+/**
+ * @param {Function} cb Callback. Take two arguments: {Object} exception, {Object} result.
+ * @return {Array} List of jars from driver folder.
+ */
+Client.prototype.availableDrivers = function(cb) {
+    this._invokeRmtMethod('availableDrivers', arguments)
+};
+
+Client.prototype._invokeRmtMethod = function(methodName, args) {
+    var cb = null;
+
+    var m = argsToArray(args);
+
+    if (m.length > 0 && typeof m[m.length - 1] == 'function')
+        cb = m.pop();
+
+    if (this._ws.readyState != 1) {
+        if (cb)
+            cb({type: 'org.apache.ignite.agent.AgentException', message: 'Connection is closed'});
+
+        return
+    }
+
+    var msg = {
+        mtdName: methodName,
+        args: m
+    };
+
+    if (cb) {
+        var reqId = this._reqCounter++;
+
+        this._cbMap[reqId] = cb;
+
+        msg.reqId = reqId;
+    }
+
+    this._ws.send(JSON.stringify(msg))
+};
+
+Client.prototype._rmtAuthMessage = function(msg) {
+    var self = this;
+
+    db.Account.findOne({ token: msg.token }, function (err, account) {
+        if (err) {
+            self.authResult('Failed to authorize user');
+            // TODO IGNITE-1379 send error to web master.
+        }
+        else if (!account)
+            self.authResult('Invalid token, user not found');
+        else {
+            self.authResult(null);
+
+            self._user = account;
+
+            self._manager._addClient(account._id, self);
+
+            self._ignite = new apacheIgnite.Ignite(new AgentServer(self));
+        }
+    });
+};
+
+Client.prototype._rmtCallRes = function(msg) {
+    var cb = this._cbMap[msg.reqId];
+
+    if (!cb) return;
+
+    delete this._cbMap[msg.reqId];
+
+    if (msg.ex)
+        cb(msg.ex);
+    else
+        cb(null, msg.res);
+};
+
+/**
+ * @return {Ignite}
+ */
+Client.prototype.ignite = function() {
+    return this._ignite;
+};
+
+function removeFromArray(arr, val) {
+    var idx;
+
+    while ((idx = arr.indexOf(val)) !== -1) {
+        arr.splice(idx, 1);
+    }
+}
+
+/**
+ * @param args
+ * @returns {Array}
+ */
+function argsToArray(args) {
+    var res = [];
+
+    for (var i = 0; i < args.length; i++)
+        res.push(args[i])
+
+    return res;
+}
+
+exports.AgentManager = AgentManager;
+
+/**
+ * @type {AgentManager}
+ */
+var manager = null;
+
+exports.createManager = function(srv) {
+    if (manager)
+        throw 'Agent manager already cleared!';
+
+    manager = new AgentManager(srv);
+};
+
+/**
+ * @return {AgentManager}
+ */
+exports.getAgentManager = function() {
+    return manager;
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/agents/agent-server.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/agents/agent-server.js b/modules/control-center-web/src/main/js/agents/agent-server.js
new file mode 100644
index 0000000..bd7efbf
--- /dev/null
+++ b/modules/control-center-web/src/main/js/agents/agent-server.js
@@ -0,0 +1,98 @@
+/*
+ *
+ *  * 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');
+
+/**
+ * Creates an instance of server for Ignite
+ *
+ * @constructor
+ * @this {AgentServer}
+ * @param {Client} client connected client
+ */
+function AgentServer(client) {
+    this._client = client;
+}
+
+/**
+ * Run http request
+ *
+ * @this {AgentServer}
+ * @param {Command} cmd Command
+ * @param {callback} callback on finish
+ */
+AgentServer.prototype.runCommand = function(cmd, callback) {
+    var params = {cmd: cmd.name()};
+
+    _.forEach(cmd._params, function (p) {
+        params[p.key] = p.value;
+    });
+
+    var body = undefined;
+
+    var headers = undefined;
+
+    var method = 'GET';
+
+    if (cmd._isPost()) {
+        body = cmd.postData();
+
+        method = 'POST';
+
+        headers = {'JSONObject': 'application/json'};
+    }
+
+    this._client.executeRest("ignite", params, method, headers, body, function(error, code, message) {
+        if (error) {
+            callback(error);
+            return
+        }
+
+        if (code !== 200) {
+            if (code === 401) {
+                callback.call(null, "Authentication failed. Status code 401.");
+            }
+            else {
+                callback.call(null, "Request failed. Status code " + code);
+            }
+
+            return;
+        }
+
+        var igniteResponse;
+
+        try {
+            igniteResponse = JSON.parse(message);
+        }
+        catch (e) {
+            callback.call(null, e, null);
+
+            return;
+        }
+
+        if (igniteResponse.successStatus) {
+            callback.call(null, igniteResponse.error, null)
+        }
+        else {
+            callback.call(null, null, igniteResponse.response);
+        }
+    });
+};
+
+exports.AgentServer = AgentServer;

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/bin/www
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/bin/www b/modules/control-center-web/src/main/js/bin/www
new file mode 100644
index 0000000..69e73e3
--- /dev/null
+++ b/modules/control-center-web/src/main/js/bin/www
@@ -0,0 +1,126 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+var http = require('http');
+var https = require('https');
+var config = require('../helpers/configuration-loader.js');
+var app = require('../app');
+var agentManager = require('../agents/agent-manager');
+
+var fs = require('fs');
+
+var debug = require('debug')('ignite-web-console:server');
+
+/**
+ * Get port from environment and store in Express.
+ */
+var port = config.normalizePort(config.get('server:port') || process.env.PORT || 80);
+
+// Create HTTP server.
+var server = http.createServer(app);
+
+app.set('port', port);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+if (config.get('server:ssl')) {
+    httpsServer = https.createServer({
+        key: fs.readFileSync(config.get('server:key')),
+        cert: fs.readFileSync(config.get('server:cert')),
+        passphrase: config.get('server:keyPassphrase')
+    }, app);
+
+    var httpsPort = config.normalizePort(config.get('server:https-port') || 443);
+
+    /**
+     * Listen on provided port, on all network interfaces.
+     */
+    httpsServer.listen(httpsPort);
+    httpsServer.on('error', onError);
+    httpsServer.on('listening', onListening);
+}
+
+/**
+ * Start agent server.
+ */
+var agentServer;
+
+if (config.get('agent-server:ssl')) {
+    agentServer = https.createServer({
+    key: fs.readFileSync(config.get('agent-server:key')),
+    cert: fs.readFileSync(config.get('agent-server:cert')),
+    passphrase: config.get('agent-server:keyPassphrase')
+  });
+}
+else {
+  agentServer = http.createServer();
+}
+
+agentServer.listen(config.get('agent-server:port'));
+
+agentManager.createManager(agentServer);
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+function onError(error) {
+  if (error.syscall !== 'listen') {
+    throw error;
+  }
+
+  var bind = typeof port === 'string'
+    ? 'Pipe ' + port
+    : 'Port ' + port;
+
+  // handle specific listen errors with friendly messages
+  switch (error.code) {
+    case 'EACCES':
+      console.error(bind + ' requires elevated privileges');
+      process.exit(1);
+      break;
+    case 'EADDRINUSE':
+      console.error(bind + ' is already in use');
+      process.exit(1);
+      break;
+    default:
+      throw error;
+  }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+function onListening() {
+  var addr = server.address();
+  var bind = typeof addr === 'string'
+    ? 'pipe ' + addr
+    : 'port ' + addr.port;
+
+  debug('Listening on ' + bind);
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/config/default.json
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/config/default.json b/modules/control-center-web/src/main/js/config/default.json
new file mode 100644
index 0000000..bf1e88b
--- /dev/null
+++ b/modules/control-center-web/src/main/js/config/default.json
@@ -0,0 +1,25 @@
+{
+    "server": {
+        "port": 3000,
+        "https-port": 8443,
+        "ssl": false,
+        "key": "keys/test.key",
+        "cert": "keys/test.crt",
+        "keyPassphrase": "password"
+    },
+    "mongoDB": {
+        "url": "mongodb://localhost/web-control-center"
+    },
+    "agent-server": {
+        "port": 3001,
+        "ssl": true,
+        "key": "keys/test.key",
+        "cert": "keys/test.crt",
+        "keyPassphrase": "password"
+    },
+    "smtp": {
+        "service": "",
+        "username": "",
+        "password": ""
+    }
+}

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

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/caches-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/caches-controller.js b/modules/control-center-web/src/main/js/controllers/caches-controller.js
new file mode 100644
index 0000000..28cedf0
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/caches-controller.js
@@ -0,0 +1,594 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Controller for Caches screen.
+consoleModule.controller('cachesController', [
+    '$scope', '$controller', '$filter', '$http', '$timeout', '$common', '$focus', '$confirm', '$message', '$clone', '$table', '$preview', '$loading', '$unsavedChangesGuard',
+    function ($scope, $controller, $filter, $http, $timeout, $common, $focus, $confirm, $message, $clone, $table, $preview, $loading, $unsavedChangesGuard) {
+            $unsavedChangesGuard.install($scope);
+
+            // Initialize the super class and extend it.
+            angular.extend(this, $controller('save-remove', {$scope: $scope}));
+
+            $scope.ui = $common.formUI();
+
+            $scope.showMoreInfo = $message.message;
+
+            $scope.joinTip = $common.joinTip;
+            $scope.getModel = $common.getModel;
+            $scope.javaBuildInClasses = $common.javaBuildInClasses;
+            $scope.compactJavaName = $common.compactJavaName;
+            $scope.saveBtnTipText = $common.saveBtnTipText;
+
+            $scope.tableReset = $table.tableReset;
+            $scope.tableNewItem = $table.tableNewItem;
+            $scope.tableNewItemActive = $table.tableNewItemActive;
+            $scope.tableEditing = $table.tableEditing;
+            $scope.tableStartEdit = $table.tableStartEdit;
+            $scope.tableRemove = function (item, field, index) {
+                $table.tableRemove(item, field, index);
+            };
+
+            $scope.tableSimpleSave = $table.tableSimpleSave;
+            $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+            $scope.tableSimpleUp = $table.tableSimpleUp;
+            $scope.tableSimpleDown = $table.tableSimpleDown;
+            $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
+
+            $scope.tablePairSave = $table.tablePairSave;
+            $scope.tablePairSaveVisible = $table.tablePairSaveVisible;
+
+            var previews = [];
+
+            $scope.previewInit = function (preview) {
+                previews.push(preview);
+
+                $preview.previewInit(preview);
+            };
+
+            $scope.previewChanged = $preview.previewChanged;
+
+            $scope.hidePopover = $common.hidePopover;
+
+            var showPopoverMessage = $common.showPopoverMessage;
+
+            $scope.atomicities = $common.mkOptions(['ATOMIC', 'TRANSACTIONAL']);
+
+            $scope.cacheModes = $common.mkOptions(['PARTITIONED', 'REPLICATED', 'LOCAL']);
+
+            $scope.atomicWriteOrderModes = $common.mkOptions(['CLOCK', 'PRIMARY']);
+
+            $scope.memoryModes = $common.mkOptions(['ONHEAP_TIERED', 'OFFHEAP_TIERED', 'OFFHEAP_VALUES']);
+
+            $scope.evictionPolicies = [
+                {value: 'LRU', label: 'LRU'},
+                {value: 'RND', label: 'Random'},
+                {value: 'FIFO', label: 'FIFO'},
+                {value: 'SORTED', label: 'Sorted'},
+                {value: undefined, label: 'Not set'}
+            ];
+
+            $scope.rebalanceModes = $common.mkOptions(['SYNC', 'ASYNC', 'NONE']);
+
+            $scope.cacheStoreFactories = [
+                {value: 'CacheJdbcPojoStoreFactory', label: 'JDBC POJO store factory'},
+                {value: 'CacheJdbcBlobStoreFactory', label: 'JDBC BLOB store factory'},
+                {value: 'CacheHibernateBlobStoreFactory', label: 'Hibernate BLOB store factory'},
+                {value: undefined, label: 'Not set'}
+            ];
+
+            $scope.cacheStoreJdbcDialects = [
+                {value: 'Oracle', label: 'Oracle'},
+                {value: 'DB2', label: 'IBM DB2'},
+                {value: 'SQLServer', label: 'Microsoft SQL Server'},
+                {value: 'MySQL', label: 'My SQL'},
+                {value: 'PostgreSQL', label: 'Postgre SQL'},
+                {value: 'H2', label: 'H2 database'}
+            ];
+
+            $scope.toggleExpanded = function () {
+                $scope.ui.expanded = !$scope.ui.expanded;
+
+                $common.hidePopover();
+            };
+
+            $scope.panels = {activePanels: [0]};
+
+            $scope.general = [];
+            $scope.advanced = [];
+            $scope.caches = [];
+            $scope.metadatas = [];
+
+            $scope.preview = {
+                general: {xml: '', java: '', allDefaults: true},
+                memory: {xml: '', java: '', allDefaults: true},
+                query: {xml: '', java: '', allDefaults: true},
+                store: {xml: '', java: '', allDefaults: true},
+                concurrency: {xml: '', java: '', allDefaults: true},
+                rebalance: {xml: '', java: '', allDefaults: true},
+                serverNearCache: {xml: '', java: '', allDefaults: true},
+                statistics: {xml: '', java: '', allDefaults: true}
+            };
+
+            $scope.required = function (field) {
+                var model = $common.isDefined(field.path) ? field.path + '.' + field.model : field.model;
+
+                var backupItem = $scope.backupItem;
+
+                var memoryMode = backupItem.memoryMode;
+
+                var onHeapTired = memoryMode == 'ONHEAP_TIERED';
+                var offHeapTired = memoryMode == 'OFFHEAP_TIERED';
+
+                var offHeapMaxMemory = backupItem.offHeapMaxMemory;
+
+                if (model == 'offHeapMaxMemory' && offHeapTired)
+                    return true;
+
+                if (model == 'evictionPolicy.kind' && onHeapTired)
+                    return backupItem.swapEnabled || ($common.isDefined(offHeapMaxMemory) && offHeapMaxMemory >= 0);
+
+                return false;
+            };
+
+            $scope.tableSimpleValid = function (item, field, fx, index) {
+                var model;
+
+                switch (field.model) {
+                    case 'hibernateProperties':
+                        if (fx.indexOf('=') < 0)
+                            return showPopoverMessage(null, null, $table.tableFieldId(index, 'HibProp'), 'Property should be present in format key=value!');
+
+                        model = item.cacheStoreFactory.CacheHibernateBlobStoreFactory[field.model];
+
+                        var key = fx.split('=')[0];
+
+                        var exist = false;
+
+                        if ($common.isDefined(model)) {
+                            model.forEach(function (val) {
+                                if (val.split('=')[0] == key)
+                                    exist = true;
+                            })
+                        }
+
+                        if (exist)
+                            return showPopoverMessage(null, null, $table.tableFieldId(index, 'HibProp'), 'Property with such name already exists!');
+
+                        break;
+
+                    case 'sqlFunctionClasses':
+                        if (!$common.isValidJavaClass('SQL function', fx, false, $table.tableFieldId(index, 'SqlFx')))
+                            return $table.tableFocusInvalidField(index, 'SqlFx');
+
+                        model = item[field.model];
+
+                        if ($common.isDefined(model)) {
+                            var idx = _.indexOf(model, fx);
+
+                            // Found duplicate.
+                            if (idx >= 0 && idx != index)
+                                return showPopoverMessage(null, null, $table.tableFieldId(index, 'SqlFx'), 'SQL function with such class name already exists!');
+                        }
+                }
+
+                return true;
+            };
+
+            $scope.tablePairValid = function (item, field, index) {
+                var pairValue = $table.tablePairValue(field, index);
+
+                if (!$common.isValidJavaClass('Indexed type key', pairValue.key, true, $table.tableFieldId(index, 'KeyIndexedType')))
+                    return $table.tableFocusInvalidField(index, 'KeyIndexedType');
+
+                if (!$common.isValidJavaClass('Indexed type value', pairValue.value, true, $table.tableFieldId(index, 'ValueIndexedType')))
+                    return $table.tableFocusInvalidField(index, 'ValueIndexedType');
+
+                var model = item[field.model];
+
+                if ($common.isDefined(model)) {
+                    var idx = _.findIndex(model, function (pair) {
+                        return pair.keyClass == pairValue.key && pair.valueClass == pairValue.value;
+                    });
+
+                    // Found duplicate.
+                    if (idx >= 0 && idx != index)
+                        return showPopoverMessage(null, null, $table.tableFieldId(index, 'ValueIndexedType'), 'Indexed type with such key and value classes already exists!');
+                }
+
+                return true;
+            };
+
+            function selectFirstItem() {
+                if ($scope.caches.length > 0)
+                    $scope.selectItem($scope.caches[0]);
+            }
+
+            function cacheMetadatas(item) {
+                return _.reduce($scope.metadatas, function (memo, meta) {
+                    if (item && _.contains(item.metadatas, meta.value)) {
+                        memo.push(meta.meta);
+                    }
+
+                    return memo;
+                }, []);
+            }
+
+            $loading.start('loadingCachesScreen');
+
+            // When landing on the page, get caches and show them.
+            $http.post('caches/list')
+                .success(function (data) {
+                    var validFilter = $filter('metadatasValidation');
+
+                    $scope.spaces = data.spaces;
+                    $scope.caches = data.caches;
+                    $scope.clusters = data.clusters;
+                    $scope.metadatas = _.sortBy(_.map(validFilter(data.metadatas, true, false), function (meta) {
+                        return {value: meta._id, label: meta.valueType, kind: meta.kind, meta: meta}
+                    }), 'label');
+
+                    // Load page descriptor.
+                    $http.get('/models/caches.json')
+                        .success(function (data) {
+                            $scope.screenTip = data.screenTip;
+                            $scope.moreInfo = data.moreInfo;
+                            $scope.general = data.general;
+                            $scope.advanced = data.advanced;
+
+                            $scope.ui.addGroups(data.general, data.advanced);
+
+                            if ($common.getQueryVariable('new'))
+                                $scope.createItem();
+                            else {
+                                var lastSelectedCache = angular.fromJson(sessionStorage.lastSelectedCache);
+
+                                if (lastSelectedCache) {
+                                    var idx = _.findIndex($scope.caches, function (cache) {
+                                        return cache._id == lastSelectedCache;
+                                    });
+
+                                    if (idx >= 0)
+                                        $scope.selectItem($scope.caches[idx]);
+                                    else {
+                                        sessionStorage.removeItem('lastSelectedCache');
+
+                                        selectFirstItem();
+                                    }
+
+                                }
+                                else
+                                    selectFirstItem();
+                            }
+
+                            $scope.$watch('backupItem', function (val) {
+                                if (val) {
+                                    var srcItem = $scope.selectedItem ? $scope.selectedItem : prepareNewItem();
+
+                                    $scope.ui.checkDirty(val, srcItem);
+
+                                    var metas = cacheMetadatas(val);
+                                    var varName = $commonUtils.toJavaName('cache', val.name);
+
+                                    $scope.preview.general.xml = $generatorXml.cacheMetadatas(metas, $generatorXml.cacheGeneral(val)).asString();
+                                    $scope.preview.general.java = $generatorJava.cacheMetadatas(metas, varName, $generatorJava.cacheGeneral(val, varName)).asString();
+                                    $scope.preview.general.allDefaults = $common.isEmptyString($scope.preview.general.xml);
+
+                                    $scope.preview.memory.xml = $generatorXml.cacheMemory(val).asString();
+                                    $scope.preview.memory.java = $generatorJava.cacheMemory(val, varName).asString();
+                                    $scope.preview.memory.allDefaults = $common.isEmptyString($scope.preview.memory.xml);
+
+                                    $scope.preview.query.xml = $generatorXml.cacheQuery(val).asString();
+                                    $scope.preview.query.java = $generatorJava.cacheQuery(val, varName).asString();
+                                    $scope.preview.query.allDefaults = $common.isEmptyString($scope.preview.query.xml);
+
+                                    $scope.preview.store.xml = $generatorXml.cacheStore(val).asString();
+                                    $scope.preview.store.java = $generatorJava.cacheStore(val, varName).asString();
+                                    $scope.preview.store.allDefaults = $common.isEmptyString($scope.preview.store.xml);
+
+                                    $scope.preview.concurrency.xml = $generatorXml.cacheConcurrency(val).asString();
+                                    $scope.preview.concurrency.java = $generatorJava.cacheConcurrency(val, varName).asString();
+                                    $scope.preview.concurrency.allDefaults = $common.isEmptyString($scope.preview.concurrency.xml);
+
+                                    $scope.preview.rebalance.xml = $generatorXml.cacheRebalance(val).asString();
+                                    $scope.preview.rebalance.java = $generatorJava.cacheRebalance(val, varName).asString();
+                                    $scope.preview.rebalance.allDefaults = $common.isEmptyString($scope.preview.rebalance.xml);
+
+                                    $scope.preview.serverNearCache.xml = $generatorXml.cacheServerNearCache(val).asString();
+                                    $scope.preview.serverNearCache.java = $generatorJava.cacheServerNearCache(val, varName).asString();
+                                    $scope.preview.serverNearCache.allDefaults = $common.isEmptyString($scope.preview.serverNearCache.xml);
+
+                                    $scope.preview.statistics.xml = $generatorXml.cacheStatistics(val).asString();
+                                    $scope.preview.statistics.java = $generatorJava.cacheStatistics(val, varName).asString();
+                                    $scope.preview.statistics.allDefaults = $common.isEmptyString($scope.preview.statistics.xml);
+                                }
+                            }, true);
+
+                            $scope.$watch('backupItem.metadatas', function (val) {
+                                var item = $scope.backupItem;
+
+                                var cacheStoreFactory = $common.isDefined(item) &&
+                                    $common.isDefined(item.cacheStoreFactory) &&
+                                    $common.isDefined(item.cacheStoreFactory.kind);
+
+                                if (val && !cacheStoreFactory) {
+                                    if (_.findIndex(cacheMetadatas(item), $common.metadataForStoreConfigured) >= 0) {
+                                        item.cacheStoreFactory.kind = 'CacheJdbcPojoStoreFactory';
+
+                                        if (!item.readThrough && !item.writeThrough) {
+                                            item.readThrough = true;
+                                            item.writeThrough = true;
+                                        }
+
+                                        $timeout(function () {
+                                            $common.ensureActivePanel($scope.panels, 'store');
+                                        });
+                                    }
+                                }
+                            }, true);
+                        })
+                        .error(function (errMsg) {
+                            $common.showError(errMsg);
+                        });
+                })
+                .error(function (errMsg) {
+                    $common.showError(errMsg);
+                })
+                .finally(function () {
+                    $scope.ui.ready = true;
+                    $loading.finish('loadingCachesScreen');
+                });
+
+            $scope.selectItem = function (item, backup) {
+                function selectItem() {
+                    $table.tableReset();
+
+                    $scope.selectedItem = angular.copy(item);
+
+                    try {
+                        if (item)
+                            sessionStorage.lastSelectedCache = angular.toJson(item._id);
+                        else
+                            sessionStorage.removeItem('lastSelectedCache');
+                    }
+                    catch (error) { }
+
+                    _.forEach(previews, function(preview) {
+                        preview.attractAttention = false;
+                    });
+
+                    if (backup)
+                        $scope.backupItem = backup;
+                    else if (item)
+                        $scope.backupItem = angular.copy(item);
+                    else
+                        $scope.backupItem = undefined;
+                }
+
+                $common.confirmUnsavedChanges($scope.ui.isDirty(), selectItem);
+
+                $scope.ui.formTitle = $common.isDefined($scope.backupItem) && $scope.backupItem._id ?
+                    'Selected cache: ' + $scope.backupItem.name : 'New cache';
+            };
+
+            function prepareNewItem() {
+                return {
+                    space: $scope.spaces[0]._id,
+                    cacheMode: 'PARTITIONED',
+                    atomicityMode: 'ATOMIC',
+                    readFromBackup: true,
+                    copyOnRead: true,
+                    clusters: [],
+                    metadatas: []
+                }
+            }
+
+            // Add new cache.
+            $scope.createItem = function () {
+                $table.tableReset();
+
+                $timeout(function () {
+                    $common.ensureActivePanel($scope.panels, 'general', 'cacheName');
+                });
+
+                $scope.selectItem(undefined, prepareNewItem());
+            };
+
+            // Check cache logical consistency.
+            function validate(item) {
+                if ($common.isEmptyString(item.name))
+                    return showPopoverMessage($scope.panels, 'general', 'cacheName', 'Name should not be empty');
+
+                if (item.memoryMode == 'OFFHEAP_TIERED' && item.offHeapMaxMemory == null)
+                    return showPopoverMessage($scope.panels, 'memory', 'offHeapMaxMemory',
+                        'Off-heap max memory should be specified');
+
+                if (item.memoryMode == 'ONHEAP_TIERED' && item.offHeapMaxMemory > 0 &&
+                        !$common.isDefined(item.evictionPolicy.kind)) {
+                    return showPopoverMessage($scope.panels, 'memory', 'evictionPolicy', 'Eviction policy should not be configured');
+                }
+
+                var cacheStoreFactorySelected = item.cacheStoreFactory && item.cacheStoreFactory.kind;
+
+                if (cacheStoreFactorySelected) {
+                    if (item.cacheStoreFactory.kind == 'CacheJdbcPojoStoreFactory') {
+                        if ($common.isEmptyString(item.cacheStoreFactory.CacheJdbcPojoStoreFactory.dataSourceBean))
+                            return showPopoverMessage($scope.panels, 'store', 'dataSourceBean',
+                                'Data source bean should not be empty');
+
+                        if (!item.cacheStoreFactory.CacheJdbcPojoStoreFactory.dialect)
+                            return showPopoverMessage($scope.panels, 'store', 'dialect',
+                                'Dialect should not be empty');
+                    }
+
+                    if (item.cacheStoreFactory.kind == 'CacheJdbcBlobStoreFactory') {
+                        if ($common.isEmptyString(item.cacheStoreFactory.CacheJdbcBlobStoreFactory.user))
+                            return showPopoverMessage($scope.panels, 'store', 'user',
+                                'User should not be empty');
+
+                        if ($common.isEmptyString(item.cacheStoreFactory.CacheJdbcBlobStoreFactory.dataSourceBean))
+                            return showPopoverMessage($scope.panels, 'store', 'dataSourceBean',
+                                'Data source bean should not be empty');
+                    }
+                }
+
+                if ((item.readThrough || item.writeThrough) && !cacheStoreFactorySelected)
+                    return showPopoverMessage($scope.panels, 'store', 'cacheStoreFactory',
+                        (item.readThrough ? 'Read' : 'Write') + ' through are enabled but store is not configured!');
+
+                if (item.writeBehindEnabled && !cacheStoreFactorySelected)
+                    return showPopoverMessage($scope.panels, 'store', 'cacheStoreFactory',
+                        'Write behind enabled but store is not configured!');
+
+                if (cacheStoreFactorySelected) {
+                    if (!item.readThrough && !item.writeThrough)
+                        return showPopoverMessage($scope.panels, 'store', 'readThrough',
+                            'Store is configured but read/write through are not enabled!');
+
+                    if (item.cacheStoreFactory.kind == 'CacheJdbcPojoStoreFactory') {
+                        if ($common.isDefined(item.metadatas)) {
+                            var metadatas = cacheMetadatas($scope.backupItem);
+
+                            if (_.findIndex(metadatas, $common.metadataForStoreConfigured) < 0)
+                                return showPopoverMessage($scope.panels, 'general', 'metadata',
+                                    'Cache with configured JDBC POJO store factory should contain at least one metadata with store configuration');
+                        }
+                    }
+                }
+
+                return true;
+            }
+
+            // Save cache into database.
+            function save(item) {
+                $http.post('caches/save', item)
+                    .success(function (_id) {
+                        $scope.ui.markPristine();
+
+                        var idx = _.findIndex($scope.caches, function (cache) {
+                            return cache._id == _id;
+                        });
+
+                        if (idx >= 0)
+                            angular.extend($scope.caches[idx], item);
+                        else {
+                            item._id = _id;
+                            $scope.caches.push(item);
+                        }
+
+                        $scope.selectItem(item);
+
+                        $common.showInfo('Cache "' + item.name + '" saved.');
+                    })
+                    .error(function (errMsg) {
+                        $common.showError(errMsg);
+                    });
+            }
+
+            // Save cache.
+            $scope.saveItem = function () {
+                $table.tableReset();
+
+                var item = $scope.backupItem;
+
+                if (validate(item))
+                    save(item);
+            };
+
+            // Save cache with new name.
+            $scope.cloneItem = function () {
+                $table.tableReset();
+
+                if (validate($scope.backupItem))
+                    $clone.confirm($scope.backupItem.name).then(function (newName) {
+                        var item = angular.copy($scope.backupItem);
+
+                        item._id = undefined;
+                        item.name = newName;
+
+                        save(item);
+                    });
+            };
+
+            // Remove cache from db.
+            $scope.removeItem = function () {
+                $table.tableReset();
+
+                var selectedItem = $scope.selectedItem;
+
+                $confirm.confirm('Are you sure you want to remove cache: "' + selectedItem.name + '"?')
+                    .then(function () {
+                            var _id = selectedItem._id;
+
+                            $http.post('caches/remove', {_id: _id})
+                                .success(function () {
+                                    $common.showInfo('Cache has been removed: ' + selectedItem.name);
+
+                                    var caches = $scope.caches;
+
+                                    var idx = _.findIndex(caches, function (cache) {
+                                        return cache._id == _id;
+                                    });
+
+                                    if (idx >= 0) {
+                                        caches.splice(idx, 1);
+
+                                        if (caches.length > 0)
+                                            $scope.selectItem(caches[0]);
+                                        else
+                                            $scope.selectItem(undefined, undefined);
+                                    }
+                                })
+                                .error(function (errMsg) {
+                                    $common.showError(errMsg);
+                                });
+                    });
+            };
+
+            // Remove all caches from db.
+            $scope.removeAllItems = function () {
+                $table.tableReset();
+
+                $confirm.confirm('Are you sure you want to remove all caches?')
+                    .then(function () {
+                            $http.post('caches/remove/all')
+                                .success(function () {
+                                    $common.showInfo('All caches have been removed');
+
+                                    $scope.caches = [];
+
+                                    $scope.selectItem(undefined, undefined);
+                                })
+                                .error(function (errMsg) {
+                                    $common.showError(errMsg);
+                                });
+                    });
+            };
+
+            $scope.resetItem = function (group) {
+                var resetTo = $scope.selectedItem;
+
+                if (!$common.isDefined(resetTo))
+                    resetTo = prepareNewItem();
+
+                $common.resetItem($scope.backupItem, resetTo, $scope.general, group);
+                $common.resetItem($scope.backupItem, resetTo, $scope.advanced, group);
+            }
+        }]
+);


[15/18] ignite git commit: IGNITE-843 Split schema-import

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java
----------------------------------------------------------------------
diff --git a/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java
index 4f419e6..cbcddc2 100644
--- a/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java
+++ b/modules/schema-import/src/main/java/org/apache/ignite/schema/ui/SchemaImportApp.java
@@ -22,16 +22,11 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
 import java.sql.Connection;
-import java.sql.Driver;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -45,6 +40,7 @@ import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.concurrent.Task;
+import javafx.embed.swing.SwingFXUtils;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.geometry.Insets;
@@ -81,34 +77,7 @@ import org.apache.ignite.schema.model.PojoDescriptor;
 import org.apache.ignite.schema.model.PojoField;
 import org.apache.ignite.schema.model.SchemaDescriptor;
 import org.apache.ignite.schema.parser.DatabaseMetadataParser;
-
-import static javafx.embed.swing.SwingFXUtils.fromFXImage;
-import static org.apache.ignite.schema.ui.Controls.booleanColumn;
-import static org.apache.ignite.schema.ui.Controls.borderPane;
-import static org.apache.ignite.schema.ui.Controls.button;
-import static org.apache.ignite.schema.ui.Controls.buttonsPane;
-import static org.apache.ignite.schema.ui.Controls.checkBox;
-import static org.apache.ignite.schema.ui.Controls.comboBox;
-import static org.apache.ignite.schema.ui.Controls.customColumn;
-import static org.apache.ignite.schema.ui.Controls.hBox;
-import static org.apache.ignite.schema.ui.Controls.image;
-import static org.apache.ignite.schema.ui.Controls.imageView;
-import static org.apache.ignite.schema.ui.Controls.label;
-import static org.apache.ignite.schema.ui.Controls.list;
-import static org.apache.ignite.schema.ui.Controls.paneEx;
-import static org.apache.ignite.schema.ui.Controls.passwordField;
-import static org.apache.ignite.schema.ui.Controls.progressIndicator;
-import static org.apache.ignite.schema.ui.Controls.scene;
-import static org.apache.ignite.schema.ui.Controls.splitPane;
-import static org.apache.ignite.schema.ui.Controls.stackPane;
-import static org.apache.ignite.schema.ui.Controls.tableColumn;
-import static org.apache.ignite.schema.ui.Controls.tableView;
-import static org.apache.ignite.schema.ui.Controls.text;
-import static org.apache.ignite.schema.ui.Controls.textColumn;
-import static org.apache.ignite.schema.ui.Controls.textField;
-import static org.apache.ignite.schema.ui.Controls.titledPane;
-import static org.apache.ignite.schema.ui.Controls.tooltip;
-import static org.apache.ignite.schema.ui.Controls.vBox;
+import org.apache.ignite.schema.parser.DbMetadataReader;
 
 /**
  * Schema Import utility application.
@@ -117,68 +86,6 @@ import static org.apache.ignite.schema.ui.Controls.vBox;
 public class SchemaImportApp extends Application {
     /** Logger. */
     private static final Logger log = Logger.getLogger(SchemaImportApp.class.getName());
-
-    /** Presets for database settings. */
-    private static class Preset {
-        /** Name in preferences. */
-        private String pref;
-
-        /** RDBMS name to show on screen. */
-        private String name;
-
-        /** Path to JDBC driver jar. */
-        private String jar;
-
-        /** JDBC driver class name. */
-        private String drv;
-
-        /** JDBC URL. */
-        private String url;
-
-        /** User name. */
-        private String user;
-
-        /**
-         * Preset constructor.
-         *
-         * @param pref Name in preferences.
-         * @param name RDBMS name to show on screen.
-         * @param jar Path to JDBC driver jar..
-         * @param drv JDBC driver class name.
-         * @param url JDBC URL.
-         * @param user User name.
-         */
-        Preset(String pref, String name, String jar, String drv, String url, String user) {
-            this.pref = pref;
-            this.name = name;
-            this.jar = jar;
-            this.drv = drv;
-            this.url = url;
-            this.user = user;
-        }
-
-        /** {@inheritDoc} */
-        @Override public String toString() {
-            return name;
-        }
-    }
-
-    /** Default presets for popular databases. */
-    private final Preset[] presets = {
-        new Preset("h2", "H2 Database", "h2.jar", "org.h2.Driver", "jdbc:h2:[database]", "sa"),
-        new Preset("db2", "DB2", "db2jcc4.jar", "com.ibm.db2.jcc.DB2Driver", "jdbc:db2://[host]:[port]/[database]",
-            "db2admin"),
-        new Preset("oracle", "Oracle", "ojdbc6.jar", "oracle.jdbc.OracleDriver",
-            "jdbc:oracle:thin:@[host]:[port]:[database]", "system"),
-        new Preset("mysql", "MySQL", "mysql-connector-java-5-bin.jar", "com.mysql.jdbc.Driver",
-            "jdbc:mysql://[host]:[port]/[database]", "root"),
-        new Preset("mssql", "Microsoft SQL Server", "sqljdbc41.jar", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
-            "jdbc:sqlserver://[host]:[port][;databaseName=database]", "sa"),
-        new Preset("postgresql", "PostgreSQL", "postgresql-9.3.jdbc4.jar", "org.postgresql.Driver",
-            "jdbc:postgresql://[host]:[port]/[database]", "sa"),
-        new Preset("custom", "Custom server...", "custom-jdbc.jar", "org.custom.Driver", "jdbc:custom", "sa")
-    };
-
     /** */
     private static final String PREF_WINDOW_X = "window.x";
     /** */
@@ -187,7 +94,6 @@ public class SchemaImportApp extends Application {
     private static final String PREF_WINDOW_WIDTH = "window.width";
     /** */
     private static final String PREF_WINDOW_HEIGHT = "window.height";
-
     /** */
     private static final String PREF_JDBC_DB_PRESET = "jdbc.db.preset";
     /** */
@@ -198,151 +104,154 @@ public class SchemaImportApp extends Application {
     private static final String PREF_JDBC_URL = "jdbc.url";
     /** */
     private static final String PREF_JDBC_USER = "jdbc.user";
-
     /** */
     private static final String PREF_OUT_FOLDER = "out.folder";
-
     /** */
     private static final String PREF_POJO_PACKAGE = "pojo.package";
     /** */
     private static final String PREF_POJO_INCLUDE = "pojo.include";
     /** */
     private static final String PREF_POJO_CONSTRUCTOR = "pojo.constructor";
-
     /** */
     private static final String PREF_XML_SINGLE = "xml.single";
-
     /** */
     private static final String PREF_NAMING_PATTERN = "naming.pattern";
     /** */
     private static final String PREF_NAMING_REPLACE = "naming.replace";
+    /** Empty POJO fields model. */
+    private static final ObservableList<PojoField> NO_FIELDS = FXCollections.emptyObservableList();
+    /** Default presets for popular databases. */
+    private final Preset[] presets = {
+        new Preset("h2", "H2 Database", "h2.jar", "org.h2.Driver", "jdbc:h2:[database]", "sa"),
+        new Preset("db2", "DB2", "db2jcc4.jar", "com.ibm.db2.jcc.DB2Driver", "jdbc:db2://[host]:[port]/[database]",
+            "db2admin"),
+        new Preset("oracle", "Oracle", "ojdbc6.jar", "oracle.jdbc.OracleDriver",
+            "jdbc:oracle:thin:@[host]:[port]:[database]", "system"),
+        new Preset("mysql", "MySQL", "mysql-connector-java-5-bin.jar", "com.mysql.jdbc.Driver",
+            "jdbc:mysql://[host]:[port]/[database]", "root"),
+        new Preset("mssql", "Microsoft SQL Server", "sqljdbc41.jar", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
+            "jdbc:sqlserver://[host]:[port][;databaseName=database]", "sa"),
+        new Preset("postgresql", "PostgreSQL", "postgresql-9.3.jdbc4.jar", "org.postgresql.Driver",
+            "jdbc:postgresql://[host]:[port]/[database]", "sa"),
+        new Preset("custom", "Custom server...", "custom-jdbc.jar", "org.custom.Driver", "jdbc:custom", "sa")
+    };
+    /** Application preferences. */
+    private final Properties prefs = new Properties();
+    /** File path for storing on local file system. */
+    private final File prefsFile = new File(System.getProperty("user.home"), ".ignite-schema-import");
+    /** */
+    private final ExecutorService exec = Executors.newSingleThreadExecutor(new ThreadFactory() {
+        @Override public Thread newThread(Runnable r) {
+            Thread t = new Thread(r, "ignite-schema-import-worker");
+
+            t.setDaemon(true);
 
+            return t;
+        }
+    });
     /** */
     private Stage owner;
-
     /** */
     private BorderPane rootPane;
-
     /** Header pane. */
     private BorderPane hdrPane;
-
     /** */
     private HBox dbIcon;
-
     /** */
     private HBox genIcon;
-
     /** */
     private Label titleLb;
-
     /** */
     private Label subTitleLb;
-
     /** */
     private Button prevBtn;
-
     /** */
     private Button nextBtn;
-
     /** */
     private ComboBox<Preset> rdbmsCb;
-
     /** */
     private TextField jdbcDrvJarTf;
-
     /** */
     private TextField jdbcDrvClsTf;
-
     /** */
     private TextField jdbcUrlTf;
-
     /** */
     private TextField userTf;
-
     /** */
     private PasswordField pwdTf;
-
     /** */
     private ComboBox<String> parseCb;
-
     /** */
     private ListView<SchemaDescriptor> schemaLst;
-
     /** */
     private GridPaneEx connPnl;
-
     /** */
     private StackPane connLayerPnl;
-
     /** */
     private TableView<PojoDescriptor> pojosTbl;
-
     /** */
     private TableView<PojoField> fieldsTbl;
-
     /** */
     private Node curTbl;
-
     /** */
     private TextField outFolderTf;
-
     /** */
     private TextField pkgTf;
-
     /** */
     private CheckBox pojoConstructorCh;
-
     /** */
     private CheckBox pojoIncludeKeysCh;
-
     /** */
     private CheckBox xmlSingleFileCh;
-
     /** */
     private TextField regexTf;
-
     /** */
     private TextField replaceTf;
-
     /** */
     private GridPaneEx genPnl;
-
     /** */
     private StackPane genLayerPnl;
-
     /** */
     private ProgressIndicator pi;
-
+    /** */
     private ObservableList<SchemaDescriptor> schemas = FXCollections.emptyObservableList();
-
     /** List with POJOs descriptors. */
     private ObservableList<PojoDescriptor> pojos = FXCollections.emptyObservableList();
-
     /** Currently selected POJO. */
     private PojoDescriptor curPojo;
 
-    /** */
-    private final Map<String, Driver> drivers = new HashMap<>();
-
-    /** Application preferences. */
-    private final Properties prefs = new Properties();
+    /**
+     * Schema Import utility launcher.
+     *
+     * @param args Command line arguments passed to the application.
+     */
+    public static void main(String[] args) {
+        // Workaround for JavaFX ugly text AA.
+        System.setProperty("prism.lcdtext", "false");
+        System.setProperty("prism.text", "t2k");
 
-    /** File path for storing on local file system. */
-    private final File prefsFile = new File(System.getProperty("user.home"), ".ignite-schema-import");
+        // Workaround for AWT + JavaFX: we should initialize AWT before JavaFX.
+        java.awt.Toolkit.getDefaultToolkit();
 
-    /** Empty POJO fields model. */
-    private static final ObservableList<PojoField> NO_FIELDS = FXCollections.emptyObservableList();
+        // Workaround for JavaFX + Mac OS dock icon.
+        if (System.getProperty("os.name").toLowerCase().contains("mac os")) {
+            System.setProperty("javafx.macosx.embedded", "true");
 
-    /** */
-    private final ExecutorService exec = Executors.newSingleThreadExecutor(new ThreadFactory() {
-        @Override public Thread newThread(Runnable r) {
-            Thread t = new Thread(r, "ignite-schema-import-worker");
+            try {
+                Class<?> appCls = Class.forName("com.apple.eawt.Application");
 
-            t.setDaemon(true);
+                Object osxApp = appCls.getDeclaredMethod("getApplication").invoke(null);
 
-            return t;
+                appCls.getDeclaredMethod("setDockIconImage", java.awt.Image.class)
+                    .invoke(osxApp, SwingFXUtils.fromFXImage(Controls.image("ignite", 128), null));
+            }
+            catch (Exception ignore) {
+                // No-op.
+            }
         }
-    });
+
+        launch(args);
+    }
 
     /**
      * Lock UI before start long task.
@@ -412,7 +321,7 @@ public class SchemaImportApp extends Application {
         if (!pwd.isEmpty())
             jdbcInfo.put("password", pwd);
 
-        return connect(jdbcDrvJarPath, jdbcDrvCls, jdbcUrl, jdbcInfo);
+        return DbMetadataReader.getInstance().connect(jdbcDrvJarPath, jdbcDrvCls, jdbcUrl, jdbcInfo);
     }
 
     /**
@@ -478,7 +387,7 @@ public class SchemaImportApp extends Application {
 
                     prevBtn.setDisable(false);
                     nextBtn.setText("Generate");
-                    tooltip(nextBtn, "Generate XML and POJO files");
+                    Controls.tooltip(nextBtn, "Generate XML and POJO files");
                 }
                 finally {
                     unlockUI(connLayerPnl, connPnl, nextBtn);
@@ -701,15 +610,16 @@ public class SchemaImportApp extends Application {
      * @return Header pane with title label.
      */
     private BorderPane createHeaderPane() {
-        dbIcon = hBox(0, true, imageView("data_connection", 48));
-        genIcon = hBox(0, true, imageView("text_tree", 48));
+        dbIcon = Controls.hBox(0, true, Controls.imageView("data_connection", 48));
+        genIcon = Controls.hBox(0, true, Controls.imageView("text_tree", 48));
 
-        titleLb = label("");
+        titleLb = Controls.label("");
         titleLb.setId("banner");
 
-        subTitleLb = label("");
+        subTitleLb = Controls.label("");
 
-        BorderPane bp = borderPane(null, vBox(5, titleLb, subTitleLb), null, dbIcon, hBox(0, true, imageView("ignite", 48)));
+        BorderPane bp = Controls.borderPane(null, Controls.vBox(5, titleLb, subTitleLb), null, dbIcon,
+            Controls.hBox(0, true, Controls.imageView("ignite", 48)));
         bp.setId("banner");
 
         return bp;
@@ -719,19 +629,19 @@ public class SchemaImportApp extends Application {
      * @return Panel with control buttons.
      */
     private Pane createButtonsPane() {
-        prevBtn = button("Prev", "Go to \"Database connection\" page", new EventHandler<ActionEvent>() {
+        prevBtn = Controls.button("Prev", "Go to \"Database connection\" page", new EventHandler<ActionEvent>() {
             @Override public void handle(ActionEvent evt) {
                 prev();
             }
         });
 
-        nextBtn = button("Next", "Go to \"POJO and XML generation\" page", new EventHandler<ActionEvent>() {
+        nextBtn = Controls.button("Next", "Go to \"POJO and XML generation\" page", new EventHandler<ActionEvent>() {
             @Override public void handle(ActionEvent evt) {
                 next();
             }
         });
 
-        return buttonsPane(Pos.BOTTOM_RIGHT, true, prevBtn, nextBtn);
+        return Controls.buttonsPane(Pos.BOTTOM_RIGHT, true, prevBtn, nextBtn);
     }
 
     /**
@@ -762,7 +672,7 @@ public class SchemaImportApp extends Application {
 
         prevBtn.setDisable(true);
         nextBtn.setText("Next");
-        tooltip(nextBtn, "Go to \"XML and POJO generation\" page");
+        Controls.tooltip(nextBtn, "Go to \"XML and POJO generation\" page");
     }
 
     /**
@@ -805,57 +715,12 @@ public class SchemaImportApp extends Application {
     }
 
     /**
-     * Connect to database.
-     *
-     * @param jdbcDrvJarPath Path to JDBC driver.
-     * @param jdbcDrvCls JDBC class name.
-     * @param jdbcUrl JDBC connection URL.
-     * @param jdbcInfo Connection properties.
-     * @return Connection to database.
-     * @throws SQLException if connection failed.
-     */
-    private Connection connect(String jdbcDrvJarPath, String jdbcDrvCls, String jdbcUrl, Properties jdbcInfo)
-        throws SQLException {
-        Driver drv = drivers.get(jdbcDrvCls);
-
-        if (drv == null) {
-            if (jdbcDrvJarPath.isEmpty())
-                throw new IllegalStateException("Driver jar file name is not specified.");
-
-            File drvJar = new File(jdbcDrvJarPath);
-
-            if (!drvJar.exists())
-                throw new IllegalStateException("Driver jar file is not found.");
-
-            try {
-                URL u = new URL("jar:" + drvJar.toURI() + "!/");
-
-                URLClassLoader ucl = URLClassLoader.newInstance(new URL[] {u});
-
-                drv = (Driver)Class.forName(jdbcDrvCls, true, ucl).newInstance();
-
-                drivers.put(jdbcDrvCls, drv);
-            }
-            catch (Exception e) {
-                throw new IllegalStateException(e);
-            }
-        }
-
-        Connection conn = drv.connect(jdbcUrl, jdbcInfo);
-
-        if (conn == null)
-            throw new IllegalStateException("Connection was not established (JDBC driver returned null value).");
-
-        return conn;
-    }
-
-    /**
      * Create connection pane with controls.
      *
      * @return Pane with connection controls.
      */
     private Pane createConnectionPane() {
-        connPnl = paneEx(10, 10, 0, 10);
+        connPnl = Controls.paneEx(10, 10, 0, 10);
 
         connPnl.addColumn();
         connPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
@@ -864,18 +729,18 @@ public class SchemaImportApp extends Application {
         connPnl.addRows(9);
         connPnl.addRow(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
 
-        connPnl.add(text("This utility is designed to automatically generate configuration XML files and" +
+        connPnl.add(Controls.text("This utility is designed to automatically generate configuration XML files and" +
             " POJO classes from database schema information.", 550), 3);
 
         connPnl.wrap();
 
-        GridPaneEx presetPnl = paneEx(0, 0, 0, 0);
+        GridPaneEx presetPnl = Controls.paneEx(0, 0, 0, 0);
         presetPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
         presetPnl.addColumn();
 
-        rdbmsCb = presetPnl.add(comboBox("Select database server to get predefined settings", presets));
+        rdbmsCb = presetPnl.add(Controls.comboBox("Select database server to get predefined settings", presets));
 
-        presetPnl.add(button("Save preset", "Save current settings in preferences", new EventHandler<ActionEvent>() {
+        presetPnl.add(Controls.button("Save preset", "Save current settings in preferences", new EventHandler<ActionEvent>() {
             @Override public void handle(ActionEvent evt) {
                 Preset preset = rdbmsCb.getSelectionModel().getSelectedItem();
 
@@ -883,12 +748,12 @@ public class SchemaImportApp extends Application {
             }
         }));
 
-        connPnl.add(label("DB Preset:"));
+        connPnl.add(Controls.label("DB Preset:"));
         connPnl.add(presetPnl, 2);
 
-        jdbcDrvJarTf = connPnl.addLabeled("Driver JAR:", textField("Path to driver jar"));
+        jdbcDrvJarTf = connPnl.addLabeled("Driver JAR:", Controls.textField("Path to driver jar"));
 
-        connPnl.add(button("...", "Select JDBC driver jar or zip", new EventHandler<ActionEvent>() {
+        connPnl.add(Controls.button("...", "Select JDBC driver jar or zip", new EventHandler<ActionEvent>() {
             /** {@inheritDoc} */
             @Override public void handle(ActionEvent evt) {
                 FileChooser fc = new FileChooser();
@@ -916,9 +781,9 @@ public class SchemaImportApp extends Application {
             }
         }));
 
-        jdbcDrvClsTf = connPnl.addLabeled("JDBC Driver:", textField("Enter class name for JDBC driver"), 2);
+        jdbcDrvClsTf = connPnl.addLabeled("JDBC Driver:", Controls.textField("Enter class name for JDBC driver"), 2);
 
-        jdbcUrlTf = connPnl.addLabeled("JDBC URL:", textField("JDBC URL of the database connection string"), 2);
+        jdbcUrlTf = connPnl.addLabeled("JDBC URL:", Controls.textField("JDBC URL of the database connection string"), 2);
 
         rdbmsCb.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Preset>() {
             @Override public void changed(ObservableValue<? extends Preset> val, Preset oldVal, Preset newVal) {
@@ -929,34 +794,33 @@ public class SchemaImportApp extends Application {
             }
         });
 
-        userTf = connPnl.addLabeled("User:", textField("User name"), 2);
+        userTf = connPnl.addLabeled("User:", Controls.textField("User name"), 2);
 
-        pwdTf = connPnl.addLabeled("Password:", passwordField("User password"), 2);
+        pwdTf = connPnl.addLabeled("Password:", Controls.passwordField("User password"), 2);
 
-        parseCb = connPnl.addLabeled("Parse:", comboBox("Type of tables to parse", "Tables only", "Tables and Views"), 2);
+        parseCb = connPnl.addLabeled("Parse:", Controls.comboBox("Type of tables to parse", "Tables only", "Tables and Views"), 2);
 
-        GridPaneEx schemaPnl = paneEx(5, 5, 5, 5);
+        GridPaneEx schemaPnl = Controls.paneEx(5, 5, 5, 5);
         schemaPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
         schemaPnl.addColumn();
 
-        schemaLst = schemaPnl.add(list("Select schemas to load", new SchemaCell()));
+        schemaLst = schemaPnl.add(Controls.list("Select schemas to load", new SchemaCell()));
 
         schemaPnl.wrap();
 
-        schemaPnl.add(button("Load schemas", "Load schemas for specified database", new EventHandler<ActionEvent>() {
-            @Override
-            public void handle(ActionEvent evt) {
+        schemaPnl.add(Controls.button("Load schemas", "Load schemas for specified database", new EventHandler<ActionEvent>() {
+            @Override public void handle(ActionEvent evt) {
                 loadSchemas();
             }
         }));
 
-        TitledPane titledPnl = connPnl.add(titledPane("Schemas", schemaPnl, false), 3);
+        TitledPane titledPnl = connPnl.add(Controls.titledPane("Schemas", schemaPnl, false), 3);
 
         titledPnl.setExpanded(true);
 
         GridPaneEx.setValignment(titledPnl, VPos.TOP);
 
-        connLayerPnl = stackPane(connPnl);
+        connLayerPnl = Controls.stackPane(connPnl);
 
         return connLayerPnl;
     }
@@ -1020,7 +884,7 @@ public class SchemaImportApp extends Application {
      * Create generate pane with controls.
      */
     private void createGeneratePane() {
-        genPnl = paneEx(10, 10, 0, 10);
+        genPnl = Controls.paneEx(10, 10, 0, 10);
 
         genPnl.addColumn();
         genPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
@@ -1029,10 +893,10 @@ public class SchemaImportApp extends Application {
         genPnl.addRow(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
         genPnl.addRows(7);
 
-        TableColumn<PojoDescriptor, Boolean> useCol = customColumn("Schema / Table", "use",
+        TableColumn<PojoDescriptor, Boolean> useCol = Controls.customColumn("Schema / Table", "use",
             "If checked then this table will be used for XML and POJOs generation", PojoDescriptorCell.cellFactory());
 
-        TableColumn<PojoDescriptor, String> keyClsCol = textColumn("Key Class Name", "keyClassName", "Key class name",
+        TableColumn<PojoDescriptor, String> keyClsCol = Controls.textColumn("Key Class Name", "keyClassName", "Key class name",
             new TextColumnValidator<PojoDescriptor>() {
                 @Override public boolean valid(PojoDescriptor rowVal, String newVal) {
                     boolean valid = checkClassName(rowVal, newVal, true);
@@ -1044,7 +908,7 @@ public class SchemaImportApp extends Application {
                 }
             });
 
-        TableColumn<PojoDescriptor, String> valClsCol = textColumn("Value Class Name", "valueClassName", "Value class name",
+        TableColumn<PojoDescriptor, String> valClsCol = Controls.textColumn("Value Class Name", "valueClassName", "Value class name",
             new TextColumnValidator<PojoDescriptor>() {
                 @Override public boolean valid(PojoDescriptor rowVal, String newVal) {
                     boolean valid = checkClassName(rowVal, newVal, false);
@@ -1056,26 +920,26 @@ public class SchemaImportApp extends Application {
                 }
             });
 
-        pojosTbl = tableView("Tables not found in database", useCol, keyClsCol, valClsCol);
+        pojosTbl = Controls.tableView("Tables not found in database", useCol, keyClsCol, valClsCol);
 
-        TableColumn<PojoField, Boolean> useFldCol = customColumn("Use", "use",
+        TableColumn<PojoField, Boolean> useFldCol = Controls.customColumn("Use", "use",
             "Check to use this field for XML and POJO generation\n" +
-            "Note that NOT NULL columns cannot be unchecked", PojoFieldUseCell.cellFactory());
+                "Note that NOT NULL columns cannot be unchecked", PojoFieldUseCell.cellFactory());
         useFldCol.setMinWidth(50);
         useFldCol.setMaxWidth(50);
 
-        TableColumn<PojoField, Boolean> keyCol = booleanColumn("Key", "key",
+        TableColumn<PojoField, Boolean> keyCol = Controls.booleanColumn("Key", "key",
             "Check to include this field into key object");
 
-        TableColumn<PojoField, Boolean> akCol = booleanColumn("AK", "affinityKey",
+        TableColumn<PojoField, Boolean> akCol = Controls.booleanColumn("AK", "affinityKey",
             "Check to annotate key filed with @AffinityKeyMapped annotation in generated POJO class\n" +
-            "Note that a class can have only ONE key field annotated with @AffinityKeyMapped annotation");
+                "Note that a class can have only ONE key field annotated with @AffinityKeyMapped annotation");
 
-        TableColumn<PojoField, String> dbNameCol = tableColumn("DB Name", "dbName", "Field name in database");
+        TableColumn<PojoField, String> dbNameCol = Controls.tableColumn("DB Name", "dbName", "Field name in database");
 
-        TableColumn<PojoField, String> dbTypeNameCol = tableColumn("DB Type", "dbTypeName", "Field type in database");
+        TableColumn<PojoField, String> dbTypeNameCol = Controls.tableColumn("DB Type", "dbTypeName", "Field type in database");
 
-        TableColumn<PojoField, String> javaNameCol = textColumn("Java Name", "javaName", "Field name in POJO class",
+        TableColumn<PojoField, String> javaNameCol = Controls.textColumn("Java Name", "javaName", "Field name in POJO class",
             new TextColumnValidator<PojoField>() {
                 @Override public boolean valid(PojoField rowVal, String newVal) {
                     if (newVal.trim().isEmpty()) {
@@ -1097,25 +961,25 @@ public class SchemaImportApp extends Application {
                 }
             });
 
-        TableColumn<PojoField, String> javaTypeNameCol = customColumn("Java Type", "javaTypeName",
+        TableColumn<PojoField, String> javaTypeNameCol = Controls.customColumn("Java Type", "javaTypeName",
             "Field java type in POJO class", JavaTypeCell.cellFactory());
 
-        fieldsTbl = tableView("Select table to see table columns",
+        fieldsTbl = Controls.tableView("Select table to see table columns",
             useFldCol, keyCol, akCol, dbNameCol, dbTypeNameCol, javaNameCol, javaTypeNameCol);
 
-        genPnl.add(splitPane(pojosTbl, fieldsTbl, 0.6), 3);
+        genPnl.add(Controls.splitPane(pojosTbl, fieldsTbl, 0.6), 3);
 
-        final GridPaneEx keyValPnl = paneEx(0, 0, 0, 0);
+        final GridPaneEx keyValPnl = Controls.paneEx(0, 0, 0, 0);
         keyValPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
         keyValPnl.addColumn();
         keyValPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
         keyValPnl.addColumn();
 
-        pkgTf = genPnl.addLabeled("Package:", textField("Package that will be used for POJOs generation"), 2);
+        pkgTf = genPnl.addLabeled("Package:", Controls.textField("Package that will be used for POJOs generation"), 2);
 
-        outFolderTf = genPnl.addLabeled("Output Folder:", textField("Output folder for XML and POJOs files"));
+        outFolderTf = genPnl.addLabeled("Output Folder:", Controls.textField("Output folder for XML and POJOs files"));
 
-        genPnl.add(button("...", "Select output folder", new EventHandler<ActionEvent>() {
+        genPnl.add(Controls.button("...", "Select output folder", new EventHandler<ActionEvent>() {
             @Override public void handle(ActionEvent evt) {
                 DirectoryChooser dc = new DirectoryChooser();
 
@@ -1136,30 +1000,30 @@ public class SchemaImportApp extends Application {
             }
         }));
 
-        pojoIncludeKeysCh = genPnl.add(checkBox("Include key fields into value POJOs",
+        pojoIncludeKeysCh = genPnl.add(Controls.checkBox("Include key fields into value POJOs",
             "If selected then include key fields into value object", true), 3);
 
-        pojoConstructorCh = genPnl.add(checkBox("Generate constructors for POJOs",
+        pojoConstructorCh = genPnl.add(Controls.checkBox("Generate constructors for POJOs",
             "If selected then generate empty and full constructors for POJOs", false), 3);
 
-        xmlSingleFileCh = genPnl.add(checkBox("Write all configurations to a single XML file",
+        xmlSingleFileCh = genPnl.add(Controls.checkBox("Write all configurations to a single XML file",
             "If selected then all configurations will be saved into the file 'ignite-type-metadata.xml'", true), 3);
 
-        GridPaneEx regexPnl = paneEx(5, 5, 5, 5);
+        GridPaneEx regexPnl = Controls.paneEx(5, 5, 5, 5);
         regexPnl.addColumn();
         regexPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
         regexPnl.addColumn();
         regexPnl.addColumn(100, 100, Double.MAX_VALUE, Priority.ALWAYS);
 
-        regexTf = regexPnl.addLabeled("  Regexp:", textField("Regular expression. For example: (\\w+)"));
+        regexTf = regexPnl.addLabeled("  Regexp:", Controls.textField("Regular expression. For example: (\\w+)"));
 
-        replaceTf = regexPnl.addLabeled("  Replace with:", textField("Replace text. For example: $1_SomeText"));
+        replaceTf = regexPnl.addLabeled("  Replace with:", Controls.textField("Replace text. For example: $1_SomeText"));
 
-        final ComboBox<String> replaceCb = regexPnl.addLabeled("  Replace:", comboBox("Replacement target",
+        final ComboBox<String> replaceCb = regexPnl.addLabeled("  Replace:", Controls.comboBox("Replacement target",
             "Key class names", "Value class names", "Java names"));
 
-        regexPnl.add(buttonsPane(Pos.CENTER_LEFT, false,
-            button("Rename Selected", "Replaces each substring of this string that matches the given regular expression" +
+        regexPnl.add(Controls.buttonsPane(Pos.CENTER_LEFT, false,
+            Controls.button("Rename Selected", "Replaces each substring of this string that matches the given regular expression" +
                     " with the given replacement",
                 new EventHandler<ActionEvent>() {
                     @Override public void handle(ActionEvent evt) {
@@ -1216,7 +1080,7 @@ public class SchemaImportApp extends Application {
                         }
                     }
                 }),
-            button("Reset Selected", "Revert changes for selected items to initial auto-generated values", new EventHandler<ActionEvent>() {
+            Controls.button("Reset Selected", "Revert changes for selected items to initial auto-generated values", new EventHandler<ActionEvent>() {
                 @Override public void handle(ActionEvent evt) {
                     String sel = replaceCb.getSelectionModel().getSelectedItem();
 
@@ -1312,10 +1176,10 @@ public class SchemaImportApp extends Application {
             }
         });
 
-        genPnl.add(titledPane("Rename \"Key class name\", \"Value class name\" or  \"Java name\" for selected tables",
+        genPnl.add(Controls.titledPane("Rename \"Key class name\", \"Value class name\" or  \"Java name\" for selected tables",
             regexPnl, true), 3);
 
-        genLayerPnl = stackPane(genPnl);
+        genLayerPnl = Controls.stackPane(genPnl);
     }
 
     /**
@@ -1548,7 +1412,7 @@ public class SchemaImportApp extends Application {
 
                 if (customPrefsFile == null)
                     log.log(Level.WARNING, "Failed to resolve path to file with custom preferences: " +
-                        customPrefsFile);
+                        customPrefsFileName);
                 else {
                     Properties customPrefs = new Properties();
 
@@ -1577,21 +1441,21 @@ public class SchemaImportApp extends Application {
         primaryStage.setTitle("Apache Ignite Auto Schema Import Utility");
 
         primaryStage.getIcons().addAll(
-            image("ignite", 16),
-            image("ignite", 24),
-            image("ignite", 32),
-            image("ignite", 48),
-            image("ignite", 64),
-            image("ignite", 128));
+            Controls.image("ignite", 16),
+            Controls.image("ignite", 24),
+            Controls.image("ignite", 32),
+            Controls.image("ignite", 48),
+            Controls.image("ignite", 64),
+            Controls.image("ignite", 128));
 
-        pi = progressIndicator(50);
+        pi = Controls.progressIndicator(50);
 
         createGeneratePane();
 
         hdrPane = createHeaderPane();
-        rootPane = borderPane(hdrPane, createConnectionPane(), createButtonsPane(), null, null);
+        rootPane = Controls.borderPane(hdrPane, createConnectionPane(), createButtonsPane(), null, null);
 
-        primaryStage.setScene(scene(rootPane));
+        primaryStage.setScene(Controls.scene(rootPane));
 
         primaryStage.setWidth(650);
         primaryStage.setMinWidth(650);
@@ -1709,37 +1573,49 @@ public class SchemaImportApp extends Application {
         savePreferences();
     }
 
-    /**
-     * Schema Import utility launcher.
-     *
-     * @param args Command line arguments passed to the application.
-     */
-    public static void main(String[] args) {
-        // Workaround for JavaFX ugly text AA.
-        System.setProperty("prism.lcdtext", "false");
-        System.setProperty("prism.text", "t2k");
+    /** Presets for database settings. */
+    private static class Preset {
+        /** Name in preferences. */
+        private String pref;
 
-        // Workaround for AWT + JavaFX: we should initialize AWT before JavaFX.
-        java.awt.Toolkit.getDefaultToolkit();
+        /** RDBMS name to show on screen. */
+        private String name;
 
-        // Workaround for JavaFX + Mac OS dock icon.
-        if (System.getProperty("os.name").toLowerCase().contains("mac os")) {
-            System.setProperty("javafx.macosx.embedded", "true");
+        /** Path to JDBC driver jar. */
+        private String jar;
 
-            try {
-                Class<?> appCls = Class.forName("com.apple.eawt.Application");
+        /** JDBC driver class name. */
+        private String drv;
 
-                Object osxApp = appCls.getDeclaredMethod("getApplication").invoke(null);
+        /** JDBC URL. */
+        private String url;
 
-                appCls.getDeclaredMethod("setDockIconImage", java.awt.Image.class)
-                    .invoke(osxApp, fromFXImage(image("ignite", 128), null));
-            }
-            catch (Exception ignore) {
-                // No-op.
-            }
+        /** User name. */
+        private String user;
+
+        /**
+         * Preset constructor.
+         *
+         * @param pref Name in preferences.
+         * @param name RDBMS name to show on screen.
+         * @param jar Path to JDBC driver jar..
+         * @param drv JDBC driver class name.
+         * @param url JDBC URL.
+         * @param user User name.
+         */
+        Preset(String pref, String name, String jar, String drv, String url, String user) {
+            this.pref = pref;
+            this.name = name;
+            this.jar = jar;
+            this.drv = drv;
+            this.url = url;
+            this.user = user;
         }
 
-        launch(args);
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return name;
+        }
     }
 
     /**
@@ -1750,19 +1626,6 @@ public class SchemaImportApp extends Application {
         private final ComboBox<String> comboBox;
 
         /**
-         * Creates a ComboBox cell factory for use in TableColumn controls.
-         *
-         * @return Cell factory for cell with java types combobox.
-         */
-        public static Callback<TableColumn<PojoField, String>, TableCell<PojoField, String>> cellFactory() {
-            return new Callback<TableColumn<PojoField, String>, TableCell<PojoField, String>>() {
-                @Override public TableCell<PojoField, String> call(TableColumn<PojoField, String> col) {
-                    return new JavaTypeCell();
-                }
-            };
-        }
-
-        /**
          * Default constructor.
          */
         private JavaTypeCell() {
@@ -1778,6 +1641,19 @@ public class SchemaImportApp extends Application {
             getStyleClass().add("combo-box-table-cell");
         }
 
+        /**
+         * Creates a ComboBox cell factory for use in TableColumn controls.
+         *
+         * @return Cell factory for cell with java types combobox.
+         */
+        public static Callback<TableColumn<PojoField, String>, TableCell<PojoField, String>> cellFactory() {
+            return new Callback<TableColumn<PojoField, String>, TableCell<PojoField, String>>() {
+                @Override public TableCell<PojoField, String> call(TableColumn<PojoField, String> col) {
+                    return new JavaTypeCell();
+                }
+            };
+        }
+
         /** {@inheritDoc} */
         @Override public void startEdit() {
             if (comboBox.getItems().size() > 1) {
@@ -1827,8 +1703,8 @@ public class SchemaImportApp extends Application {
      * Special list view cell to select loaded schemas.
      */
     private static class SchemaCell implements Callback<SchemaDescriptor, ObservableValue<Boolean>> {
-        @Override
-        public ObservableValue<Boolean> call(SchemaDescriptor item) {
+        /** {@inheritDoc} */
+        @Override public ObservableValue<Boolean> call(SchemaDescriptor item) {
             return item.selected();
         }
     }
@@ -1837,6 +1713,11 @@ public class SchemaImportApp extends Application {
      * Special table cell to select schema or table.
      */
     private static class PojoDescriptorCell extends TableCell<PojoDescriptor, Boolean> {
+        /** Previous POJO bound to cell. */
+        private PojoDescriptor prevPojo;
+        /** Previous cell graphic. */
+        private Pane prevGraphic;
+
         /**
          * Creates a ComboBox cell factory for use in TableColumn controls.
          *
@@ -1850,12 +1731,6 @@ public class SchemaImportApp extends Application {
             };
         }
 
-        /** Previous POJO bound to cell. */
-        private PojoDescriptor prevPojo;
-
-        /** Previous cell graphic. */
-        private Pane prevGraphic;
-
         /** {@inheritDoc} */
         @Override public void updateItem(Boolean item, boolean empty) {
             super.updateItem(item, empty);
@@ -1898,6 +1773,11 @@ public class SchemaImportApp extends Application {
      * Special table cell to select &quot;used&quot; fields for code generation.
      */
     private static class PojoFieldUseCell extends TableCell<PojoField, Boolean> {
+        /** Previous POJO field bound to cell. */
+        private PojoField prevField;
+        /** Previous cell graphic. */
+        private CheckBox prevGraphic;
+
         /**
          * Creates a ComboBox cell factory for use in TableColumn controls.
          *
@@ -1911,12 +1791,6 @@ public class SchemaImportApp extends Application {
             };
         }
 
-        /** Previous POJO field bound to cell. */
-        private PojoField prevField;
-
-        /** Previous cell graphic. */
-        private CheckBox prevGraphic;
-
         /** {@inheritDoc} */
         @Override public void updateItem(Boolean item, boolean empty) {
             super.updateItem(item, empty);
@@ -1947,4 +1821,4 @@ public class SchemaImportApp extends Application {
             }
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/parent/pom.xml
----------------------------------------------------------------------
diff --git a/parent/pom.xml b/parent/pom.xml
index ba44c85..5a1d385 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -21,8 +21,8 @@
     POM file.
 -->
 <project
-        xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
@@ -708,6 +708,7 @@
                                         <exclude>dev-tools/.gradle/**/*</exclude>
                                         <exclude>dev-tools/gradle/wrapper/**/*</exclude>
                                         <exclude>dev-tools/gradlew</exclude>
+                                        <exclude>src/main/js/package.json</exclude>
                                         <!--shmem-->
                                         <exclude>ipc/shmem/**/Makefile.in</exclude><!--auto generated files-->
                                         <exclude>ipc/shmem/**/Makefile</exclude><!--auto generated files-->

http://git-wip-us.apache.org/repos/asf/ignite/blob/bd50bdf8/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 208dbbc..a2e61cd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
 <!--
     POM file.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
@@ -660,6 +660,7 @@
             </activation>
             <modules>
                 <module>modules/schema-import</module>
+                <module>modules/schema-import-db</module>
             </modules>
             <build>
                 <plugins>
@@ -716,6 +717,7 @@
             </activation>
             <modules>
                 <module>modules/schema-import</module>
+                <module>modules/schema-import-db</module>
             </modules>
             <build>
                 <plugins>
@@ -764,6 +766,42 @@
         </profile>
 
         <profile>
+            <id>control-center</id>
+            <modules>
+                <module>modules/control-center-agent</module>
+                <module>modules/control-center-web</module>
+                <module>modules/schema-import-db</module>
+            </modules>
+        </profile>
+
+        <profile>
+            <id>ignite-npm</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>compile</phase>
+                                <goals>
+                                    <goal>exec</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                        <configuration>
+                            <executable>npm</executable>
+                            <arguments>
+                                <argument>publish</argument>
+                                <argument>modules/nodejs/src/main/js</argument>
+                            </arguments>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        
+        <profile>
             <id>update-versions</id>
             <!-- updates dotnet & cpp versions -->
             <build>


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/clusters-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/clusters-controller.js b/modules/control-center-web/src/main/js/controllers/clusters-controller.js
new file mode 100644
index 0000000..3b95a2e
--- /dev/null
+++ b/modules/control-center-web/src/main/js/controllers/clusters-controller.js
@@ -0,0 +1,560 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Controller for Clusters screen.
+consoleModule.controller('clustersController', [
+    '$scope', '$controller', '$http', '$timeout', '$common', '$focus', '$confirm', '$message', '$clone', '$table', '$preview', '$loading', '$unsavedChangesGuard',
+    function ($scope, $controller, $http, $timeout, $common, $focus, $confirm, $message, $clone, $table, $preview, $loading, $unsavedChangesGuard) {
+        $unsavedChangesGuard.install($scope);
+
+        // Initialize the super class and extend it.
+        angular.extend(this, $controller('save-remove', {$scope: $scope}));
+
+        $scope.ui = $common.formUI();
+
+        $scope.showMoreInfo = $message.message;
+
+        $scope.joinTip = $common.joinTip;
+        $scope.getModel = $common.getModel;
+        $scope.compactJavaName = $common.compactJavaName;
+        $scope.saveBtnTipText = $common.saveBtnTipText;
+
+        $scope.tableReset = $table.tableReset;
+        $scope.tableNewItem = $table.tableNewItem;
+        $scope.tableNewItemActive = $table.tableNewItemActive;
+        $scope.tableEditing = $table.tableEditing;
+        $scope.tableStartEdit = $table.tableStartEdit;
+        $scope.tableRemove = function (item, field, index) {
+            $table.tableRemove(item, field, index);
+        };
+
+        $scope.tableSimpleSave = $table.tableSimpleSave;
+        $scope.tableSimpleSaveVisible = $table.tableSimpleSaveVisible;
+        $scope.tableSimpleUp = $table.tableSimpleUp;
+        $scope.tableSimpleDown = $table.tableSimpleDown;
+        $scope.tableSimpleDownVisible = $table.tableSimpleDownVisible;
+
+        var previews = [];
+
+        $scope.previewInit = function (preview) {
+            previews.push(preview);
+
+            $preview.previewInit(preview);
+        };
+
+        $scope.trustManagersConfigured = function() {
+            return $scope.backupItem.sslEnabled && $common.isDefined($scope.backupItem.sslContextFactory)
+                && !$common.isEmptyArray($scope.backupItem.sslContextFactory.trustManagers)
+        };
+
+        $scope.previewChanged = $preview.previewChanged;
+
+        $scope.hidePopover = $common.hidePopover;
+
+        var showPopoverMessage = $common.showPopoverMessage;
+
+        $scope.discoveries = [
+            {value: 'Vm', label: 'static IPs'},
+            {value: 'Multicast', label: 'multicast'},
+            {value: 'S3', label: 'AWS S3'},
+            {value: 'Cloud', label: 'apache jclouds'},
+            {value: 'GoogleStorage', label: 'google cloud storage'},
+            {value: 'Jdbc', label: 'JDBC'},
+            {value: 'SharedFs', label: 'shared filesystem'}
+        ];
+
+        $scope.swapSpaceSpis = [
+            {value: 'FileSwapSpaceSpi', label: 'File-based swap'},
+            {value: undefined, label: 'Not set'}
+        ];
+
+        $scope.events = [];
+
+        for (var eventGroupName in $dataStructures.EVENT_GROUPS) {
+            if ($dataStructures.EVENT_GROUPS.hasOwnProperty(eventGroupName)) {
+                $scope.events.push({value: eventGroupName, label: eventGroupName});
+            }
+        }
+
+        $scope.preview = {
+            general: {xml: '', java: '', allDefaults: true},
+            atomics: {xml: '', java: '', allDefaults: true},
+            communication: {xml: '', java: '', allDefaults: true},
+            deployment: {xml: '', java: '', allDefaults: true},
+            discovery: {xml: '', java: '', allDefaults: true},
+            events: {xml: '', java: '', allDefaults: true},
+            marshaller: {xml: '', java: '', allDefaults: true},
+            metrics: {xml: '', java: '', allDefaults: true},
+            p2p: {xml: '', java: '', allDefaults: true},
+            swap: {xml: '', java: '', allDefaults: true},
+            time: {xml: '', java: '', allDefaults: true},
+            pools: {xml: '', java: '', allDefaults: true},
+            transactions: {xml: '', java: '', allDefaults: true},
+            sslConfiguration: {xml: '', java: '', allDefaults: true}
+        };
+
+        $scope.cacheModes = $common.mkOptions(['LOCAL', 'REPLICATED', 'PARTITIONED']);
+
+        $scope.deploymentModes = $common.mkOptions(['PRIVATE', 'ISOLATED', 'SHARED', 'CONTINUOUS']);
+
+        $scope.transactionConcurrency = $common.mkOptions(['OPTIMISTIC', 'PESSIMISTIC']);
+
+        $scope.transactionIsolation = $common.mkOptions(['READ_COMMITTED', 'REPEATABLE_READ', 'SERIALIZABLE']);
+
+        $scope.segmentationPolicy = $common.mkOptions(['RESTART_JVM', 'STOP', 'NOOP']);
+
+        $scope.marshallers = $common.mkOptions(['OptimizedMarshaller', 'JdkMarshaller']);
+
+        $scope.sslKeyAlgorithms = ['SumX509', 'X509'];
+
+        $scope.sslStoreType = ['JKS', 'PCKS11', 'PCKS12'];
+
+        $scope.sslProtocols = ['TSL', 'SSL'];
+
+        $scope.toggleExpanded = function () {
+            $scope.ui.expanded = !$scope.ui.expanded;
+
+            $common.hidePopover();
+        };
+
+        $scope.panels = {activePanels: [0]};
+
+        var simpleTables = {
+            addresses: {msg: 'Such IP address already exists!', id: 'IpAddress'},
+            regions: {msg: 'Such region already exists!', id: 'Region'},
+            zones: {msg: 'Such zone already exists!', id: 'Zone'},
+            peerClassLoadingLocalClassPathExclude: {msg: 'Such package already exists!', id: 'PeerClsPathExclude'},
+            trustManagers: {msg: 'Such trust manager already exists!', id: 'trustManagers'}
+        };
+
+        $scope.tableSimpleValid = function (item, field, val, index) {
+            var model = $common.getModel(item, field)[field.model];
+
+            if (field.model == 'trustManagers' && !$common.isValidJavaClass('Trust manager', val, false,  $table.tableFieldId(index, 'trustManagers'), false))
+                return false;
+
+            if ($common.isDefined(model)) {
+                var idx = _.indexOf(model, val);
+
+                // Found duplicate.
+                if (idx >= 0 && idx != index) {
+                    var simpleTable = simpleTables[field.model];
+
+                    if (simpleTable) {
+                        $common.showPopoverMessage(null, null, $table.tableFieldId(index, 'trustManagers'), simpleTable.msg);
+
+                        return $table.tableFocusInvalidField(index, simpleTable.id);
+                    }
+                }
+            }
+
+            return true;
+        };
+
+        $scope.clusters = [];
+
+        function selectFirstItem() {
+            if ($scope.clusters.length > 0)
+                $scope.selectItem($scope.clusters[0]);
+        }
+
+        $loading.start('loadingClustersScreen');
+
+        // When landing on the page, get clusters and show them.
+        $http.post('clusters/list')
+            .success(function (data) {
+                $scope.spaces = data.spaces;
+                $scope.clusters = data.clusters;
+                $scope.caches = _.map(data.caches, function (cache) {
+                    return {value: cache._id, label: cache.name, cache: cache};
+                });
+
+                // Load page descriptor.
+                $http.get('/models/clusters.json')
+                    .success(function (data) {
+                        $scope.screenTip = data.screenTip;
+                        $scope.moreInfo = data.moreInfo;
+                        $scope.general = data.general;
+                        $scope.advanced = data.advanced;
+
+                        $scope.ui.addGroups(data.general, data.advanced);
+
+                        if ($common.getQueryVariable('new'))
+                            $scope.createItem();
+                        else {
+                            var lastSelectedCluster = angular.fromJson(sessionStorage.lastSelectedCluster);
+
+                            if (lastSelectedCluster) {
+                                var idx = _.findIndex($scope.clusters, function (cluster) {
+                                    return cluster._id == lastSelectedCluster;
+                                });
+
+                                if (idx >= 0)
+                                    $scope.selectItem($scope.clusters[idx]);
+                                else {
+                                    sessionStorage.removeItem('lastSelectedCluster');
+
+                                    selectFirstItem();
+                                }
+                            }
+                            else
+                                selectFirstItem();
+                        }
+
+                        $scope.$watch('backupItem', function (val) {
+                            if (val) {
+                                var clusterCaches = _.reduce($scope.caches, function(caches, cache){
+                                    if (_.contains(val.caches, cache.value)) {
+                                        caches.push(cache.cache);
+                                    }
+
+                                    return caches;
+                                }, []);
+
+                                var srcItem = $scope.selectedItem ? $scope.selectedItem : prepareNewItem();
+
+                                $scope.ui.checkDirty(val, srcItem);
+
+                                $scope.preview.general.xml = $generatorXml.clusterCaches(clusterCaches, $generatorXml.clusterGeneral(val)).asString();
+                                $scope.preview.general.java = $generatorJava.clusterCaches(clusterCaches, $generatorJava.clusterGeneral(val)).asString();
+                                $scope.preview.general.allDefaults = $common.isEmptyString($scope.preview.general.xml);
+
+                                $scope.preview.atomics.xml = $generatorXml.clusterAtomics(val).asString();
+                                $scope.preview.atomics.java = $generatorJava.clusterAtomics(val).asString();
+                                $scope.preview.atomics.allDefaults = $common.isEmptyString($scope.preview.atomics.xml);
+
+                                $scope.preview.communication.xml = $generatorXml.clusterCommunication(val).asString();
+                                $scope.preview.communication.java = $generatorJava.clusterCommunication(val).asString();
+                                $scope.preview.communication.allDefaults = $common.isEmptyString($scope.preview.communication.xml);
+
+                                $scope.preview.deployment.xml = $generatorXml.clusterDeployment(val).asString();
+                                $scope.preview.deployment.java = $generatorJava.clusterDeployment(val).asString();
+                                $scope.preview.deployment.allDefaults = $common.isEmptyString($scope.preview.deployment.xml);
+
+                                $scope.preview.discovery.xml = $generatorXml.clusterDiscovery(val.discovery).asString();
+                                $scope.preview.discovery.java = $generatorJava.clusterDiscovery(val.discovery).asString();
+                                $scope.preview.discovery.allDefaults = $common.isEmptyString($scope.preview.discovery.xml);
+
+                                $scope.preview.events.xml = $generatorXml.clusterEvents(val).asString();
+                                $scope.preview.events.java = $generatorJava.clusterEvents(val).asString();
+                                $scope.preview.events.allDefaults = $common.isEmptyString($scope.preview.events.xml);
+
+                                $scope.preview.marshaller.xml = $generatorXml.clusterMarshaller(val).asString();
+                                $scope.preview.marshaller.java = $generatorJava.clusterMarshaller(val).asString();
+                                $scope.preview.marshaller.allDefaults = $common.isEmptyString($scope.preview.marshaller.xml);
+
+                                $scope.preview.metrics.xml = $generatorXml.clusterMetrics(val).asString();
+                                $scope.preview.metrics.java = $generatorJava.clusterMetrics(val).asString();
+                                $scope.preview.metrics.allDefaults = $common.isEmptyString($scope.preview.metrics.xml);
+
+                                $scope.preview.p2p.xml = $generatorXml.clusterP2p(val).asString();
+                                $scope.preview.p2p.java = $generatorJava.clusterP2p(val).asString();
+                                $scope.preview.p2p.allDefaults = $common.isEmptyString($scope.preview.p2p.xml);
+
+                                $scope.preview.swap.xml = $generatorXml.clusterSwap(val).asString();
+                                $scope.preview.swap.java = $generatorJava.clusterSwap(val).asString();
+                                $scope.preview.swap.allDefaults = $common.isEmptyString($scope.preview.swap.xml);
+
+                                $scope.preview.time.xml = $generatorXml.clusterTime(val).asString();
+                                $scope.preview.time.java = $generatorJava.clusterTime(val).asString();
+                                $scope.preview.time.allDefaults = $common.isEmptyString($scope.preview.time.xml);
+
+                                $scope.preview.pools.xml = $generatorXml.clusterPools(val).asString();
+                                $scope.preview.pools.java = $generatorJava.clusterPools(val).asString();
+                                $scope.preview.pools.allDefaults = $common.isEmptyString($scope.preview.pools.xml);
+
+                                $scope.preview.transactions.xml = $generatorXml.clusterTransactions(val).asString();
+                                $scope.preview.transactions.java = $generatorJava.clusterTransactions(val).asString();
+                                $scope.preview.transactions.allDefaults = $common.isEmptyString($scope.preview.transactions.xml);
+
+                                $scope.preview.sslConfiguration.xml = $generatorXml.clusterSsl(val).asString();
+                                $scope.preview.sslConfiguration.java = $generatorJava.clusterSsl(val).asString();
+                                $scope.preview.sslConfiguration.allDefaults = $common.isEmptyString($scope.preview.sslConfiguration.xml);
+                            }
+                        }, true);
+                    })
+                    .error(function (errMsg) {
+                        $common.showError(errMsg);
+                    });
+            })
+            .error(function (errMsg) {
+                $common.showError(errMsg);
+            })
+            .finally(function () {
+                $scope.ui.ready = true;
+                $loading.finish('loadingClustersScreen');
+            });
+
+        $scope.selectItem = function (item, backup) {
+            function selectItem() {
+                $table.tableReset();
+
+                $scope.selectedItem = angular.copy(item);
+
+                try {
+                    if (item && item._id)
+                        sessionStorage.lastSelectedCluster = angular.toJson(item._id);
+                    else
+                        sessionStorage.removeItem('lastSelectedCluster');
+                }
+                catch (error) { }
+
+                _.forEach(previews, function(preview) {
+                    preview.attractAttention = false;
+                });
+
+                if (backup)
+                    $scope.backupItem = backup;
+                else if (item)
+                    $scope.backupItem = angular.copy(item);
+                else
+                    $scope.backupItem = undefined;
+            }
+
+            $common.confirmUnsavedChanges($scope.ui.isDirty(), selectItem);
+
+            $scope.ui.formTitle = $common.isDefined($scope.backupItem) && $scope.backupItem._id ?
+                'Selected cluster: ' + $scope.backupItem.name : 'New cluster';
+        };
+
+        function prepareNewItem() {
+            var newItem = {
+                discovery: {kind: 'Multicast', Vm: {addresses: ['127.0.0.1:47500..47510']}, Multicast: {}},
+                deploymentMode: 'SHARED'
+            };
+
+            newItem.caches = [];
+            newItem.space = $scope.spaces[0]._id;
+
+            return newItem;
+        }
+
+        // Add new cluster.
+        $scope.createItem = function () {
+            $table.tableReset();
+
+            $timeout(function () {
+                $common.ensureActivePanel($scope.panels, "general", 'clusterName');
+            });
+
+            $scope.selectItem(undefined, prepareNewItem());
+        };
+
+        $scope.indexOfCache = function (cacheId) {
+            return _.findIndex($scope.caches, function (cache) {
+                return cache.value == cacheId;
+            });
+        };
+
+        // Check cluster logical consistency.
+        function validate(item) {
+            if ($common.isEmptyString(item.name))
+                return showPopoverMessage($scope.panels, 'general', 'clusterName', 'Name should not be empty');
+
+            var d = item.discovery;
+
+            if (!$common.isEmptyString(d.addressResolver) && !$common.isValidJavaClass('Address resolver', d.addressResolver, false, 'addressResolver', false, $scope.panels, 'discovery'))
+                return false;
+
+            if (!$common.isEmptyString(d.listener) && !$common.isValidJavaClass('Discovery listener', d.listener, false, 'listener', false, $scope.panels, 'discovery'))
+                return false;
+
+            if (!$common.isEmptyString(d.dataExchange) && !$common.isValidJavaClass('Data exchange', d.dataExchange, false, 'dataExchange', false, $scope.panels, 'discovery'))
+                return false;
+
+            if (!$common.isEmptyString(d.metricsProvider) && !$common.isValidJavaClass('Metrics provider', d.metricsProvider, false, 'metricsProvider', false, $scope.panels, 'discovery'))
+                return false;
+
+            if (!$common.isEmptyString(d.authenticator) && !$common.isValidJavaClass('Node authenticator', d.authenticator, false, 'authenticator', false, $scope.panels, 'discovery'))
+                return false;
+
+            if (item.discovery.kind == 'Vm' && item.discovery.Vm.addresses.length == 0)
+                return showPopoverMessage($scope.panels, 'general', 'addresses', 'Addresses are not specified');
+
+            if (item.discovery.kind == 'S3' && $common.isEmptyString(item.discovery.S3.bucketName))
+                return showPopoverMessage($scope.panels, 'general', 'bucketName', 'Bucket name should not be empty');
+
+            if (item.discovery.kind == 'Cloud') {
+                if ($common.isEmptyString(item.discovery.Cloud.identity))
+                    return showPopoverMessage($scope.panels, 'general', 'identity', 'Identity should not be empty');
+
+                if ($common.isEmptyString(item.discovery.Cloud.provider))
+                    return showPopoverMessage($scope.panels, 'general', 'provider', 'Provider should not be empty');
+            }
+
+            if (item.discovery.kind == 'GoogleStorage') {
+                if ($common.isEmptyString(item.discovery.GoogleStorage.projectName))
+                    return showPopoverMessage($scope.panels, 'general', 'projectName', 'Project name should not be empty');
+
+                if ($common.isEmptyString(item.discovery.GoogleStorage.bucketName))
+                    return showPopoverMessage($scope.panels, 'general', 'bucketName', 'Bucket name should not be empty');
+
+                if ($common.isEmptyString(item.discovery.GoogleStorage.serviceAccountP12FilePath))
+                    return showPopoverMessage($scope.panels, 'general', 'serviceAccountP12FilePath', 'Private key path should not be empty');
+
+                if ($common.isEmptyString(item.discovery.GoogleStorage.serviceAccountId))
+                    return showPopoverMessage($scope.panels, 'general', 'serviceAccountId', 'Account ID should not be empty');
+            }
+
+            if (item.sslEnabled) {
+                if (!$common.isDefined(item.sslContextFactory)
+                    || $common.isEmptyString(item.sslContextFactory.keyStoreFilePath))
+                    return showPopoverMessage($scope.panels, 'sslConfiguration', 'keyStoreFilePath', 'Key store file should not be empty');
+
+                if ($common.isEmptyString(item.sslContextFactory.trustStoreFilePath) && $common.isEmptyArray(item.sslContextFactory.trustManagers))
+                    return showPopoverMessage($scope.panels, 'sslConfiguration', 'sslConfiguration-title', 'Trust storage file or managers should be configured');
+            }
+
+            if (!item.swapSpaceSpi || !item.swapSpaceSpi.kind && item.caches) {
+                for (var i = 0; i < item.caches.length; i++) {
+                    var idx = $scope.indexOfCache(item.caches[i]);
+
+                    if (idx >= 0) {
+                        var cache = $scope.caches[idx];
+
+                        if (cache.cache.swapEnabled) {
+                            $scope.ui.expanded = true;
+
+                            return showPopoverMessage($scope.panels, 'swap', 'swapSpaceSpi',
+                                'Swap space SPI is not configured, but cache "' + cache.label + '" configured to use swap!');
+                        }
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        // Save cluster in database.
+        function save(item) {
+            $http.post('clusters/save', item)
+                .success(function (_id) {
+                    $scope.ui.markPristine();
+
+                    var idx = _.findIndex($scope.clusters, function (cluster) {
+                        return cluster._id == _id;
+                    });
+
+                    if (idx >= 0)
+                        angular.extend($scope.clusters[idx], item);
+                    else {
+                        item._id = _id;
+                        $scope.clusters.push(item);
+                    }
+
+                    $scope.selectItem(item);
+
+                    $common.showInfo('Cluster "' + item.name + '" saved.');
+                })
+                .error(function (errMsg) {
+                    $common.showError(errMsg);
+                });
+        }
+
+        // Save cluster.
+        $scope.saveItem = function () {
+            $table.tableReset();
+
+            var item = $scope.backupItem;
+
+            if (validate(item))
+                save(item);
+        };
+
+        // Copy cluster with new name.
+        $scope.cloneItem = function () {
+            $table.tableReset();
+
+            if (validate($scope.backupItem))
+                $clone.confirm($scope.backupItem.name).then(function (newName) {
+                    var item = angular.copy($scope.backupItem);
+
+                    item._id = undefined;
+                    item.name = newName;
+
+                    save(item);
+                });
+        };
+
+        // Remove cluster from db.
+        $scope.removeItem = function () {
+            $table.tableReset();
+
+            var selectedItem = $scope.selectedItem;
+
+            $confirm.confirm('Are you sure you want to remove cluster: "' + selectedItem.name + '"?')
+                .then(function () {
+                        var _id = selectedItem._id;
+
+                        $http.post('clusters/remove', {_id: _id})
+                            .success(function () {
+                                $common.showInfo('Cluster has been removed: ' + selectedItem.name);
+
+                                var clusters = $scope.clusters;
+
+                                var idx = _.findIndex(clusters, function (cluster) {
+                                    return cluster._id == _id;
+                                });
+
+                                if (idx >= 0) {
+                                    clusters.splice(idx, 1);
+
+                                    if (clusters.length > 0)
+                                        $scope.selectItem(clusters[0]);
+                                    else
+                                        $scope.selectItem(undefined, undefined);
+                                }
+                            })
+                            .error(function (errMsg) {
+                                $common.showError(errMsg);
+                            });
+                });
+        };
+
+        // Remove all clusters from db.
+        $scope.removeAllItems = function () {
+            $table.tableReset();
+
+            $confirm.confirm('Are you sure you want to remove all clusters?')
+                .then(function () {
+                        $http.post('clusters/remove/all')
+                            .success(function () {
+                                $common.showInfo('All clusters have been removed');
+
+                                $scope.clusters = [];
+
+                                $scope.selectItem(undefined, undefined);
+                            })
+                            .error(function (errMsg) {
+                                $common.showError(errMsg);
+                            });
+                });
+        };
+
+        $scope.resetItem = function (group) {
+            var resetTo = $scope.selectedItem;
+
+            if (!$common.isDefined(resetTo))
+                resetTo = prepareNewItem();
+
+            $common.resetItem($scope.backupItem, resetTo, $scope.general, group);
+            $common.resetItem($scope.backupItem, resetTo, $scope.advanced, group);
+        }
+    }]
+);


[18/18] ignite git commit: IGNITE-843 Fixed after merge

Posted by an...@apache.org.
IGNITE-843 Fixed after merge


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

Branch: refs/heads/ignite-843-rc1
Commit: 4331605284ac448e55b7855e068786b588439289
Parents: bd50bdf
Author: Andrey <an...@gridgain.com>
Authored: Tue Oct 13 10:10:15 2015 +0700
Committer: Andrey <an...@gridgain.com>
Committed: Tue Oct 13 10:18:22 2015 +0700

----------------------------------------------------------------------
 .../assembly/release-control-center-agent.xml   |  32 +++++++++----------
 modules/control-center-agent/pom.xml            |  32 +++++++++----------
 .../apache/ignite/agent/AgentConfiguration.java |  26 +++++++--------
 .../org/apache/ignite/agent/AgentLauncher.java  |  26 +++++++--------
 .../ignite/agent/AgentLoggingConfigurator.java  |  26 +++++++--------
 .../org/apache/ignite/agent/AgentSocket.java    |  26 +++++++--------
 .../org/apache/ignite/agent/AgentUtils.java     |  26 +++++++--------
 .../handlers/DatabaseMetadataExtractor.java     |  26 +++++++--------
 .../ignite/agent/handlers/RestExecutor.java     |  26 +++++++--------
 .../org/apache/ignite/agent/remote/Remote.java  |  26 +++++++--------
 .../ignite/agent/remote/RemoteHandler.java      |  26 +++++++--------
 .../ignite/agent/remote/WebSocketSender.java    |  26 +++++++--------
 .../agent/testdrive/AgentMetadataTestDrive.java |  26 +++++++--------
 .../agent/testdrive/AgentSqlTestDrive.java      |  26 +++++++--------
 .../ignite/agent/testdrive/model/Car.java       |  26 +++++++--------
 .../ignite/agent/testdrive/model/CarKey.java    |  26 +++++++--------
 .../ignite/agent/testdrive/model/Country.java   |  26 +++++++--------
 .../agent/testdrive/model/CountryKey.java       |  26 +++++++--------
 .../agent/testdrive/model/Department.java       |  26 +++++++--------
 .../agent/testdrive/model/DepartmentKey.java    |  26 +++++++--------
 .../ignite/agent/testdrive/model/Employee.java  |  26 +++++++--------
 .../agent/testdrive/model/EmployeeKey.java      |  26 +++++++--------
 .../ignite/agent/testdrive/model/Parking.java   |  26 +++++++--------
 .../agent/testdrive/model/ParkingKey.java       |  26 +++++++--------
 .../src/main/resources/logging.properties       |  28 +++++++---------
 modules/control-center-web/pom.xml              |  32 +++++++++----------
 .../src/main/js/agents/agent-manager.js         |  26 +++++++--------
 .../src/main/js/agents/agent-server.js          |  26 +++++++--------
 modules/control-center-web/src/main/js/app.js   |  26 +++++++--------
 modules/control-center-web/src/main/js/bin/www  |  19 -----------
 .../src/main/js/controllers/admin-controller.js |  26 +++++++--------
 .../main/js/controllers/caches-controller.js    |  26 +++++++--------
 .../main/js/controllers/clusters-controller.js  |  26 +++++++--------
 .../src/main/js/controllers/common-module.js    |  26 +++++++--------
 .../main/js/controllers/metadata-controller.js  |  26 +++++++--------
 .../main/js/controllers/profile-controller.js   |  26 +++++++--------
 .../src/main/js/controllers/sql-controller.js   |  26 +++++++--------
 .../main/js/controllers/summary-controller.js   |  26 +++++++--------
 modules/control-center-web/src/main/js/db.js    |  26 +++++++--------
 .../src/main/js/helpers/common-utils.js         |  26 +++++++--------
 .../src/main/js/helpers/configuration-loader.js |  26 +++++++--------
 .../src/main/js/helpers/data-structures.js      |  26 +++++++--------
 .../src/main/js/public/favicon.ico              | Bin 0 -> 1150 bytes
 .../src/main/js/public/images/cache.png         | Bin 0 -> 143060 bytes
 .../src/main/js/public/images/cluster.png       | Bin 0 -> 279519 bytes
 .../src/main/js/public/images/docker.png        | Bin 0 -> 994 bytes
 .../src/main/js/public/images/java.png          | Bin 0 -> 170 bytes
 .../src/main/js/public/images/logo.png          | Bin 0 -> 8148 bytes
 .../src/main/js/public/images/metadata.png      | Bin 0 -> 451859 bytes
 .../src/main/js/public/images/query-chart.png   | Bin 0 -> 290052 bytes
 .../main/js/public/images/query-metadata.png    | Bin 0 -> 308360 bytes
 .../src/main/js/public/images/query-table.png   | Bin 0 -> 275475 bytes
 .../src/main/js/public/images/summary.png       | Bin 0 -> 294358 bytes
 .../src/main/js/public/images/xml.png           | Bin 0 -> 232 bytes
 .../public/stylesheets/_bootstrap-custom.scss   |  26 +++++++--------
 .../stylesheets/_bootstrap-variables.scss       |  26 +++++++--------
 .../src/main/js/public/stylesheets/style.scss   |  26 +++++++--------
 .../src/main/js/routes/admin.js                 |  26 +++++++--------
 .../src/main/js/routes/agent.js                 |  26 +++++++--------
 .../src/main/js/routes/caches.js                |  26 +++++++--------
 .../src/main/js/routes/clusters.js              |  26 +++++++--------
 .../js/routes/generator/generator-common.js     |  26 +++++++--------
 .../js/routes/generator/generator-docker.js     |  26 +++++++--------
 .../main/js/routes/generator/generator-java.js  |  26 +++++++--------
 .../js/routes/generator/generator-properties.js |  26 +++++++--------
 .../main/js/routes/generator/generator-xml.js   |  26 +++++++--------
 .../src/main/js/routes/metadata.js              |  26 +++++++--------
 .../src/main/js/routes/notebooks.js             |  26 +++++++--------
 .../src/main/js/routes/presets.js               |  26 +++++++--------
 .../src/main/js/routes/profile.js               |  26 +++++++--------
 .../src/main/js/routes/public.js                |  26 +++++++--------
 .../src/main/js/routes/sql.js                   |  26 +++++++--------
 .../src/main/js/routes/summary.js               |  26 +++++++--------
 .../src/test/js/routes/agent.js                 |  26 +++++++--------
 74 files changed, 741 insertions(+), 884 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/assembly/release-control-center-agent.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/assembly/release-control-center-agent.xml b/modules/control-center-agent/assembly/release-control-center-agent.xml
index 76760d4..5004825 100644
--- a/modules/control-center-agent/assembly/release-control-center-agent.xml
+++ b/modules/control-center-agent/assembly/release-control-center-agent.xml
@@ -1,23 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  ~ /*
-  ~  * 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.
-  ~  */
-  -->
+  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.
+-->
 
 <assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/pom.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/pom.xml b/modules/control-center-agent/pom.xml
index 0237f5f..761cc63 100644
--- a/modules/control-center-agent/pom.xml
+++ b/modules/control-center-agent/pom.xml
@@ -1,23 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  ~ /*
-  ~  * 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.
-  ~  */
-  -->
+  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.
+-->
 
 <!--
     POM file.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java
index 63d02a3..8ed3613 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
index c85e25c..08d701a 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java
index 4912b06..3a0084a 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
index 12b87b9..d9fc3e7 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java
index d59e2d0..6ee5633 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java
index be84f48..f40c77a 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.handlers;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java
index f4c5dff..f745ba5 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.handlers;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
index aec2f17..8fe49bd 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.remote;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java
index 5bbe609..f8b006a 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.remote;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java
index b686b27..655ff85 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.remote;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java
index 09ceb53..3e40dee 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java
index d920a1b..9e387fb 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentSqlTestDrive.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java
index 04e3ee6..969830b 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Car.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java
index 063e6c2..1c661d2 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CarKey.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java
index edead2f..9f10e6f 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Country.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java
index f3f3761..8e10c94 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/CountryKey.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java
index fb87d72..2786b0b 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Department.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java
index a2d33a4..0f4ce64 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/DepartmentKey.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java
index 39fc2b3..eacc538 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Employee.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java
index 160606a..da89c20 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/EmployeeKey.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java
index 145cb6d..3363d6f 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/Parking.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java
index 5875cd1..1e0e595 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/model/ParkingKey.java
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.ignite.agent.testdrive.model;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-agent/src/main/resources/logging.properties
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/resources/logging.properties b/modules/control-center-agent/src/main/resources/logging.properties
index 44115f8..661f52c 100644
--- a/modules/control-center-agent/src/main/resources/logging.properties
+++ b/modules/control-center-agent/src/main/resources/logging.properties
@@ -1,21 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
 #
-# /*
-#  * Licensed to the Apache Software Foundation (ASF) under one or more
-#  * contributor license agreements.  See the NOTICE file distributed with
-#  * this work for additional information regarding copyright ownership.
-#  * The ASF licenses this file to You under the Apache License, Version 2.0
-#  * (the "License"); you may not use this file except in compliance with
-#  * the License.  You may obtain a copy of the License at
-#  *
-#  *      http://www.apache.org/licenses/LICENSE-2.0
-#  *
-#  * Unless required by applicable law or agreed to in writing, software
-#  * distributed under the License is distributed on an "AS IS" BASIS,
-#  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  * See the License for the specific language governing permissions and
-#  * limitations under the License.
-#  */
+#      http://www.apache.org/licenses/LICENSE-2.0
 #
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 handlers =java.util.logging.ConsoleHandler
 .level=FINE

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/pom.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-web/pom.xml b/modules/control-center-web/pom.xml
index fcd9b91..1744805 100644
--- a/modules/control-center-web/pom.xml
+++ b/modules/control-center-web/pom.xml
@@ -1,23 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  ~ /*
-  ~  * 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.
-  ~  */
-  -->
+  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.
+-->
 
 <!--
     POM file.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/agents/agent-manager.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/agents/agent-manager.js b/modules/control-center-web/src/main/js/agents/agent-manager.js
index 582cb11..cd6b084 100644
--- a/modules/control-center-web/src/main/js/agents/agent-manager.js
+++ b/modules/control-center-web/src/main/js/agents/agent-manager.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var WebSocketServer = require('ws').Server;

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/agents/agent-server.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/agents/agent-server.js b/modules/control-center-web/src/main/js/agents/agent-server.js
index bd7efbf..2efe531 100644
--- a/modules/control-center-web/src/main/js/agents/agent-server.js
+++ b/modules/control-center-web/src/main/js/agents/agent-server.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var _ = require('lodash');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/app.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/app.js b/modules/control-center-web/src/main/js/app.js
index 69f6663..e4dd654 100644
--- a/modules/control-center-web/src/main/js/app.js
+++ b/modules/control-center-web/src/main/js/app.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var express = require('express');

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/bin/www
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/bin/www b/modules/control-center-web/src/main/js/bin/www
index 69e73e3..0340cb0 100644
--- a/modules/control-center-web/src/main/js/bin/www
+++ b/modules/control-center-web/src/main/js/bin/www
@@ -1,22 +1,3 @@
-/*
- *
- *  * 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.
- *
- */
-
 #!/usr/bin/env node
 
 /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/admin-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/admin-controller.js b/modules/control-center-web/src/main/js/controllers/admin-controller.js
index 0b57da5..f5e372b 100644
--- a/modules/control-center-web/src/main/js/controllers/admin-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/admin-controller.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Controller for Admin screen.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/caches-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/caches-controller.js b/modules/control-center-web/src/main/js/controllers/caches-controller.js
index 28cedf0..9062d58 100644
--- a/modules/control-center-web/src/main/js/controllers/caches-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/caches-controller.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Controller for Caches screen.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/clusters-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/clusters-controller.js b/modules/control-center-web/src/main/js/controllers/clusters-controller.js
index 3b95a2e..9afbbc6 100644
--- a/modules/control-center-web/src/main/js/controllers/clusters-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/clusters-controller.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Controller for Clusters screen.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/common-module.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/common-module.js b/modules/control-center-web/src/main/js/controllers/common-module.js
index 5454c29..59ad2e6 100644
--- a/modules/control-center-web/src/main/js/controllers/common-module.js
+++ b/modules/control-center-web/src/main/js/controllers/common-module.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 var consoleModule = angular.module('ignite-web-console',

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/metadata-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/metadata-controller.js b/modules/control-center-web/src/main/js/controllers/metadata-controller.js
index e274e44..9f35aaa 100644
--- a/modules/control-center-web/src/main/js/controllers/metadata-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/metadata-controller.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Controller for Metadata screen.

http://git-wip-us.apache.org/repos/asf/ignite/blob/43316052/modules/control-center-web/src/main/js/controllers/profile-controller.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/controllers/profile-controller.js b/modules/control-center-web/src/main/js/controllers/profile-controller.js
index 5d4567b..8d37c18 100644
--- a/modules/control-center-web/src/main/js/controllers/profile-controller.js
+++ b/modules/control-center-web/src/main/js/controllers/profile-controller.js
@@ -1,20 +1,18 @@
 /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- *  * Licensed to the Apache Software Foundation (ASF) under one or more
- *  * contributor license agreements.  See the NOTICE file distributed with
- *  * this work for additional information regarding copyright ownership.
- *  * The ASF licenses this file to You under the Apache License, Version 2.0
- *  * (the "License"); you may not use this file except in compliance with
- *  * the License.  You may obtain a copy of the License at
- *  *
- *  *      http://www.apache.org/licenses/LICENSE-2.0
- *  *
- *  * Unless required by applicable law or agreed to in writing, software
- *  * distributed under the License is distributed on an "AS IS" BASIS,
- *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  * See the License for the specific language governing permissions and
- *  * limitations under the License.
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 // Controller for Profile screen.


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/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..42b4a1a
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/metadata.jade
@@ -0,0 +1,65 @@
+//-
+    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
+    .docs-body(ng-controller='metadataController')
+        div(dw-loading='loadingMetadataScreen' dw-loading-options='{text: "Loading metadata screen...", className: "page-loading-overlay"}')
+            div(ng-show='ui.ready')
+                +block-callout('{{screenTip.workflowTitle}}', 'screenTip.workflowContent', '{{screenTip.whatsNextTitle}}', 'screenTip.whatsNextContent')
+                hr
+                .padding-bottom-dflt(ng-show='metadatas && metadatas.length > 0')
+                    table.links(st-set-filter='metadatasSearch' st-table='displayedRows' st-safe-src='metadatas')
+                        thead
+                            tr
+                                th
+                                    lable.labelHeader.labelFormField {{metadataTitle()}}
+                                    .col-sm-8.pull-right(style='padding: 0')
+                                        .col-sm-6
+                                            .pull-right.labelLogin.additional-filter(ng-if='(displayedRows | metadatasValidation:false:true).length > 0')
+                                                a.labelFormField(ng-if='ui.showValid' ng-click='ui.showValid = !ui.showValid') Key fields should be configured: {{(displayedRows | metadatasValidation:false:true).length}}&nbsp
+                                                a.labelFormField(ng-if='!ui.showValid' ng-click='ui.showValid = !ui.showValid') Show all metadata: {{displayedRows.length}}&nbsp
+                                        .col-sm-6
+                                            input.form-control.pull-right(type='text' st-search='' placeholder='Filter metadatas...')
+                            tbody
+                                tr
+                                    td
+                                        .scrollable-y(style='max-height: 200px')
+                                            table
+                                                tbody
+                                                    tr(ng-repeat='row in (displayedRows | metadatasValidation:ui.showValid:true) track by row._id')
+                                                        td
+                                                            a(ng-class='{active: row._id == selectedItem._id}' ng-click='selectItem(row)') {{$index + 1}}) {{row.valueType}}
+                .padding-top-dflt(bs-affix)
+                    .panel-tip-container(data-placement='bottom' bs-tooltip data-title='Create new metadata')
+                        button.btn.btn-primary(id='new-item' ng-click='createItem()') Add metadata
+                    .panel-tip-container(bs-tooltip data-title='Load new metadata from database' data-placement='bottom')
+                        button.btn.btn-primary(ng-click='showLoadMetadataModal()') Load from database
+                    +save-remove-buttons('metadata')
+                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('metadata', 'backupItem')
+                    .section
+                        +save-remove-buttons('metadata')

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/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..abd0ba8
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/sidebar.jade
@@ -0,0 +1,48 @@
+//-
+    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
+
+append scripts
+    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='/data-structures.js')
+    script(src='/generator-common.js')
+    script(src='/generator-xml.js')
+    script(src='/generator-java.js')
+
+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-xs-3.col-sm-3.col-md-2.border-right.section-left.greedy
+            .sidebar-nav(bs-affix)
+                ul.menu(ng-controller='activeLink')
+                    +sidebar-item('/configuration/clusters', 1, 'Clusters')
+                    +sidebar-item('/configuration/caches', 2, 'Caches')
+                    +sidebar-item('/configuration/metadata', 3, 'Metadata')
+                    +sidebar-item('/configuration/summary', 4, 'Summary')
+
+        .col-xs-9.col-sm-9.col-md-10.border-left.section-right
+            .docs-content
+                block content

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/configuration/summary-tabs.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/configuration/summary-tabs.jade b/modules/control-center-web/src/main/js/views/configuration/summary-tabs.jade
new file mode 100644
index 0000000..94b9315
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/summary-tabs.jade
@@ -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.
+
+ul.nav(ng-class='$navClass', role='tablist')
+    li(role='presentation' ng-repeat='$pane in $panes track by $index' ng-class='[ $isActive($pane, $index) ? $activeClass : "", $pane.disabled ? "disabled" : "" ]')
+        a.summary-tab(ng-show='$pane.title != "POJO" || pojoAvailable()' ng-switch='$pane.title' role='tab' data-toggle='tab' ng-click='!$pane.disabled && $setActive($pane.name || $index)' data-index='{{ $index }}' aria-controls='$pane.title') {{$pane.title}}
+            img(ng-switch-when='XML' src='/images/xml.png')
+            img(ng-switch-when='Java' src='/images/java.png')
+            img(ng-switch-when='POJO' src='/images/java.png')
+            img(ng-switch-when='Dockerfile' src='/images/docker.png')
+.tab-content(ng-transclude)

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/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..b900e88
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/configuration/summary.jade
@@ -0,0 +1,124 @@
+//-
+    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='//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-dockerfile.js')
+
+    script(src='/generator-docker.js')
+    script(src='/summary-controller.js')
+
+include ../includes/controls
+
+mixin hard-link(ref, txt)
+    a(style='color:#ec1c24' href=ref target='_blank') #{txt}
+
+mixin btn-download(side)
+    .panel-tip-container(style='float: right;' data-placement='bottom' bs-tooltip='"Download #{side} configuration"')
+        button.btn.btn-primary.pull-right(type='submit' ng-click='$event.stopPropagation()') Download
+
+mixin pojos(side)
+    div(bs-pane title='POJO' ng-show='pojoAvailable()')
+        .details-row
+            .col-xs-2.col-sm-2.col-md-2
+                label POJO class:
+            .col-xs-10.col-sm-10.col-md-10
+                button.select-toggle.form-control(ng-model='config#{side}.pojoClass' bs-select bs-options='item for item in pojoClasses()')
+            .col-xs-2.col-sm-2.col-md-2
+                label
+            .col-xs-10.col-sm-10.col-md-10
+                input(type='checkbox' ng-model='configServer.useConstructor')
+                | Generate constructors
+            .col-xs-2.col-sm-2.col-md-2
+                label
+            .col-xs-10.col-sm-10.col-md-10
+                input(type='checkbox' ng-model='configServer.includeKeyFields')
+                | Include key fields
+        div(ui-ace='{ onLoad: aceInit, mode: "java" }' ng-model='config#{side}.pojoClassBody')
+
+block content
+    .docs-header
+        h1 Configurations Summary
+    .docs-body(ng-controller='summaryController' dw-loading='loadingSummaryScreen' dw-loading-options='{text: "Loading summary screen...", className: "page-loading-overlay"}')
+        +block-callout('{{screenTip.workflowTitle}}', 'screenTip.workflowContent', '{{screenTip.whatsNextTitle}}', 'screenTip.whatsNextContent')
+        hr
+        .padding-dflt(ng-if='clusters.length == 0')
+            | You have no clusters configured. Please configure them &nbsp;
+            a(href='clusters') here.
+        +main-table('Clusters:', 'clusters', 'clusterName', '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' bs-collapse-toggle) Server
+                        input(type='hidden' name='_id' value='{{selectedItem._id}}')
+                        input(type='hidden' name='os' value='{{os}}')
+                        input(type='hidden' name='javaClass' value='{{javaClassServer}}')
+                        input(type='hidden' name='useConstructor' value='{{configServer.useConstructor}}')
+                        input(type='hidden' name='includeKeyFields' value='{{configServer.includeKeyFields}}')
+                        +btn-download('server')
+                    .panel-collapse(role='tabpanel' bs-collapse-target)
+                        .summary-tabs(bs-tabs data-bs-active-pane="tabsServer.activeTab" ng-show='selectedItem' template='summary/summary-tabs')
+                            div(bs-pane title='XML')
+                                div(ui-ace='{ onLoad: aceInit, mode: "xml" }' ng-model='xmlServer')
+                            div(bs-pane title='Java')
+                                .details-row
+                                    .col-xs-2.col-sm-2.col-md-1
+                                        label Generate:
+                                    .col-xs-4.col-sm-3.col-md-3
+                                        button.select-toggle.form-control(ng-model='configServer.javaClassServer' bs-select bs-options='item.value as item.label for item in javaClassItems' data-sort='false')
+                                div(ui-ace='{ onLoad: aceInit, mode: "java" }' ng-model='javaServer')
+                            +pojos('Server')
+                            div(bs-pane title='Dockerfile')
+                                .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.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-xs-3.col-sm-2
+                                        label(for='os') Operation System:
+                                    .col-xs-5.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' bs-typeahead retain-selection 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' bs-collapse-toggle) Client
+                        input(type='hidden' name='_id' value='{{selectedItem._id}}')
+                        input(type='hidden' name='javaClass' value='{{javaClassClient}}')
+                        input(type='hidden' name='clientNearConfiguration' value='{{backupItem}}')
+                        input(type='hidden' name='useConstructor' value='{{configServer.useConstructor}}')
+                        input(type='hidden' name='includeKeyFields' value='{{configServer.includeKeyFields}}')
+                        +btn-download('client')
+                    .panel-collapse(role='tabpanel' bs-collapse-target)
+                        div(ng-show='selectedItem')
+                            .details-row(ng-repeat='field in clientFields')
+                                +form-row-custom(['col-xs-4 col-sm-4 col-md-3'], ['col-xs-4 col-sm-4 col-md-3'], 'backupItem')
+                            .summary-tabs(bs-tabs data-bs-active-pane="tabsClient.activeTab" template='summary/summary-tabs')
+                                div(bs-pane title='XML')
+                                    div(ui-ace='{ onLoad: aceInit, mode: "xml" }' ng-model='xmlClient')
+                                div(bs-pane title='Java')
+                                    .details-row
+                                        .col-xs-2.col-sm-2.col-md-1
+                                            label Generate:
+                                        .col-xs-4.col-sm-3.col-md-3
+                                            button.select-toggle.form-control(ng-model='backupItem.javaClassClient' bs-select bs-options='item.value as item.label for item in javaClassItems' data-sort='false')
+                                    div(ui-ace='{ onLoad: aceInit, mode: "java" }' ng-model='javaClient')
+                                +pojos('Client')

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/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}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/includes/controls.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/includes/controls.jade b/modules/control-center-web/src/main/js/views/includes/controls.jade
new file mode 100644
index 0000000..aa40367
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/includes/controls.jade
@@ -0,0 +1,515 @@
+//-
+    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.
+
+mixin block-callout(workflowTitle, workflowContent, whatsNextTitle, whatsNextContent)
+    .block-callout-parent
+        table
+            tbody
+                tr
+                    td.block-callout-left(width='50%')
+                        i.fa.fa-check-square.block-callout-header-left
+                        label.block-callout-header-left #{workflowTitle}
+                        ul
+                            li(ng-repeat='item in #{workflowContent}')
+                                div(ng-switch='item')
+                                    div(ng-switch-when='more-info')
+                                        a(ng-click='showMoreInfo(moreInfo.title, moreInfo.content)') More info
+                                    div(ng-switch-default) {{::item}}
+                    td.block-callout-right(width='50%')
+                        i.fa.fa-check-square.block-callout-header-right
+                        label.block-callout-header-right #{whatsNextTitle}
+                        ul
+                            li(ng-repeat='item in #{whatsNextContent}' ng-bind-html='item')
+
+mixin tipField(lines)
+    i.tipField.fa.fa-question-circle(ng-if=lines bs-tooltip='joinTip(#{lines})')
+    i.tipField.fa.fa-question-circle.blank(ng-if='!#{lines}')
+
+mixin tipLabel(lines)
+    i.tipLabel.fa.fa-question-circle(ng-if=lines bs-tooltip='joinTip(#{lines})')
+    i.tipLabel.fa.fa-question-circle.blank(ng-if='!#{lines}')
+
+mixin ico-exclamation(mdl, err, msg)
+    i.fa.fa-exclamation-triangle.form-control-feedback(ng-show='ui.inputForm["#{mdl}"].$error.#{err}' bs-tooltip data-title='#{msg}')
+
+mixin btn-save(show, click)
+    i.tipField.fa.fa-floppy-o(ng-show=show ng-click=click bs-tooltip data-title='Click icon or press [Enter] to save item' data-trigger='hover')
+
+mixin group-tip(lines)
+    i.group-legend-btn.fa.fa-question-circle(ng-if=lines bs-tooltip='joinTip(#{lines})')
+
+mixin group-btn-add(click, tip)
+    i.group-legend-btn.fa.fa-plus(ng-click=click bs-tooltip=tip)
+
+mixin btn-add(click, tip)
+    i.tipField.fa.fa-plus(ng-click=click bs-tooltip=tip)
+
+mixin btn-remove(click, tip)
+    i.tipField.fa.fa-remove(ng-click=click bs-tooltip=tip data-trigger='hover')
+
+mixin btn-up(show, click)
+    i.tipField.fa.fa-arrow-up(ng-if=show ng-click=click bs-tooltip data-title='Move item up')
+
+mixin btn-down(show, click)
+    i.tipField.fa.fa-arrow-down(ng-if=show ng-click=click bs-tooltip data-title='Move item down')
+
+mixin table-pair-edit(prefix, keyPlaceholder, valPlaceholder, keyJavaBuildInTypes, valueJavaBuildInTypes, focusId, index)
+    -var keyModel = 'field.' + prefix + 'Key'
+    -var valModel = 'field.' + prefix + 'Value'
+
+    -var keyFocusId = prefix + 'Key' + focusId
+    -var valFocusId = prefix + 'Value' + focusId
+
+    .col-xs-6.col-sm-6.col-md-6
+        label.placeholder #{keyPlaceholder}
+        label.fieldSep /
+        .input-tip
+            if keyJavaBuildInTypes
+                input.form-control(id=keyFocusId enter-focus-next=valFocusId type='text' ng-model=keyModel placeholder=keyPlaceholder bs-typeahead container='body' retain-selection data-min-length='1' bs-options='javaClass for javaClass in javaBuildInClasses' on-escape='tableReset()')
+            else
+                input.form-control(id=keyFocusId enter-focus-next=valFocusId type='text' ng-model=keyModel placeholder=keyPlaceholder on-escape='tableReset()')
+    .col-xs-6.col-sm-6.col-md-6
+        -var arg = keyModel + ', ' + valModel
+        -var btnVisible = 'tablePairSaveVisible(field, ' + index + ')'
+        -var btnSave = 'tablePairSave(tablePairValid, backupItem, field, ' + index + ')'
+        -var btnVisibleAndSave = btnVisible + ' && ' + btnSave
+
+        label.placeholder #{valPlaceholder}
+        +btn-save(btnVisible, btnSave)
+        .input-tip
+            if valueJavaBuildInTypes
+                input.form-control(id=valFocusId type='text' ng-model=valModel placeholder=valPlaceholder bs-typeahead container='body' retain-selection data-min-length='1' bs-options='javaClass for javaClass in javaBuildInClasses' on-enter=btnVisibleAndSave on-escape='tableReset()')
+            else
+                input.form-control(id=valFocusId type='text' ng-model=valModel placeholder=valPlaceholder on-enter=btnVisibleAndSave on-escape='tableReset()')
+
+mixin table-pair(header, tblMdl, keyFld, valFld, keyPlaceholder, valPlaceholder, keyJavaBuildInTypes, valueJavaBuildInTypes)
+    .col-sm-12.group
+        .group-legend
+            label #{header}
+            +group-tip('field.tip')
+            +group-btn-add('tableNewItem(field)', 'field.addTip')
+        .group-content-empty(ng-if='!((#{tblMdl} && #{tblMdl}.length > 0) || tableNewItemActive(field))') Not defined
+        .group-content(ng-show='(#{tblMdl} && #{tblMdl}.length > 0) || tableNewItemActive(field)')
+            table.links-edit(id='{{::field.model}}' st-table=tblMdl)
+                tbody
+                    tr(ng-repeat='item in #{tblMdl}')
+                        td.col-sm-12(ng-show='!tableEditing(field, $index)')
+                            a.labelFormField(ng-click='tableStartEdit(backupItem, field, $index)') {{compactJavaName(field.model, $index + 1, 55, [item.#{keyFld}, item.#{valFld}])}}
+                            +btn-remove('tableRemove(backupItem, field, $index)', 'field.removeTip')
+                        td.col-sm-12(ng-show='tableEditing(field, $index)')
+                            +table-pair-edit('cur', keyPlaceholder, valPlaceholder, keyJavaBuildInTypes, valueJavaBuildInTypes, '{{::field.focusId}}', '$index')
+                tfoot(ng-show='tableNewItemActive(field)')
+                    tr
+                        td.col-sm-12
+                            +table-pair-edit('new', keyPlaceholder, valPlaceholder, keyJavaBuildInTypes, valueJavaBuildInTypes, '{{::field.focusId}}', '-1')
+
+mixin details-row
+    - var lblDetailClasses = ['col-sm-4', 'details-label']
+
+    - var detailMdl = 'getModel(backupItem, detail)[detail.model]';
+    - var detailCommon = {'ng-model': detailMdl, 'ng-required': 'detail.required'};
+    - var detailDisabled = '{{detail.disabled}}'
+    - var dropdownPlaceholder = {'ng-class': '{placeholder: !' + detailMdl + ' || ' + detailMdl + '.length == 0}'}
+
+    - var customValidators = {'ng-attr-ipaddress': '{{detail.ipaddress}}'}
+
+    div(ng-switch='detail.type')
+        div(ng-switch-when='label')
+            label {{::detail.label}}
+        div.checkbox(ng-switch-when='check')
+            label(id='{{::detail.id}}')
+                input(type='checkbox')&attributes(detailCommon)
+                |{{::detail.label}}
+                +tipLabel('detail.tip')
+        div(ng-switch-when='text')
+            label(class=lblDetailClasses ng-class='{required: detail.required}') {{::detail.label}}:
+            .col-sm-8
+                +tipField('detail.tip')
+                .input-tip
+                    input.form-control(id='{{::detail.model}}' type='text' placeholder='{{::detail.placeholder}}')&attributes(detailCommon)
+        div(ng-switch-when='number')
+            label(class=lblDetailClasses ng-class='{required: detail.required}') {{::detail.label}}:
+            .col-sm-8
+                +tipField('detail.tip')
+                .input-tip
+                    input.form-control(id='{{::detail.id}}' name='{{detail.model}}' type='number' ng-disabled=detailDisabled placeholder='{{::detail.placeholder}}' min='{{detail.min ? detail.min : 0}}' max='{{detail.max ? detail.max : Number.MAX_VALUE}}')&attributes(detailCommon)
+                    +ico-exclamation('{{detail.model}}', 'min', 'Value is less than allowable minimum.')
+                    +ico-exclamation('{{detail.model}}', 'max', 'Value is more than allowable maximum.')
+                    +ico-exclamation('{{detail.model}}', 'number', 'Invalid value. Only numbers allowed.')
+        div(ng-switch-when='dropdown')
+            label(class=lblDetailClasses ng-class='{required: detail.required}') {{::detail.label}}:
+            .col-sm-8
+                +tipField('detail.tip')
+                .input-tip
+                    button.select-toggle.form-control(id='{{::detail.id}}' bs-select data-placeholder='{{::detail.placeholder}}' bs-options='item.value as item.label for item in {{detail.items}}' tabindex='0')&attributes(detailCommon)&attributes(dropdownPlaceholder)
+        div(ng-switch-when='dropdown-multiple')
+            label(class=lblDetailClasses ng-class='{required: detail.required}') {{::detail.label}}:
+            .col-sm-8
+                +tipField('detail.tip')
+                .input-tip
+                    button.select-toggle.form-control(bs-select data-multiple='1' data-placeholder='{{::detail.placeholder}}' bs-options='item.value as item.label for item in {{detail.items}}' tabindex='-1')&attributes(detailCommon)&attributes(dropdownPlaceholder)
+        .group-section(ng-switch-when='table-simple')&attributes(detailCommon)
+            .col-sm-12.group(id='{{::detail.model}}')
+                .group-legend
+                    label {{::detail.label}}
+                    +group-tip('detail.tableTip')
+                    +group-btn-add('tableNewItem(detail)', 'detail.addTip')
+                .group-content-empty(ng-show='!((#{detailMdl} && #{detailMdl}.length > 0) || tableNewItemActive(detail))') Not defined
+                .group-content(ng-show='(#{detailMdl} && #{detailMdl}.length > 0) || tableNewItemActive(detail)')
+                    table.links-edit-details(st-table='#{detailMdl}')
+                        tbody
+                            tr(ng-repeat='item in #{detailMdl} track by $index')
+                                td
+                                    div(ng-show='!tableEditing(detail, $index)')
+                                        a.labelFormField(ng-click='tableStartEdit(backupItem, detail, $index)') {{$index + 1}}) {{item}}
+                                        +btn-remove('tableRemove(backupItem, detail, $index)', 'detail.removeTip')
+                                        +btn-down('detail.reordering && tableSimpleDownVisible(backupItem, detail, $index)', 'tableSimpleDown(backupItem, detail, $index)')
+                                        +btn-up('detail.reordering && $index > 0', 'tableSimpleUp(backupItem, detail, $index)')
+                                    div(ng-if='tableEditing(detail, $index)')
+                                        label.labelField {{$index + 1}})
+                                        +btn-save('tableSimpleSaveVisible(detail, index)', 'tableSimpleSave(tableSimpleValid, backupItem, detail, $index)')
+                                        .input-tip.form-group.has-feedback
+                                            input.form-control(id='cur{{::detail.focusId}}' name='{{detail.model}}.edit' type='text' ng-model='detail.curValue' placeholder='{{::detail.placeholder}}' on-enter='tableSimpleSaveVisible(detail, index) && tableSimpleSave(tableSimpleValid, backupItem, detail, $index)' on-escape='tableReset()')&attributes(customValidators)
+                                            +ico-exclamation('{{detail.model}}.edit', 'ipaddress', 'Invalid address, see help for format description.')
+                        tfoot(ng-show='tableNewItemActive(detail)')
+                            tr
+                                td
+                                    label.placeholder {{::detail.placeholder}}
+                                    +btn-save('tableSimpleSaveVisible(detail, -1)', 'tableSimpleSave(tableSimpleValid, backupItem, detail, -1)')
+                                    .input-tip.form-group.has-feedback
+                                        input.form-control(id='new{{::detail.focusId}}' name='{{detail.model}}' type='text' ng-model='detail.newValue' ng-focus='tableNewItem(detail)' placeholder='{{::detail.placeholder}}' on-enter='tableSimpleSaveVisible(detail, -1) && tableSimpleSave(tableSimpleValid, backupItem, detail, -1)' on-escape='tableReset()')&attributes(customValidators)
+                                        +ico-exclamation('{{detail.model}}', 'ipaddress', 'Invalid address, see help for format description.')
+
+mixin table-db-field-edit(prefix, focusId, index)
+    -var databaseName = prefix + 'DatabaseName'
+    -var databaseType = prefix + 'DatabaseType'
+    -var javaName = prefix + 'JavaName'
+    -var javaType = prefix + 'JavaType'
+
+    -var databaseNameModel = 'field.' + databaseName
+    -var databaseTypeModel = 'field.' + databaseType
+    -var javaNameModel = 'field.' + javaName
+    -var javaTypeModel = 'field.' + javaType
+
+    -var databaseNameId = databaseName + focusId
+    -var databaseTypeId = databaseType + focusId
+    -var javaNameId = javaName + focusId
+    -var javaTypeId = javaType + focusId
+
+    .col-xs-3.col-sm-3.col-md-3
+        label.placeholder DB name
+        label.fieldSep /
+        .input-tip
+            input.form-control(id=databaseNameId enter-focus-next=databaseTypeId type='text' ng-model=databaseNameModel placeholder='DB name' on-enter='#{javaNameModel} = #{javaNameModel} ? #{javaNameModel} : #{databaseNameModel}' on-escape='tableReset()')
+    .col-xs-3.col-sm-3.col-md-3
+        label.placeholder DB type
+        label.fieldSep /
+        .input-tip
+            button.form-control(id=databaseTypeId enter-focus-next=javaNameId ng-model=databaseTypeModel data-placeholder='DB type' ng-class='{placeholder: !#{databaseTypeModel}}' bs-select bs-options='item.value as item.label for item in {{supportedJdbcTypes}}' on-escape='tableReset()' tabindex='0')
+    .col-xs-3.col-sm-3.col-md-3
+        label.placeholder Java name
+        label.fieldSep /
+        .input-tip
+            input.form-control(id=javaNameId enter-focus-next=javaTypeId type='text' ng-model=javaNameModel placeholder='Java name' on-escape='tableReset()')
+    .col-xs-3.col-sm-3.col-md-3
+        -var btnVisible = 'tableDbFieldSaveVisible(field, ' + index +')'
+        -var btnSave = 'tableDbFieldSave(field, ' + index +')'
+        -var btnVisibleAndSave = btnVisible + ' && ' + btnSave
+
+        label.placeholder Java type
+        +btn-save(btnVisible, btnSave)
+        .input-tip
+            button.form-control(id=javaTypeId ng-model=javaTypeModel data-placeholder='Java type' ng-class='{placeholder: !#{javaTypeModel}}' bs-select bs-options='item.value as item.label for item in {{supportedJavaTypes}}' on-enter=btnVisibleAndSave on-escape='tableReset()' tabindex='0')
+
+mixin table-group-item-edit(prefix, index)
+    -var fieldName = prefix + 'FieldName'
+    -var className = prefix + 'ClassName'
+    -var direction = prefix + 'Direction'
+
+    -var fieldNameModel = 'field.' + fieldName
+    -var classNameModel = 'field.' + className
+    -var directionModel = 'field.' + direction
+
+    -var btnVisible = 'tableGroupItemSaveVisible(field, ' + index + ')'
+    -var btnSave = 'tableGroupItemSave(field, groupIndex, ' + index + ')'
+    -var btnVisibleAndSave = btnVisible + ' && ' + btnSave
+
+    .col-xs-4.col-sm-4.col-md-4
+        label.placeholder Field name
+        label.fieldSep /
+        .input-tip
+            input.form-control(id=fieldName enter-focus-next=className type='text' ng-model=fieldNameModel placeholder='Field name' on-escape='tableReset()')
+    .col-xs-5.col-sm-5.col-md-5
+        label.placeholder Class name
+        label.fieldSep /
+        .input-tip
+            input.form-control(id=className enter-focus-next=direction type='text' ng-model=classNameModel placeholder='Class name' bs-typeahead container='body' retain-selection data-min-length='1' bs-options='javaClass for javaClass in javaBuildInClasses' on-escape='tableReset()')
+    .col-xs-3.col-sm-3.col-md-3
+        label.placeholder Sort order
+        +btn-save(btnVisible, btnSave)
+        .input-tip
+            button.form-control(id=direction ng-model=directionModel bs-select bs-options='item.value as item.label for item in {{sortDirections}}' on-enter=btnVisibleAndSave on-escape='tableReset()' tabindex='0')
+
+mixin form-row(dataSource)
+    +form-row-custom(['col-xs-4 col-sm-4 col-md-4'], ['col-xs-8 col-sm-8 col-md-8'], dataSource)
+
+mixin form-row-custom(lblClasses, fieldClasses, dataSource)
+    - var fieldMdl = 'getModel('+ dataSource + ', field)[field.model]';
+    - var fieldCommon = {'ng-model': fieldMdl, 'ng-required': 'field.required || required(field)'};
+    - var fieldRequiredClass = '{true: "required"}[field.required || required(field)]'
+    - var fieldHide = '{{field.hide}}'
+    - var fieldDisabled = '{{field.disabled}}'
+    - var dropdownPlaceholder = {'ng-class': '{placeholder: !' + fieldMdl + ' || ' + fieldMdl + '.length == 0}'}
+
+    div(ng-switch='field.type')
+        div(ng-switch-when='label')
+            label {{::field.label}}
+        div.checkbox(ng-switch-when='check' ng-hide=fieldHide)
+            label(id='{{::field.id}}')
+                input(type='checkbox' ng-disabled=fieldDisabled)&attributes(fieldCommon)
+                | {{::field.label}}
+            +tipLabel('field.tip')
+        div(ng-switch-when='text' ng-hide=fieldHide)
+            label(class=lblClasses ng-class=fieldRequiredClass) {{::field.label}}:
+            div(class=fieldClasses)
+                +tipField('field.tip')
+                .input-tip
+                    input.form-control(id='{{::field.id}}' type='text' ng-disabled=fieldDisabled placeholder='{{::field.placeholder}}' ng-focus='tableReset()')&attributes(fieldCommon)
+        div(ng-switch-when='typeahead' ng-hide=fieldHide)
+            label(class=lblClasses ng-class=fieldRequiredClass) {{::field.label}}:
+            div(class=fieldClasses)
+                +tipField('field.tip')
+                .input-tip
+                    input.form-control(id='{{::field.id}}' type='text' placeholder='{{::field.placeholder}}' ng-focus='tableReset()' bs-typeahead container='body' retain-selection data-min-length='1' bs-options='item for item in {{::field.items}}')&attributes(fieldCommon)
+        div(ng-switch-when='password' ng-hide=fieldHide)
+            label(class=lblClasses ng-class=fieldRequiredClass) {{::field.label}}:
+            div(class=fieldClasses)
+                +tipField('field.tip')
+                .input-tip
+                    input.form-control(id='{{::field.id}}' type='password' placeholder='{{::field.placeholder}}' on-enter='{{::field.onEnter}}')&attributes(fieldCommon)
+        div(ng-switch-when='number' ng-hide=fieldHide)
+            label(class=lblClasses ng-class=fieldRequiredClass) {{::field.label}}:
+            div(class=fieldClasses)
+                +tipField('field.tip')
+                .input-tip
+                    input.form-control(id='{{::field.id}}' name='{{field.model}}' type='number' ng-disabled=fieldDisabled placeholder='{{::field.placeholder}}' ng-focus='tableReset()' min='{{field.min ? field.min : 0}}' max='{{field.max ? field.max : Number.MAX_VALUE}}')&attributes(fieldCommon)
+                    +ico-exclamation('{{field.model}}', 'min', 'Value is less than allowable minimum.')
+                    +ico-exclamation('{{field.model}}', 'max', 'Value is more than allowable maximum.')
+                    +ico-exclamation('{{field.model}}', 'number', 'Invalid value. Only numbers allowed.')
+        div(ng-switch-when='dropdown' ng-hide=fieldHide)
+            label(class=lblClasses ng-class=fieldRequiredClass) {{::field.label}}:
+            div(class=fieldClasses)
+                +tipField('field.tip')
+                .input-tip
+                    button.select-toggle.form-control(id='{{::field.id}}' bs-select ng-disabled=fieldDisabled data-placeholder='{{::field.placeholder}}' bs-options='item.value as item.label for item in {{field.items}}' ng-attr-data-container='{{::field.container}}' tabindex='0')&attributes(fieldCommon)&attributes(dropdownPlaceholder)
+        div(ng-switch-when='dropdown-multiple' ng-hide=fieldHide)
+            dic(class=lblClasses)
+                label(ng-class=fieldRequiredClass) {{::field.label}}:
+                a.customize(ng-if='field.addLink' ng-href='{{field.addLink.ref}}') (add)
+            div(class=fieldClasses)
+                +tipField('field.tip')
+                .input-tip
+                    button.select-toggle.form-control(id='{{::field.id}}' bs-select ng-disabled='{{field.items}}.length == 0' data-multiple='1' data-placeholder='{{::$eval(field.items).length == 0 ? field.placeholderEmpty || field.placeholder : field.placeholder}}' bs-options='item.value as item.label for item in {{field.items}}' tabindex='-1')&attributes(fieldCommon)&attributes(dropdownPlaceholder)
+        div(ng-switch-when='dropdown-details' ng-hide=fieldHide)
+            - var expanded = 'field.details[' + fieldMdl + '].expanded'
+
+            label(class=lblClasses ng-class=fieldRequiredClass) {{::field.label}}:
+            div(class=fieldClasses)
+                +tipField('field.tip')
+                .input-tip
+                    button.select-toggle.form-control(id='{{::field.id}}' bs-select ng-disabled=fieldDisabled data-placeholder='{{::field.placeholder}}' bs-options='item.value as item.label for item in {{field.items}}' tabindex='0')&attributes(fieldCommon)&attributes(dropdownPlaceholder)
+            a.customize(ng-if='#{fieldMdl} && field.settings && field.details[#{fieldMdl}].fields.length > 0' ng-click='#{expanded} = !#{expanded}') {{#{expanded} ? 'Hide settings' : 'Show settings'}}
+            .col-sm-12.panel-details(ng-show='(#{expanded} || !field.settings) && #{fieldMdl}')
+                .details-row(ng-repeat='detail in field.details[#{fieldMdl}].fields')
+                    +details-row
+        .section(ng-switch-when='panel-details' ng-hide=fieldHide)&attributes(fieldCommon)
+            .col-sm-12.group
+                .group-legend
+                    label {{::field.label}}
+                    +group-tip('field.tip')
+                .group-content
+                    .details-row(ng-repeat='detail in field.details')
+                        +details-row
+        .group-section(ng-switch-when='table-simple' ng-hide=fieldHide)&attributes(fieldCommon)
+            .col-sm-12.group
+                .group-legend
+                    label {{::field.label}}
+                    +group-tip('field.tableTip')
+                    +group-btn-add('tableNewItem(field)', 'field.addTip')
+                .group-content-empty(ng-show='!((#{fieldMdl} && #{fieldMdl}.length > 0) || tableNewItemActive(field))') Not defined
+                .group-content(ng-show='(#{fieldMdl} && #{fieldMdl}.length > 0) || tableNewItemActive(field)')
+                    table.links-edit(id='{{::field.model}}' st-table='#{fieldMdl}')
+                        tbody
+                            tr(ng-repeat='item in #{fieldMdl} track by $index')
+                                td
+                                    div(ng-show='!tableEditing(field, $index)')
+                                        a.labelFormField(ng-click='tableStartEdit(backupItem, field, $index)') {{compactJavaName(field.model, $index + 1, 55, [item])}}
+                                        +btn-remove('tableRemove(backupItem, field, $index)', 'field.removeTip')
+                                        +btn-down('field.reordering && tableSimpleDownVisible(backupItem, field, $index)', 'tableSimpleDown(backupItem, field, $index)')
+                                        +btn-up('field.reordering && $index > 0', 'tableSimpleUp(backupItem, field, $index)')
+                                    div(ng-if='tableEditing(field, $index)')
+                                        label.labelField {{$index + 1}})
+                                        +btn-save('tableSimpleSaveVisible(field, $index)', 'tableSimpleSave(tableSimpleValid, backupItem, field, $index)')
+                                        .input-tip
+                                            input.form-control(id='cur{{::field.focusId}}' type='text' ng-model='field.curValue' placeholder='{{::field.placeholder}}' on-enter='tableSimpleSaveVisible(field) && tableSimpleSave(tableSimpleValid, backupItem, field, $index)' on-escape='tableReset()')
+                        tfoot(ng-show='tableNewItemActive(field)')
+                            tr
+                                td
+                                    label.placeholder {{::field.placeholder}}
+                                    +btn-save('tableSimpleSaveVisible(field, -1)', 'tableSimpleSave(tableSimpleValid, backupItem, field, -1)')
+                                    .input-tip
+                                        input.form-control(id='new{{::field.focusId}}' type='text' ng-model='field.newValue' placeholder='{{::field.placeholder}}' on-enter='tableSimpleSaveVisible(field, -1) && tableSimpleSave(tableSimpleValid, backupItem, field, -1)' on-escape='tableReset()')
+        .group-section(ng-switch-when='indexedTypes')
+            +table-pair('Index key-value type pairs', fieldMdl, 'keyClass', 'valueClass', 'Key full class name', 'Value class full name', true, true)
+        div(ng-switch-when='queryFieldsFirst' ng-hide=fieldHide)
+            +table-pair('{{::field.label}}', fieldMdl, 'name', 'className', 'Field name', 'Field full class name', false, true)
+        .group-section(ng-switch-when='queryFields' ng-hide=fieldHide)
+            +table-pair('{{::field.label}}', fieldMdl, 'name', 'className', 'Field name', 'Field full class name', false, true)
+        .group-section(ng-switch-when='table-db-fields' ng-hide=fieldHide)
+            .col-sm-12.group
+                .group-legend
+                    label(id='{{::field.id + "-add"}}') {{::field.label}}
+                    +group-tip('field.tip')
+                    +group-btn-add('tableNewItem(field)', 'field.addTip')
+                .group-content-empty(ng-show='!((#{fieldMdl} && #{fieldMdl}.length > 0) || tableNewItemActive(field))') Not defined
+                .group-content(ng-show='(#{fieldMdl} && #{fieldMdl}.length > 0) || tableNewItemActive(field)')
+                    table.links-edit(st-table=fieldMdl)
+                        tbody
+                            tr(ng-repeat='item in #{fieldMdl}')
+                                td
+                                    div(ng-show='!tableEditing(field, $index)')
+                                        a.labelFormField(ng-click='tableStartEdit(backupItem, field, $index)') {{$index + 1}}) {{item.databaseName}} / {{item.databaseType}} / {{item.javaName}} / {{item.javaType}}
+                                        +btn-remove('tableRemove(backupItem, field, $index)', 'field.removeTip')
+                                    div(ng-if='tableEditing(field, $index)')
+                                        +table-db-field-edit('cur', '{{::field.focusId}}', '$index')
+                        tfoot(ng-show='tableNewItemActive(field)')
+                            tr
+                                td
+                                    +table-db-field-edit('new', '{{::field.focusId}}', '-1')
+        .group-section(ng-switch-when='table-query-groups' ng-hide=fieldHide)
+            .col-sm-12.group
+                .group-legend
+                    label {{::field.label}}
+                    +group-tip('field.tip')
+                    +group-btn-add('tableNewItem(field)', 'field.addTip')
+                .group-content-empty(id='{{::field.id + "-add"}}' ng-show='!((#{fieldMdl} && #{fieldMdl}.length > 0) || tableNewItemActive(field))') Not defined
+                .group-content(ng-show='(#{fieldMdl} && #{fieldMdl}.length > 0) || tableNewItemActive(field)')
+                    table.links-edit(st-table=fieldMdl ng-init='newDirection = false')
+                        tbody
+                            tr(ng-repeat='group in #{fieldMdl}')
+                                td
+                                    .col-sm-12(ng-show='!tableEditing(field, $index)')
+                                        a.labelFormField(id='{{field.id + $index}}' ng-click='tableStartEdit(backupItem, field, $index)') {{$index + 1}}) {{group.name}}
+                                        +btn-remove('tableRemove(backupItem, field, $index)', 'field.removeTip')
+                                        +btn-add('tableGroupNewItem(field, $index)', 'field.addItemTip')
+                                    div(ng-if='tableEditing(field, $index)')
+                                        label.labelField {{$index + 1}})
+                                        +btn-save('tableGroupSaveVisible(field, $index)', 'tableGroupSave(field, $index)')
+                                        .input-tip
+                                            input#curGroupName.form-control(type='text' ng-model='field.curGroupName' placeholder='Index name' on-enter='tableGroupSaveVisible(field, $index) && tableGroupSave(field, $index)' on-escape='tableReset()')
+                                    .margin-left-dflt
+                                        table.links-edit-sub(st-table='group.fields' ng-init='groupIndex = $index')
+                                            tbody
+                                                tr(ng-repeat='groupItem in group.fields')
+                                                    td
+                                                        div(ng-show='!tableGroupItemEditing(groupIndex, $index)')
+                                                            a.labelFormField(ng-click='tableGroupItemStartEdit(field, groupIndex, $index)') {{$index + 1}}) {{groupItem.name}} / {{groupItem.className}} / {{groupItem.direction ? "DESC" : "ASC"}}
+                                                            +btn-remove('tableRemoveGroupItem(group, $index)', 'field.removeItemTip')
+                                                        div(ng-if='tableGroupItemEditing(groupIndex, $index)')
+                                                            +table-group-item-edit('cur', '$index')
+                                            tfoot(ng-if='tableGroupNewItemActive(groupIndex)')
+                                                tr(style='padding-left: 18px')
+                                                    td
+                                                        +table-group-item-edit('new', '-1')
+                        tfoot(ng-show='tableNewItemActive(field)')
+                            tr
+                                td
+                                    label.placeholder Group name
+                                    +btn-save('tableGroupSaveVisible(field, -1)', 'tableGroupSave(field, -1)')
+                                    .input-tip
+                                        input#newGroupName.form-control(type='text' ng-model='field.newGroupName' placeholder='Group name' on-enter='tableGroupSaveVisible(field, -1) && tableGroupSave(field, -1)' on-escape='tableReset()')
+
+mixin main-table(title, rows, focusId, click, rowTemplate)
+    .padding-bottom-dflt(ng-show='#{rows} && #{rows}.length > 0')
+        table.links(st-set-filter='#{rows}Search' st-table='displayedRows' st-safe-src='#{rows}')
+            thead
+                tr
+                    th
+                        lable.labelHeader.labelFormField #{title}
+                        .col-sm-3.pull-right(style='padding: 0')
+                            input.form-control(type='text' st-search='' placeholder='Filter #{rows}...')
+                tbody
+                    tr
+                        td
+                            .scrollable-y(style='max-height: 200px')
+                                table
+                                    tbody
+                                        tr(ng-repeat='row in displayedRows track by row._id')
+                                            td
+                                                a(ng-class='{active: row._id == selectedItem._id}' on-click-focus=focusId ng-click=click) #{rowTemplate}
+
+mixin groups(groups, dataSource)
+    .panel.panel-default(ng-repeat='group in #{groups}' ng-click='triggerDigest=true' ng-hide='{{group.hide}}')
+        .panel-heading(bs-collapse-toggle ng-click='hidePopover()') {{::group.label}}
+            label(id='{{::group.group + "-title"}}')
+            i.tipLabel.fa.fa-question-circle(ng-if='group.tip' bs-tooltip='joinTip(group.tip)')
+            i.tipLabel.fa.fa-question-circle.blank(ng-if='!group.tip')
+            i.pull-right.fa.fa-undo(ng-show='group.dirty' ng-click='resetItem(group.group); $event.stopPropagation()' bs-tooltip data-title='Undo unsaved changes')
+        .panel-collapse(role='tabpanel' bs-collapse-target id='{{::group.group}}' number='{{::group.number}}')
+            .panel-body
+                .col-sm-6(id='{{::group.group + "-left"}}')
+                    .settings-row(ng-repeat='field in group.fields')
+                        +form-row(dataSource)
+                .col-sm-6
+                    +preview('group.group', '{{::group.group + "-right"}}')
+
+mixin advanced-options
+    .advanced-options
+        i.fa(ng-class='ui.expanded ? "fa-chevron-circle-up" : "fa-chevron-circle-down"' ng-click='toggleExpanded()')
+        a(ng-click='toggleExpanded()') {{ui.expanded ? 'Hide advanced settings...' : 'Show advanced settings...'}}
+
+mixin preview-content(preview, state, mode)
+    -var previewMode = 'group.preview'
+
+    .preview-content(ng-if='!preview[#{preview}].allDefaults && #{previewMode} == #{state}' id='#{id}'
+        ui-ace='{onLoad: previewInit, onChange: previewChanged, mode: "#{mode}", rendererOptions: {minLines: group.previewMinLines || 3}}' ng-model='preview[#{preview}].#{mode}')
+
+mixin preview(preview, id)
+    -var previewMode = 'group.preview'
+
+    .preview-panel(ng-init='#{previewMode} = false')
+        .preview-legend
+            a(ng-class='{active: !#{previewMode}, inactive: #{previewMode}}' ng-click='#{previewMode} = false') XML&nbsp;
+            a(ng-class='{active: #{previewMode}, inactive: !#{previewMode}}' ng-click='#{previewMode} = true') Java
+        +preview-content(preview, false, 'xml')
+        +preview-content(preview, true, 'java')
+        .preview-content-empty(ng-if='preview[#{preview}].allDefaults' id='#{id}')
+            label All Defaults
+
+mixin save-remove-buttons(objectName)
+    -var removeTip = '"Remove current ' + objectName + '"'
+    -var cloneTip = '"Clone current ' + objectName + '"'
+
+    .panel-tip-container(ng-hide='!backupItem || backupItem._id')
+        a.btn.btn-primary(ng-disabled='!ui.isDirty()' ng-click='ui.isDirty() ? saveItem() : ""' bs-tooltip='' data-title='{{saveBtnTipText(ui.isDirty(), "#{objectName}")}}' data-placement='bottom' data-trigger='hover') Save
+    .panel-tip-container(ng-show='backupItem._id')
+        a.btn.btn-primary(id='save-item' ng-disabled='!ui.isDirty()' ng-click='ui.isDirty() ? saveItem() : ""' bs-tooltip='' data-title='{{saveBtnTipText(ui.isDirty(), "#{objectName}")}}' data-placement='bottom' data-trigger='hover') Save
+    .panel-tip-container(ng-show='backupItem._id')
+        a.btn.btn-primary(id='clone-item' ng-click='cloneItem()' bs-tooltip=cloneTip data-placement='bottom' data-trigger='hover') Clone
+    .btn-group.panel-tip-container(ng-show='backupItem._id')
+        button.btn.btn-primary(id='remove-item' ng-click='removeItem()' bs-tooltip=removeTip data-placement='bottom' data-trigger='hover') Remove
+        button.btn.dropdown-toggle.btn-primary(id='remove-item-dropdown' data-toggle='dropdown' data-container='body' bs-dropdown='removeDropdown' data-placement='bottom-right')
+            span.caret

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/includes/footer.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/includes/footer.jade b/modules/control-center-web/src/main/js/views/includes/footer.jade
new file mode 100644
index 0000000..8ebac3d
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/includes/footer.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.
+
+.container.container-footer
+    footer
+        center
+            p Apache Ignite Web Console, version 1.0.0 beta
+            p © 2015 The Apache Software Foundation.
+            p Apache, Apache Ignite, the Apache feather and the Apache Ignite logo are trademarks of The Apache Software Foundation.

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/includes/header.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/includes/header.jade b/modules/control-center-web/src/main/js/views/includes/header.jade
new file mode 100644
index 0000000..0d1d04a
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/includes/header.jade
@@ -0,0 +1,40 @@
+//-
+    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.
+mixin header-item(active, ref, txt)
+    li
+        a(ng-class='{active: isActive("#{active}")}' href='#{ref}') #{txt}
+
+header#header.header
+    .viewedUser(ng-show='becomeUsed') Currently assuming "
+        strong {{user.username}}
+        | ",&nbsp;&nbsp;
+        a(href='/admin/become') revert to your identity.
+    .container(ng-controller='auth')
+        h1.navbar-brand
+            a(href='/') Apache Ignite Web Console
+        .navbar-collapse.collapse(ng-controller='activeLink')
+            ul.nav.navbar-nav(ng-show='user')
+                +header-item('/configuration', '/configuration/clusters', 'Configuration')
+                //+header-item('/monitoring', '/monitoring', 'Monitoring')
+                li(ng-controller='notebooks')
+                    a.dropdown-toggle(ng-hide='$root.notebooks.length == 0' ng-class='{active: isActive("/sql")}' data-toggle='dropdown' bs-dropdown='notebookDropdown' data-placement='bottom-right') SQL
+                        span.caret
+                    a(ng-hide='$root.notebooks.length > 0' ng-click='inputNotebookName()') SQL
+                //+header-item('/deploy', '/deploy', 'Deploy')
+            ul.nav.navbar-nav.pull-right
+                li(ng-if='user')
+                    a.dropdown-toggle(data-toggle='dropdown' ng-class='{active: isActive("/profile") || isActive("/admin")}' bs-dropdown='userDropdown' data-placement='bottom-right') {{user.username}}
+                        span.caret

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/index.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/index.jade b/modules/control-center-web/src/main/js/views/index.jade
new file mode 100644
index 0000000..8a7c4f5
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/index.jade
@@ -0,0 +1,141 @@
+//-
+    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
+
+append scripts
+    script(src='//cdn.rawgit.com/twbs/bootstrap/master/js/carousel.js')
+
+mixin lbl(txt)
+    label.col-xs-3.col-md-3.required #{txt}
+
+block body
+    header#header.header
+        .container
+            h1.navbar-brand
+                a(href='/') Apache Ignite Web Configurator
+            p.navbar-text(style='font-size: 18px;') Apache Ignite Web Console
+
+    .container.body-container
+        .main-content(ng-controller='auth')
+            .row.greedy.home
+                .col-xs-12.col-md-6
+                    form(name='loginForm')
+                        .modal-body.row(style='padding: 0; margin: 0')
+                            .settings-row(ng-if='action == "register"')
+                                h3.login-header Sign Up
+                            .settings-row(ng-if='action == "login"')
+                                h3.login-header Sign In
+                            .settings-row(ng-if='action == "password/forgot"')
+                                h3.login-header Forgot password?
+                            .settings-row
+                                p.col-xs-12.col-md-11(ng-show='action == "password/forgot"')
+                                    | That's ok! Simply enter your email below and a reset password link will be sent to you via email. You can then follow that link and select a new password.
+                            .settings-row(ng-show='action == "register"')
+                                +lbl('Full Name:')
+                                .col-xs-9.col-md-8
+                                    input#user_name.form-control(enter-focus-next='user_email' type='text' ng-model='user_info.username' placeholder='John Smith' ng-required='action=="register"')
+                            .settings-row
+                                +lbl('Email:')
+                                .col-xs-9.col-md-8
+                                    input#user_email.form-control(enter-focus-next='user_password' type='email' ng-model='user_info.email' placeholder='you@domain.com' required on-enter='action == "password/forgot" && loginForm.$valid && auth(action, user_info)')
+                            .settings-row(ng-show='action != "password/forgot"')
+                                +lbl('Password:')
+                                .col-xs-9.col-md-8
+                                    input#user_password.form-control(enter-focus-next='user_confirm' type='password' ng-model='user_info.password' placeholder='Password' ng-required='action != "password/forgot"' on-enter='action == "login" && loginForm.$valid && auth(action, user_info)')
+                            .settings-row(ng-if='action == "register"')
+                                +lbl('Confirm:')
+                                .col-xs-9.col-md-8
+                                    input#user_confirm.form-control(type='password' ng-model='user_info.confirm' match='user_info.password' placeholder='Confirm password' ng-required='action == "register"' on-enter='loginForm.$valid && auth(action, user_info)')
+                        .col-xs-12.col-md-11
+                            .login-footer(ng-show='action == "register"')
+                                a.labelField(ng-click='action = "password/forgot"' on-click-focus='user_email') Forgot password?
+                                a.labelLogin(ng-click='action = "login"' on-click-focus='user_email') Sign In
+                                button#signup.btn.btn-primary(ng-click='auth(action, user_info)' ng-disabled='loginForm.$invalid') Sign Up
+                        .col-xs-12.col-md-11
+                            .login-footer(ng-show='action == "password/forgot"')
+                                a.labelField(ng-click='action = "login"' on-click-focus='user_email') Sign In
+                                button#forgot.btn.btn-primary(ng-click='auth(action, user_info)' ng-disabled='loginForm.$invalid') Send it to me
+                        .col-xs-12.col-md-11
+                            .login-footer(ng-show='action == "login"')
+                                a.labelField(ng-click='action = "password/forgot"' on-click-focus='user_email') Forgot password?
+                                a.labelLogin(ng-click='action = "register"' on-click-focus='user_name') Sign Up
+                                button#login.btn.btn-primary(ng-click='auth(action, user_info)' ng-disabled='loginForm.$invalid') Sign In
+
+                    .col-xs-12.col-md-11.home-panel
+                        p Apache Ignite Web Console is an interactive web management tool which allows users to:
+                        ul
+                            li Create and download various configurations for Apache Ignite
+                            li Automatically load SQL metadata form any RDBMS
+                            li Connect to Ignite cluster and run SQL analytics on it
+                .col-xs-12.col-md-6
+                    #carousel.carousel.slide(data-ride='carousel')
+                        // Indicators
+                        ol.carousel-indicators
+                            li.active(data-target='#carousel', data-slide-to='0')
+                            li(data-target='#carousel', data-slide-to='1')
+                            li(data-target='#carousel', data-slide-to='2')
+                            li(data-target='#carousel', data-slide-to='3')
+                            li(data-target='#carousel', data-slide-to='4')
+                            li(data-target='#carousel', data-slide-to='5')
+                            li(data-target='#carousel', data-slide-to='6')
+                        // Wrapper for slides
+                        .carousel-inner(role='listbox')
+                            .item.active
+                                img(src='/images/cluster.png', alt='Cluster screen')
+                                .carousel-caption
+                                    h3 Clusters screen
+                                    p Configure clusters, link clusters to caches
+                            .item
+                                img(src='/images/cache.png', alt='Caches screen')
+                                .carousel-caption
+                                    h3 Caches screen
+                                    p Configure caches, link metadata to caches, link caches to clusters
+                            .item
+                                img(src='/images/metadata.png', alt='Metadatas screen')
+                                .carousel-caption
+                                    h3 Metadata screen
+                                    p Manually enter metadata or load from database
+                            .item
+                                img(src='/images/summary.png', alt='Summary screen')
+                                .carousel-caption
+                                    h3 Summary screen
+                                    p Download XML config, JAVA code, Docker file
+                            .item
+                                img(src='/images/query-table.png', alt='Query')
+                                .carousel-caption
+                                    h3 Query
+                                    p Explain SQL, execute, scan queries
+                            .item
+                                img(src='/images/query-metadata.png', alt='Cache metadata')
+                                .carousel-caption
+                                    h3 Cache metadata
+                                    p View cache type metadata
+                            .item
+                                img(src='/images/query-chart.png', alt='Query chart')
+                                .carousel-caption
+                                    h3 Query chart
+                                    p View data in tabular form and as charts
+                        // Controls
+                        a.left.carousel-control(href='#carousel', role='button', data-slide='prev')
+                            span.fa.fa-chevron-left(aria-hidden='true')
+                            span.sr-only Previous
+                        a.right.carousel-control(href='#carousel', role='button', data-slide='next')
+                            span.fa.fa-chevron-right(aria-hidden='true')
+                            span.sr-only Next
+    include includes/footer
+
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/reset.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/reset.jade b/modules/control-center-web/src/main/js/views/reset.jade
new file mode 100644
index 0000000..08bd521
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/reset.jade
@@ -0,0 +1,38 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+extends templates/layout
+
+block container
+    .row(ng-init='email = #{JSON.stringify(email)}; token = #{JSON.stringify(token)}; error=#{JSON.stringify(error)}')
+        .text-center(ng-if='!token')
+            p Further instructions for password reset have been sent to your e-mail address.
+        .text-center(ng-if='error')
+            p {{::error}}
+        div(ng-controller='auth' ng-if='token && !error')
+            form.form-horizontal(name='resetForm' ng-init='reset_info.token = token')
+                .settings-row
+                    label.col-sm-1 E-mail:
+                    label {{::email}}
+                .settings-row
+                    label.col-sm-1.required Password:
+                    .col-sm-3
+                        input#user_password.form-control(enter-focus-next='user_confirm' type='password' ng-model='reset_info.password' placeholder='New password' required)
+                .settings-row
+                    label.col-sm-1.required Confirm:
+                    .col-sm-3
+                        input#user_confirm.form-control(type='password' ng-model='reset_info.confirm' match='reset_info.password' placeholder='Confirm new password' required on-enter='resetForm.$valid && resetPassword(user_info)')
+            button.btn.btn-primary(ng-disabled='resetForm.$invalid' ng-click='resetPassword(reset_info)') Reset Password

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/settings/admin.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/settings/admin.jade b/modules/control-center-web/src/main/js/views/settings/admin.jade
new file mode 100644
index 0000000..46210cb
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/settings/admin.jade
@@ -0,0 +1,57 @@
+//-
+    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
+
+append scripts
+    script(src='/admin-controller.js')
+
+block container
+    .row(ng-controller='adminController')
+        .docs-content
+            .docs-header
+                h1 List of registered users
+                hr
+            .docs-body
+                table.table.table-striped.admin(st-table='displayedUsers' st-safe-src='users')
+                    thead
+                        tr
+                            th.header(colspan='5')
+                                .col-sm-2.pull-right
+                                    input.form-control(type='text' st-search='' placeholder='Filter users...')
+                        tr
+                            th(st-sort='username') User name
+                            th(st-sort='email') Email
+                            th.col-sm-2(st-sort='lastLogin') Last login
+                            th(width='1%'  st-sort='admin') Admin
+                            th(width='1%') Actions
+                    tbody
+                        tr(ng-repeat='row in displayedUsers')
+                            td {{row.username}}
+                            td
+                                a(ng-href='mailto:{{row.email}}') {{row.email}}
+                            td
+                                span {{row.lastLogin | date:'medium'}}
+                            td(style='text-align: center;')
+                                input(type='checkbox' ng-disabled='row.adminChanging || row._id == user._id'
+                                    ng-model='row.admin' ng-change='toggleAdmin(row)')
+                            td(style='text-align: center;')
+                                div(ng-show='row._id != user._id')
+                                    i.fa.fa-remove(ng-click='removeUser(row)' bs-tooltip data-title='Remove user')
+                                    i.fa.fa-eye(ng-click='becomeUser(row)' style='margin-left: 5px'  bs-tooltip data-title='Become this user')
+                    tfoot
+                        tr
+                            td.text-right(colspan='5')
+                                div(st-pagination st-items-by-page='15' st-displayed-pages='5')
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/settings/profile.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/settings/profile.jade b/modules/control-center-web/src/main/js/views/settings/profile.jade
new file mode 100644
index 0000000..d45f52f
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/settings/profile.jade
@@ -0,0 +1,68 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+extends ../templates/layout
+
+mixin lbl(txt)
+    label.col-sm-2.required.labelFormField #{txt}
+
+append scripts
+    script(src='/profile-controller.js')
+
+block container
+    .row(ng-controller='profileController')
+        .docs-content
+            .docs-header
+                h1 User profile
+                hr
+            .docs-body
+                form.form-horizontal(name='profileForm' novalidate)
+                    .col-sm-10(style='padding: 0')
+                        .details-row
+                            +lbl('User name:')
+                            .col-xs-5.col-sm-4
+                                input.form-control(id='profile-username' type='text' ng-model='profileUser.username' placeholder='Input name' required auto-focus)
+                        .details-row
+                            +lbl('Email:')
+                            .col-xs-5.col-sm-4
+                                input.form-control(id='profile-email' type='email' ng-model='profileUser.email' placeholder='you@domain.com' required)
+                        .details-row
+                            .advanced-options
+                                i.fa.fa-chevron-circle-up(ng-show='profileUser.showToken' ng-click='profileUser.showToken = ! profileUser.showToken')
+                                i.fa.fa-chevron-circle-down(ng-show='!profileUser.showToken' ng-click='profileUser.showToken = ! profileUser.showToken')
+                                a(ng-click='profileUser.showToken = ! profileUser.showToken') {{profileUser.showToken ? 'Hide security token...' : 'Show security token...'}}
+                        .details-row(ng-show='profileUser.showToken')
+                            +lbl('Security token:')
+                            label {{profileUser.token}}
+                            i.tipLabel.fa.fa-refresh(ng-click='generateToken()' bs-tooltip data-title='Generate random security token')
+                            i.tipLabel.fa.fa-clipboard(ng-click-copy='{{profileUser.token}}' bs-tooltip data-title='Copy security token to clipboard')
+                            i.tipLabel.fa.fa-question-circle(ng-if=lines bs-tooltip='' data-title='The security token is used for authorization of web agent')
+                        .details-row
+                            .advanced-options
+                                i.fa.fa-chevron-circle-up(ng-show='profileUser.changePassword' ng-click='profileUser.changePassword = ! profileUser.changePassword')
+                                i.fa.fa-chevron-circle-down(ng-show='!profileUser.changePassword' ng-click='profileUser.changePassword = ! profileUser.changePassword')
+                                a(ng-click='profileUser.changePassword = ! profileUser.changePassword') {{profileUser.changePassword ? 'Cancel password changing...' : 'Change password...'}}
+                        div(ng-if='profileUser.changePassword')
+                            .details-row
+                                +lbl('New password:')
+                                .col-xs-5.col-sm-4
+                                    input.form-control(type='password' ng-model='profileUser.newPassword' placeholder='New password' ng-required='profileUser.changePassword')
+                            .details-row
+                                +lbl('Confirm:')
+                                .col-xs-5.col-sm-4
+                                    input.form-control(type='password' ng-model='profileUser.confirmPassword' match='profileUser.newPassword' placeholder='Confirm new password' ng-required='profileUser.changePassword')
+                    .col-xs-12.col-sm-12.details-row
+                        a.btn.btn-primary(ng-disabled='!profileCouldBeSaved()' ng-click='profileCouldBeSaved() && saveUser()' bs-tooltip='' data-title='{{saveBtnTipText()}}' data-placement='bottom' data-trigger='hover') Save

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/sql/cache-metadata.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/sql/cache-metadata.jade b/modules/control-center-web/src/main/js/views/sql/cache-metadata.jade
new file mode 100644
index 0000000..dd3fd88
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/sql/cache-metadata.jade
@@ -0,0 +1,26 @@
+//-
+    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.
+.popover.cache-metadata(tabindex='-1' dw-loading='loadingCacheMetadata' dw-loading-options='{text: ""}' ng-init='tryLoadMetadata(cache)')
+    h3.popover-title Metadata for: <b>{{cache.name}}</b></br>Cache mode: <b>{{cache.mode}}</b>
+    button.close(id='cache-metadata-close' ng-click='$hide()') &times;
+    .popover-content(ng-if='cache.metadata && cache.metadata.length > 0')
+        treecontrol.tree-classic(tree-model='cache.metadata' options='treeOptions')
+            label.clickable(ng-if='node.type == "type"' ng-dblclick='dblclickMetadata(paragraph, node)') {{node.name}}
+            label.clickable(ng-if='node.type == "field"' ng-dblclick='dblclickMetadata(paragraph, node)') {{node.name}} [{{node.clazz}}]
+            label(ng-if='node.type == "indexes"') {{node.name}}
+            label(ng-if='node.type == "index"') {{node.name}}
+            label.clickable(ng-if='node.type == "index-field"' ng-dblclick='dblclickMetadata(paragraph, node)') {{node.name}} [{{node.order ? 'ASC' : 'DESC'}}]
+    .popover-content(ng-if='!cache.metadata || cache.metadata.length == 0')
+        label.content-empty No types found
+    h3.popover-footer Double click to paste into editor

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/sql/chart-settings.jade
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/views/sql/chart-settings.jade b/modules/control-center-web/src/main/js/views/sql/chart-settings.jade
new file mode 100644
index 0000000..c1bedd1
--- /dev/null
+++ b/modules/control-center-web/src/main/js/views/sql/chart-settings.jade
@@ -0,0 +1,38 @@
+//-
+    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.
+
+.popover.settings(tabindex='-1')
+    .arrow
+    h3.popover-title(style='color: black') Chart settings
+    button.close(id='chart-settings-close' ng-click='$hide()') &times;
+    .popover-content
+        form.form-horizontal.chart-settings(name='chartSettingsForm' novalidate)
+            .form-group.chart-settings
+                label All columns (drag columns to axis)
+                ul.chart-settings-columns-list(dnd-list='paragraph.chartColumns' dnd-allowed-types='[]')
+                    li(ng-repeat='col in paragraph.chartColumns track by $index')
+                        .btn.btn-default.btn-chart-column-movable(dnd-draggable='col' dnd-effect-allowed='copy') {{col.label}}
+                label X axis (accept only one column)
+                ul.chart-settings-columns-list(dnd-list='paragraph.chartKeyCols' dnd-drop='chartAcceptKeyColumn(paragraph, item)')
+                    li(ng-repeat='col in paragraph.chartKeyCols track by $index')
+                        .btn.btn-info.btn-chart-column {{col.label}}
+                            i.fa.fa-close(ng-click='chartRemoveKeyColumn(paragraph, $index)')
+                label Y axis (accept only numeric columns)
+                ul.chart-settings-columns-list(dnd-list='paragraph.chartValCols' dnd-drop='chartAcceptValColumn(paragraph, item)')
+                    li(ng-repeat='col in paragraph.chartValCols track by $index')
+                        .btn.btn-success.btn-chart-column {{col.label}}
+                            i.fa.fa-close(ng-click='chartRemoveValColumn(paragraph, $index)')
+


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

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/agent.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/agent.js b/modules/control-center-web/src/main/js/routes/agent.js
new file mode 100644
index 0000000..607de15
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/agent.js
@@ -0,0 +1,261 @@
+/*
+ *
+ *  * 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 agentManager = require('../agents/agent-manager');
+
+var apacheIgnite = require('apache-ignite');
+var SqlFieldsQuery = apacheIgnite.SqlFieldsQuery;
+var ScanQuery = apacheIgnite.ScanQuery;
+
+function _client(req, res) {
+    var client = agentManager.getAgentManager().findClient(req.currentUserId());
+
+    if (!client) {
+        res.status(503).send('Client not found');
+
+        return null;
+    }
+
+    return client;
+}
+
+function _compact(className) {
+    return className.replace('java.lang.', '').replace('java.util.', '').replace('java.sql.', '');
+}
+
+/* Get grid topology. */
+router.get('/download', function (req, res) {
+    res.render('templates/agent-download');
+});
+
+/* Get grid topology. */
+router.post('/topology', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        client.ignite().cluster().then(function (clusters) {
+            var caches = clusters.map(function (cluster) {
+                return Object.keys(cluster._caches).map(function (key) {
+                    return {name: key, mode: cluster._caches[key]}
+                });
+            });
+
+            res.json(_.uniq(_.reject(_.flatten(caches), { mode: 'LOCAL' }), function(cache) {
+                return cache.name;
+            }));
+        }, function (err) {
+            res.status(500).send(err);
+        });
+    }
+});
+
+/* Execute query. */
+router.post('/query', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        // Create sql query.
+        var qry = new SqlFieldsQuery(req.body.query);
+
+        // Set page size for query.
+        qry.setPageSize(req.body.pageSize);
+
+        // Get query cursor.
+        client.ignite().cache(req.body.cacheName).query(qry).nextPage().then(function (cursor) {
+            res.json({meta: cursor.fieldsMetadata(), rows: cursor.page(), queryId: cursor.queryId()});
+        }, function (err) {
+            res.status(500).send(err);
+        });
+    }
+});
+
+/* Execute query getAll. */
+router.post('/query/getAll', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        // Create sql query.
+        var qry = new SqlFieldsQuery(req.body.query);
+
+        // Set page size for query.
+        qry.setPageSize(1024);
+
+        // Get query cursor.
+        var cursor = client.ignite().cache(req.body.cacheName).query(qry);
+
+        cursor.getAll().then(function (rows) {
+            res.json({meta: cursor.fieldsMetadata(), rows: rows});
+        }, function (err) {
+            res.status(500).send(err);
+        });
+    }
+});
+
+/* Execute query. */
+router.post('/scan', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        // Create sql query.
+        var qry = new ScanQuery();
+
+        // Set page size for query.
+        qry.setPageSize(req.body.pageSize);
+
+        // Get query cursor.
+        client.ignite().cache(req.body.cacheName).query(qry).nextPage().then(function (cursor) {
+            res.json({meta: cursor.fieldsMetadata(), rows: cursor.page(), queryId: cursor.queryId()});
+        }, function (err) {
+            res.status(500).send(err);
+        });
+    }
+});
+
+/* Get next query page. */
+router.post('/query/fetch', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        var cache = client.ignite().cache(req.body.cacheName);
+
+        var cmd = cache._createCommand('qryfetch').addParam('qryId', req.body.queryId).
+            addParam('pageSize', req.body.pageSize);
+
+        cache.__createPromise(cmd).then(function (page) {
+            res.json({rows: page['items'], last: page === null || page['last']});
+        }, function (err) {
+            res.status(500).send(err);
+        });
+    }
+});
+
+/* Get metadata for cache. */
+router.post('/cache/metadata', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        client.ignite().cache(req.body.cacheName).metadata().then(function (meta) {
+            var tables = meta.types.map(function (typeName) {
+                var fields = meta.fields[typeName];
+
+                var showSystem = fields.length == 2 && fields["_KEY"] && fields["_VAL"];
+
+                var columns = [];
+
+                for (var fieldName in fields)
+                    if (showSystem || fieldName != "_KEY" && fieldName != "_VAL") {
+                        var fieldClass = _compact(fields[fieldName]);
+
+                        columns.push({
+                            type: 'field',
+                            name: fieldName,
+                            fullName: typeName + '.' + fieldName,
+                            clazz: fieldClass
+                        });
+                    }
+
+                var indexes = [];
+
+                for (var index of meta.indexes[typeName]) {
+                    fields = [];
+
+                    for (var field of index.fields) {
+                        fields.push({
+                            type: 'index-field',
+                            name: field,
+                            fullName: typeName + '.' + index.name + '.' + field,
+                            order: index.descendings.indexOf(field) < 0,
+                            unique: index.unique
+                        });
+                    }
+
+                    if (fields.length > 0)
+                        indexes.push({
+                            type: 'index',
+                            name: index.name,
+                            fullName: typeName + '.' + index.name,
+                            children: fields
+                        });
+                }
+
+                columns = _.sortBy(columns, 'name');
+
+                if (indexes.length > 0)
+                    columns = columns.concat({type: 'indexes', name: 'Indexes', fullName: typeName + '.indexes', children: indexes });
+
+                return {type: 'type', name: typeName, fullName: req.body.cacheName + '.' +typeName,  children: columns };
+            });
+
+            res.json(tables);
+        }, function (err) {
+            res.status(500).send(err);
+        });
+    }
+});
+
+/* Get JDBC drivers list. */
+router.post('/drivers', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        client.availableDrivers(function (err, drivers) {
+            if (err)
+                return res.status(500).send(err);
+
+            res.json(drivers);
+        });
+    }
+});
+
+/** Get database schemas. */
+router.post('/schemas', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        var params = req.body;
+
+        client.metadataSchemas(params.jdbcDriverJar, params.jdbcDriverClass, params.jdbcUrl, {user: params.user, password: params.password}, function (err, meta) {
+            if (err)
+                return res.status(500).send(err);
+
+            res.json(meta);
+        });
+    }
+});
+
+/** Get database metadata. */
+router.post('/metadata', function (req, res) {
+    var client = _client(req, res);
+
+    if (client) {
+        var params = req.body;
+
+        client.metadataTables(params.jdbcDriverJar, params.jdbcDriverClass, params.jdbcUrl,
+            {user: params.user, password: params.password}, params.schemas, params.tablesOnly,
+            function (err, meta) {
+                if (err)
+                    return res.status(500).send(err);
+
+                res.json(meta);
+            });
+    }
+});
+
+module.exports = router;

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/caches.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/caches.js b/modules/control-center-web/src/main/js/routes/caches.js
new file mode 100644
index 0000000..30a5547
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/caches.js
@@ -0,0 +1,171 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+var db = require('../db');
+
+/* GET caches page. */
+router.get('/', function (req, res) {
+    res.render('configuration/caches');
+});
+
+/**
+ * Get spaces and caches 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 clusters for spaces.
+            db.Cluster.find({space: {$in: space_ids}}, '_id name').sort('name').exec(function (err, clusters) {
+                if (db.processed(err, res)) {
+                    // Get all caches type metadata for spaces.
+                    db.CacheTypeMetadata.find({space: {$in: space_ids}}).sort('name').exec(function (err, metadatas) {
+                        if (db.processed(err, res)) {
+                            // Get all caches for spaces.
+                            db.Cache.find({space: {$in: space_ids}}).sort('name').exec(function (err, caches) {
+                                if (db.processed(err, res)) {
+                                    _.forEach(caches, function (cache) {
+                                        // Remove deleted clusters.
+                                        cache.clusters = _.filter(cache.clusters, function (clusterId) {
+                                            return _.findIndex(clusters, function (cluster) {
+                                                    return cluster._id.equals(clusterId);
+                                                }) >= 0;
+                                        });
+
+                                        // Remove deleted metadata.
+                                        cache.metadatas = _.filter(cache.metadatas, function (metaId) {
+                                            return _.findIndex(metadatas, function (meta) {
+                                                    return meta._id.equals(metaId);
+                                                }) >= 0;
+                                        });
+                                    });
+
+                                    res.json({
+                                        spaces: spaces,
+                                        clusters: clusters.map(function (cluster) {
+                                            return {value: cluster._id, label: cluster.name};
+                                        }),
+                                        metadatas: metadatas,
+                                        caches: caches
+                                    });
+                                }
+                            });
+                        }
+                    });
+                }
+            });
+        }
+    });
+});
+
+/**
+ * Save cache.
+ */
+router.post('/save', function (req, res) {
+    var params = req.body;
+    var cacheId = params._id;
+    var clusters = params.clusters;
+    var metadatas = params.metadatas;
+
+    if (params._id) {
+        db.Cache.update({_id: cacheId}, params, {upsert: true}, function (err) {
+            if (db.processed(err, res))
+                db.Cluster.update({_id: {$in: clusters}}, {$addToSet: {caches: cacheId}}, {multi: true}, function (err) {
+                    if (db.processed(err, res))
+                        db.Cluster.update({_id: {$nin: clusters}}, {$pull: {caches: cacheId}}, {multi: true}, function (err) {
+                            if (db.processed(err, res))
+                                db.CacheTypeMetadata.update({_id: {$in: metadatas}}, {$addToSet: {caches: cacheId}}, {multi: true}, function (err) {
+                                    if (db.processed(err, res))
+                                        db.CacheTypeMetadata.update({_id: {$nin: metadatas}}, {$pull: {caches: cacheId}}, {multi: true}, function (err) {
+                                            if (db.processed(err, res))
+                                                res.send(params._id);
+                                        });
+                                });
+                        });
+                });
+        })
+    }
+    else
+        db.Cache.findOne({space: params.space, name: params.name}, function (err, cache) {
+            if (db.processed(err, res)) {
+                if (cache)
+                    return res.status(500).send('Cache with name: "' + cache.name + '" already exist.');
+
+                (new db.Cache(params)).save(function (err, cache) {
+                    if (db.processed(err, res)) {
+                        cacheId = cache._id;
+
+                        db.Cluster.update({_id: {$in: clusters}}, {$addToSet: {caches: cacheId}}, {multi: true}, function (err) {
+                            if (db.processed(err, res))
+                                db.CacheTypeMetadata.update({_id: {$in: metadatas}}, {$addToSet: {caches: cacheId}}, {multi: true}, function (err) {
+                                    if (db.processed(err, res))
+                                        res.send(cacheId);
+                                });
+                        });
+                    }
+                });
+            }
+        });
+});
+
+/**
+ * Remove cache by ._id.
+ */
+router.post('/remove', function (req, res) {
+    db.Cache.remove(req.body, function (err) {
+        if (db.processed(err, res))
+            res.sendStatus(200);
+    })
+});
+
+/**
+ * Remove all caches.
+ */
+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.Cache.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/clusters.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/clusters.js b/modules/control-center-web/src/main/js/routes/clusters.js
new file mode 100644
index 0000000..59773e0
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/clusters.js
@@ -0,0 +1,145 @@
+/*
+ *
+ *  * 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 router = require('express').Router();
+var db = require('../db');
+
+/* GET clusters page. */
+router.get('/', function (req, res) {
+    res.render('configuration/clusters');
+});
+
+/**
+ * Get spaces and clusters 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').deepPopulate('metadatas').exec(function (err, caches) {
+                if (db.processed(err, res)) {
+                    // Get all clusters for spaces.
+                    db.Cluster.find({space: {$in: space_ids}}).sort('name').exec(function (err, clusters) {
+                        if (db.processed(err, res)) {
+                            // Remove deleted caches.
+                            _.forEach(clusters, function (cluster) {
+                                cluster.caches = _.filter(cluster.caches, function (cacheId) {
+                                    return _.findIndex(caches, function (cache) {
+                                            return cache._id.equals(cacheId);
+                                        }) >= 0;
+                                });
+                            });
+
+                            res.json({spaces: spaces, caches: caches, clusters: clusters});
+                        }
+                    });
+                }
+            });
+        }
+    });
+});
+
+/**
+ * Save cluster.
+ */
+router.post('/save', function (req, res) {
+    var params = req.body;
+    var clusterId = params._id;
+    var caches = params.caches;
+
+    if (params._id)
+        db.Cluster.update({_id: params._id}, params, {upsert: true}, function (err) {
+            if (db.processed(err, res))
+                db.Cache.update({_id: {$in: caches}}, {$addToSet: {clusters: clusterId}}, {multi: true}, function(err) {
+                    if (db.processed(err, res)) {
+                        db.Cache.update({_id: {$nin: caches}}, {$pull: {clusters: clusterId}}, {multi: true}, function(err) {
+                            if (db.processed(err, res))
+                                res.send(params._id);
+                        });
+                    }
+                });
+        });
+    else {
+        db.Cluster.findOne({space: params.space, name: params.name}, function (err, cluster) {
+            if (db.processed(err, res)) {
+                if (cluster)
+                    return res.status(500).send('Cluster with name: "' + cluster.name + '" already exist.');
+
+                (new db.Cluster(params)).save(function (err, cluster) {
+                    if (db.processed(err, res)) {
+                        clusterId = cluster._id;
+
+                        db.Cache.update({_id: {$in: caches}}, {$addToSet: {clusters: clusterId}}, {multi: true}, function (err) {
+                            if (db.processed(err, res))
+                                res.send(clusterId);
+                        });
+                    }
+                });
+            }
+        });
+    }
+});
+
+/**
+ * Remove cluster by ._id.
+ */
+router.post('/remove', function (req, res) {
+    db.Cluster.remove(req.body, function (err) {
+        if (err)
+            return res.status(500).send(err.message);
+
+        res.sendStatus(200);
+    })
+});
+
+/**
+ * Remove all clusters.
+ */
+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.Cluster.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/generator/generator-common.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-common.js b/modules/control-center-web/src/main/js/routes/generator/generator-common.js
new file mode 100644
index 0000000..ccd11e0
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-common.js
@@ -0,0 +1,353 @@
+/*
+ *
+ *  * 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');
+}
+
+// Entry point for common functions for code generation.
+$generatorCommon = {};
+
+// Add leading zero.
+$generatorCommon.addLeadingZero = function (numberStr, minSize) {
+    if (typeof (numberStr) != 'string')
+        numberStr = '' + numberStr;
+
+    while (numberStr.length < minSize) {
+        numberStr = '0' + numberStr;
+    }
+
+    return numberStr;
+};
+
+// Format date to string.
+$generatorCommon.formatDate = function (date) {
+    var dd = $generatorCommon.addLeadingZero(date.getDate(), 2);
+    var mm = $generatorCommon.addLeadingZero(date.getMonth() + 1, 2);
+
+    var yyyy = date.getFullYear();
+
+    return mm + '/' + dd + '/' + yyyy + ' ' + $generatorCommon.addLeadingZero(date.getHours(), 2) + ':' + $generatorCommon.addLeadingZero(date.getMinutes(), 2);
+};
+
+// Generate comment for generated XML, Java, ... files.
+$generatorCommon.mainComment = function mainComment() {
+    return 'This configuration was generated by Ignite Web Console (' + $generatorCommon.formatDate(new Date()) + ')';
+};
+
+// Create result holder with service functions and properties for XML and java code generation.
+$generatorCommon.builder = function () {
+    var res = [];
+
+    res.deep = 0;
+    res.needEmptyLine = false;
+    res.lineStart = true;
+    res.datasources = [];
+    res.imports = {};
+
+    res.safeDeep = 0;
+    res.safeNeedEmptyLine = false;
+    res.safeImports = {};
+    res.safeDatasources = [];
+    res.safePoint = -1;
+
+    function getLineStart() {
+        return this.lineStart ? _.repeat('    ', this.deep) : '';
+    }
+
+    res.startSafeBlock = function () {
+        res.safeDeep = this.deep;
+        this.safeNeedEmptyLine = this.needEmptyLine;
+        this.safeImports = _.cloneDeep(this.imports);
+        this.safeDatasources = this.datasources.slice();
+        this.safePoint = this.length;
+    };
+
+    res.rollbackSafeBlock = function () {
+        if (this.safePoint >= 0) {
+            this.splice(this.safePoint, this.length - this.safePoint);
+
+            this.deep = res.safeDeep;
+            this.needEmptyLine = this.safeNeedEmptyLine;
+            this.datasources = this.safeDatasources;
+            this.imports = this.safeImports;
+            this.safePoint = -1;
+        }
+    };
+
+    res.asString = function() {
+      return this.join('\n');
+    };
+
+    res.append = function (s) {
+        this.push((this.lineStart ? _.repeat('    ', this.deep) : '') + s);
+
+        return this;
+    };
+
+    res.line = function (s) {
+        if (s) {
+            if (this.needEmptyLine)
+                this.push('');
+
+            this.append(s);
+        }
+
+        this.needEmptyLine = false;
+
+        this.lineStart = true;
+
+        return this;
+    };
+
+    res.startBlock = function (s) {
+        if (s) {
+            if (this.needEmptyLine)
+                this.push('');
+
+            this.append(s);
+        }
+
+        this.needEmptyLine = false;
+
+        this.lineStart = true;
+
+        this.deep++;
+
+        return this;
+    };
+
+    res.endBlock = function (s) {
+        this.deep--;
+
+        if (s)
+            this.append(s);
+
+        this.lineStart = true;
+
+        return this;
+    };
+
+    res.emptyLineIfNeeded = function () {
+        if (this.needEmptyLine) {
+            this.push('');
+            this.lineStart = true;
+
+            this.needEmptyLine = false;
+        }
+    };
+
+    /**
+     * Add class to imports.
+     *
+     * @param clsName Full class name.
+     * @returns {String} Short class name or full class name in case of names conflict.
+     */
+    res.importClass = function (clsName) {
+        var fullClassName = $dataStructures.fullClassName(clsName);
+
+        var dotIdx = fullClassName.lastIndexOf('.');
+
+        var shortName = dotIdx > 0 ? fullClassName.substr(dotIdx + 1) : fullClassName;
+
+        if (this.imports[shortName]) {
+            if (this.imports[shortName] != fullClassName)
+                return fullClassName; // Short class names conflict. Return full name.
+        }
+        else
+            this.imports[shortName] = fullClassName;
+
+        return shortName;
+    };
+
+    /**
+     * @returns String with "java imports" section.
+     */
+    res.generateImports = function () {
+        var res = [];
+
+        for (var clsName in this.imports) {
+            if (this.imports.hasOwnProperty(clsName) && this.imports[clsName].lastIndexOf('java.lang.', 0) != 0)
+                res.push('import ' + this.imports[clsName] + ';');
+        }
+
+        res.sort();
+
+        return res.join('\n')
+    };
+
+    return res;
+};
+
+// Eviction policies code generation descriptors.
+$generatorCommon.EVICTION_POLICIES = {
+    LRU: {
+        className: 'org.apache.ignite.cache.eviction.lru.LruEvictionPolicy',
+        fields: {batchSize: null, maxMemorySize: null, maxSize: null}
+    },
+    RND: {
+        className: 'org.apache.ignite.cache.eviction.random.RandomEvictionPolicy',
+        fields: {maxSize: null}
+    },
+    FIFO: {
+        className: 'org.apache.ignite.cache.eviction.fifo.FifoEvictionPolicy',
+        fields: {batchSize: null, maxMemorySize: null, maxSize: null}
+    },
+    SORTED: {
+        className: 'org.apache.ignite.cache.eviction.sorted.SortedEvictionPolicy',
+        fields: {batchSize: null, maxMemorySize: null, maxSize: null}
+    }
+};
+
+// Marshaller code generation descriptors.
+$generatorCommon.MARSHALLERS = {
+    OptimizedMarshaller: {
+        className: 'org.apache.ignite.marshaller.optimized.OptimizedMarshaller',
+        fields: {poolSize: null, requireSerializable: null }
+    },
+    JdkMarshaller: {
+        className: 'org.apache.ignite.marshaller.jdk.JdkMarshaller',
+        fields: {}
+    }
+};
+
+// Pairs of supported databases and their JDBC dialects.
+$generatorCommon.JDBC_DIALECTS = {
+    Oracle: 'org.apache.ignite.cache.store.jdbc.dialect.OracleDialect',
+    DB2: 'org.apache.ignite.cache.store.jdbc.dialect.DB2Dialect',
+    SQLServer: 'org.apache.ignite.cache.store.jdbc.dialect.SQLServerDialect',
+    MySQL: 'org.apache.ignite.cache.store.jdbc.dialect.MySQLDialect',
+    PostgreSQL: 'org.apache.ignite.cache.store.jdbc.dialect.BasicJdbcDialect',
+    H2: 'org.apache.ignite.cache.store.jdbc.dialect.H2Dialect'
+};
+
+// Return JDBC dialect full class name for specified database.
+$generatorCommon.jdbcDialectClassName = function(db) {
+    var dialectClsName = $generatorCommon.JDBC_DIALECTS[db];
+
+    return dialectClsName ? dialectClsName : 'Unknown database: ' + db;
+};
+
+// Pairs of supported databases and their data sources.
+$generatorCommon.DATA_SOURCES = {
+    Oracle: 'oracle.jdbc.pool.OracleDataSource',
+    DB2: 'com.ibm.db2.jcc.DB2DataSource',
+    SQLServer: 'com.microsoft.sqlserver.jdbc.SQLServerDataSource',
+    MySQL: 'com.mysql.jdbc.jdbc2.optional.MysqlDataSource',
+    PostgreSQL: 'org.postgresql.ds.PGPoolingDataSource',
+    H2: 'org.h2.jdbcx.JdbcDataSource'
+};
+
+// Return data source full class name for specified database.
+$generatorCommon.dataSourceClassName = function(db) {
+    var dsClsName = $generatorCommon.DATA_SOURCES[db];
+
+    return dsClsName ? dsClsName : 'Unknown database: ' + db;
+};
+
+// Store factories code generation descriptors.
+$generatorCommon.STORE_FACTORIES = {
+    CacheJdbcPojoStoreFactory: {
+        className: 'org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory',
+        fields: {dataSourceBean: null, dialect: {type: 'jdbcDialect'}}
+    },
+    CacheJdbcBlobStoreFactory: {
+        className: 'org.apache.ignite.cache.store.jdbc.CacheJdbcBlobStoreFactory',
+        fields: {
+            user: null,
+            dataSourceBean: null,
+            initSchema: null,
+            createTableQuery: null,
+            loadQuery: null,
+            insertQuery: null,
+            updateQuery: null,
+            deleteQuery: null
+        }
+    },
+    CacheHibernateBlobStoreFactory: {
+        className: 'org.apache.ignite.cache.store.hibernate.CacheHibernateBlobStoreFactory',
+        fields: {hibernateProperties: {type: 'propertiesAsList', propVarName: 'props'}}
+    }
+};
+
+// Swap space SPI code generation descriptor.
+$generatorCommon.SWAP_SPACE_SPI = {
+    className: 'org.apache.ignite.spi.swapspace.file.FileSwapSpaceSpi',
+    fields: {
+        baseDirectory: {type: 'path'},
+        readStripesNumber: null,
+        maximumSparsity: {type: 'float'},
+        maxWriteQueueSize: null,
+        writeBufferSize: null
+    }
+};
+
+// Transaction configuration code generation descriptor.
+$generatorCommon.TRANSACTION_CONFIGURATION = {
+    className: 'org.apache.ignite.configuration.TransactionConfiguration',
+    fields: {
+        defaultTxConcurrency: {type: 'enum', enumClass: 'org.apache.ignite.transactions.TransactionConcurrency'},
+        transactionIsolation: {
+            type: 'org.apache.ignite.transactions.TransactionIsolation',
+            setterName: 'defaultTxIsolation'
+        },
+        defaultTxTimeout: null,
+        pessimisticTxLogLinger: null,
+        pessimisticTxLogSize: null,
+        txSerializableEnabled: null,
+        txManagerLookupClassName: null
+    }
+};
+
+// SSL configuration code generation descriptor.
+$generatorCommon.SSL_CONFIGURATION_TRUST_FILE_FACTORY = {
+    className: 'org.apache.ignite.ssl.SslContextFactory',
+    fields: {
+        keyAlgorithm: null,
+        keyStoreFilePath: {type: 'path'},
+        keyStorePassword: {type: 'raw'},
+        keyStoreType: null,
+        protocol: null,
+        trustStoreFilePath: {type: 'path'},
+        trustStorePassword: {type: 'raw'},
+        trustStoreType: null
+    }
+};
+
+// SSL configuration code generation descriptor.
+$generatorCommon.SSL_CONFIGURATION_TRUST_MANAGER_FACTORY = {
+    className: 'org.apache.ignite.ssl.SslContextFactory',
+    fields: {
+        keyAlgorithm: null,
+        keyStoreFilePath: {type: 'path'},
+        keyStorePassword: {type: 'raw'},
+        keyStoreType: null,
+        protocol: null,
+        trustManagers: {type: 'array'}
+    }
+};
+
+// For server side we should export Java code generation entry point.
+if (typeof window === 'undefined') {
+    module.exports = $generatorCommon;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/generator/generator-docker.js
----------------------------------------------------------------------
diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-docker.js b/modules/control-center-web/src/main/js/routes/generator/generator-docker.js
new file mode 100644
index 0000000..676cc94
--- /dev/null
+++ b/modules/control-center-web/src/main/js/routes/generator/generator-docker.js
@@ -0,0 +1,60 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+// Docker file generation entry point.
+$generatorDocker = {};
+
+// Generate Docker file for cluster.
+$generatorDocker.clusterDocker = function (cluster, os) {
+    if (!os)
+        os = 'debian:8';
+
+    return '# Start from a OS image.\n' +
+        'FROM ' + os + '\n' +
+        '\n' +
+        '# Install tools.\n' +
+        'RUN apt-get update && apt-get install -y --fix-missing \\\n' +
+        '  wget \\\n' +
+        '  dstat \\\n' +
+        '  maven \\\n' +
+        '  git\n' +
+        '\n' +
+        '# Install Java. \n' +
+        'RUN \\\n' +
+        'apt-get update && \\\n' +
+        'apt-get install -y openjdk-7-jdk && \\\n' +
+        'rm -rf /var/lib/apt/lists/*\n' +
+        '\n' +
+        '# Define commonly used JAVA_HOME variable.\n' +
+        'ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64\n' +
+        '\n' +
+        '# Create working directory\n' +
+        'WORKDIR /home\n' +
+        '\n' +
+        'RUN wget -O ignite.zip http://tiny.cc/updater/download_ignite.php && unzip ignite.zip && rm ignite.zip\n' +
+        '\n' +
+        'COPY *.xml /tmp/\n' +
+        '\n' +
+        'RUN mv /tmp/*.xml /home/$(ls)/config';
+};
+
+// For server side we should export Java code generation entry point.
+if (typeof window === 'undefined') {
+    module.exports = $generatorDocker;
+}


[02/18] ignite git commit: ignite-843 Agent initial commit

Posted by an...@apache.org.
ignite-843 Agent initial commit


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

Branch: refs/heads/ignite-843-rc1
Commit: 81962c43ae38b643fbf043496db34118114f79bb
Parents: 6844370
Author: Andrey <an...@gridgain.com>
Authored: Tue Oct 13 10:05:15 2015 +0700
Committer: Andrey <an...@gridgain.com>
Committed: Tue Oct 13 10:05:15 2015 +0700

----------------------------------------------------------------------
 modules/control-center-agent/README.txt         |  85 ++++
 .../assembly/release-control-center-agent.xml   |  75 ++++
 .../bin/ignite-web-agent.bat                    |  18 +
 .../bin/ignite-web-agent.sh                     |  34 ++
 .../jdbc-drivers/README.txt                     |  10 +
 modules/control-center-agent/pom.xml            | 154 +++++++
 .../apache/ignite/agent/AgentConfiguration.java | 280 +++++++++++++
 .../org/apache/ignite/agent/AgentLauncher.java  | 168 ++++++++
 .../ignite/agent/AgentLoggingConfigurator.java  |  90 ++++
 .../org/apache/ignite/agent/AgentSocket.java    | 191 +++++++++
 .../org/apache/ignite/agent/AgentUtils.java     |  74 ++++
 .../handlers/DatabaseMetadataExtractor.java     | 208 ++++++++++
 .../ignite/agent/handlers/RestExecutor.java     | 175 ++++++++
 .../org/apache/ignite/agent/remote/Remote.java  |  39 ++
 .../ignite/agent/remote/RemoteHandler.java      | 253 ++++++++++++
 .../ignite/agent/remote/WebSocketSender.java    |  41 ++
 .../agent/testdrive/AgentMetadataTestDrive.java |  90 ++++
 .../agent/testdrive/AgentSqlTestDrive.java      | 414 +++++++++++++++++++
 .../ignite/agent/testdrive/model/Car.java       | 157 +++++++
 .../ignite/agent/testdrive/model/CarKey.java    |  99 +++++
 .../ignite/agent/testdrive/model/Country.java   | 128 ++++++
 .../agent/testdrive/model/CountryKey.java       |  99 +++++
 .../agent/testdrive/model/Department.java       | 186 +++++++++
 .../agent/testdrive/model/DepartmentKey.java    |  99 +++++
 .../ignite/agent/testdrive/model/Employee.java  | 360 ++++++++++++++++
 .../agent/testdrive/model/EmployeeKey.java      |  99 +++++
 .../ignite/agent/testdrive/model/Parking.java   | 128 ++++++
 .../agent/testdrive/model/ParkingKey.java       |  99 +++++
 .../src/main/resources/logging.properties       |  24 ++
 .../control-center-agent/test-drive/README.txt  |   4 +
 .../test-drive/test-drive.sql                   |  58 +++
 31 files changed, 3939 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/README.txt
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/README.txt b/modules/control-center-agent/README.txt
new file mode 100644
index 0000000..4c155b7
--- /dev/null
+++ b/modules/control-center-agent/README.txt
@@ -0,0 +1,85 @@
+Ignite Web Agent
+======================================
+Ignite Web Agent is a java standalone application that allow to connect Ignite Grid to Ignite Web Console.
+Ignite Web Agent communicates with grid nodes via REST interface and connects to Ignite Web Console via web-socket.
+
+Two main functions of Ignite Web Agent:
+ 1. Proxy between Ignite Web Console and Ignite Grid to execute SQL statements and collect metrics for monitoring.
+    You may need to specify URI for connect to Ignite REST server via "-n" option.
+
+ 2. Proxy between Ignite Web Console and user RDBMS to collect database metadata for later CacheTypeMetadata configuration.
+    You may need to copy JDBC driver into "./jdbc-drivers" subfolder or specify path via "-d" option.
+
+Usage example:
+    ignite-web-agent.sh -t 1a2b3c4d5f -s wss://console.example.com
+
+Test drive of Ignite Web Agent:
+    In order to simplify evaluation two test drive modes were implemented:
+
+    1) Get security token on Web Console "Profile" screen.
+
+    2) Test drive for metadata load from database. Activated by option: -tm or --test-drive-metadata.
+       In this mode an in-memory H2 database will started.
+       How to evaluate:
+         2.1) Go to Ignite Web Console "Metadata" screen.
+         2.2) Select "Load from database".
+         2.3) Select H2 driver and enter JDBC URL: "jdbc:h2:mem:test-drive-db".
+         2.4) You should see list of available schemas and tables. Select some of them and click "Save".
+
+    3) Test drive for SQL. Activated by option: -ts or --test-drive-sql.
+       In this mode internal Ignite node will be started. Cache created and populated with data.
+       How to evaluate:
+       3.1) Go to Ignite Web Console "SQL" menu and select "Create new notebook" menu item.
+       3.2) In notebook paragraph enter SQL queries for tables: "Country, Department, Employee" in "test-drive-employee" cache
+        and for tables: "Parking, Car" in "test-drive-car" cache.
+
+       For example:
+        3.3) select "test-drive-car" cache,
+        3.4) enter SQL:
+                select count(*) cnt, p.ParkingName from car c
+                 inner join PARKING p on (p.PARKINGID=c.PARKINGID)
+                group by c.PARKINGID order by p.ParkingName
+        3.5) Click "Execute" button. You should get some data in table.
+        3.6) Click charts buttons to see auto generated charts.
+
+Configuration file:
+    Should be a file with simple line-oriented format as described here: http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load(java.io.Reader)
+
+    Available entries names:
+        token
+        serverURI
+        nodeURI
+        driverFolder
+        test-drive-metadata
+        test-drive-sql
+
+    Example configuration file:
+        token=1a2b3c4d5f
+        serverURI=wss://console.example.com:3001
+        test-drive-sql=true
+
+Options:
+    -h, --help
+       Print this help message
+    -c, --config
+       Path to configuration file
+    -d, --driver-folder
+       Path to folder with JDBC drivers, default value: ./jdbc-drivers
+    -n, --node-uri
+       URI for connect to Ignite REST server, default value:
+       http://localhost:8080
+    -s, --server-uri
+       URI for connect to Ignite Web Console via web-socket protocol, default
+       value: wss://localhost:3001
+    -tm, --test-drive-metadata
+       Start H2 database with sample tables in same process. JDBC URL for
+       connecting to sample database: jdbc:h2:mem:test-drive-db
+    -ts, --test-drive-sql
+       Create cache and populate it with sample data for use in query
+    -t, --token
+       User's security token
+
+Ignite Web Agent Build Instructions
+==============================================
+If you want to build from sources run following command in Ignite project root folder:
+    mvn clean package -pl :ignite-control-center-agent -am -P control-center -DskipTests=true

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/assembly/release-control-center-agent.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/assembly/release-control-center-agent.xml b/modules/control-center-agent/assembly/release-control-center-agent.xml
new file mode 100644
index 0000000..76760d4
--- /dev/null
+++ b/modules/control-center-agent/assembly/release-control-center-agent.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ /*
+  ~  * 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.
+  ~  */
+  -->
+
+<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+    <id>release-ignite-web-agent</id>
+
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <fileSets>
+        <fileSet>
+            <directory>${basedir}/../indexing/target/libs</directory>
+            <outputDirectory>/jdbc-drivers</outputDirectory>
+            <includes>
+                <include>**/h2-*.jar</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>${basedir}</directory>
+            <outputDirectory>/</outputDirectory>
+            <includes>
+                <include>jdbc-drivers/*.*</include>
+                <include>test-drive/*.*</include>
+                <include>README*</include>
+                <include>LICENSE*</include>
+                <include>NOTICE*</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>${basedir}/bin</directory>
+            <outputDirectory>/</outputDirectory>
+            <filtered>true</filtered>
+            <includes>
+                <include>**/*.bat</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>${basedir}/bin</directory>
+            <outputDirectory>/</outputDirectory>
+            <filtered>true</filtered>
+            <fileMode>0755</fileMode>
+            <includes>
+                <include>**/*.sh</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>${project.build.directory}</directory>
+            <outputDirectory>/</outputDirectory>
+            <includes>
+                <include>ignite-web-agent-${project.version}.jar</include>
+            </includes>
+        </fileSet>
+    </fileSets>
+</assembly>

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/bin/ignite-web-agent.bat
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/bin/ignite-web-agent.bat b/modules/control-center-agent/bin/ignite-web-agent.bat
new file mode 100644
index 0000000..796ddf9
--- /dev/null
+++ b/modules/control-center-agent/bin/ignite-web-agent.bat
@@ -0,0 +1,18 @@
+::
+:: Licensed to the Apache Software Foundation (ASF) under one or more
+:: contributor license agreements.  See the NOTICE file distributed with
+:: this work for additional information regarding copyright ownership.
+:: The ASF licenses this file to You under the Apache License, Version 2.0
+:: (the "License"); you may not use this file except in compliance with
+:: the License.  You may obtain a copy of the License at
+::
+::      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.
+::
+
+java -jar ignite-web-agent-${version}.jar %*

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/bin/ignite-web-agent.sh
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/bin/ignite-web-agent.sh b/modules/control-center-agent/bin/ignite-web-agent.sh
new file mode 100644
index 0000000..9acdc5c
--- /dev/null
+++ b/modules/control-center-agent/bin/ignite-web-agent.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# 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.
+#
+
+SOURCE="${BASH_SOURCE[0]}"
+
+DIR="$( dirname "$SOURCE" )"
+
+while [ -h "$SOURCE" ]
+    do
+        SOURCE="$(readlink "$SOURCE")"
+
+        [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
+
+        DIR="$( cd -P "$( dirname "$SOURCE"  )" && pwd )"
+    done
+
+DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+
+java -jar ignite-web-agent-${version}.jar "$@"

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/jdbc-drivers/README.txt
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/jdbc-drivers/README.txt b/modules/control-center-agent/jdbc-drivers/README.txt
new file mode 100644
index 0000000..cad43b7
--- /dev/null
+++ b/modules/control-center-agent/jdbc-drivers/README.txt
@@ -0,0 +1,10 @@
+Ignite Web Agent
+======================================
+
+If you are are planning to load cache type metadata from your existing databases
+you need to copy JDBC drivers in this folder.
+
+This is default folder for JDBC drivers.
+
+Also, you could specify custom folder using option: "-d CUSTOM_PATH_TO_FOLDER_WITH_JDBC_DRIVERS".
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/pom.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/pom.xml b/modules/control-center-agent/pom.xml
new file mode 100644
index 0000000..0237f5f
--- /dev/null
+++ b/modules/control-center-agent/pom.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ /*
+  ~  * 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.
+  ~  */
+  -->
+
+<!--
+    POM file.
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent</relativePath>
+    </parent>
+
+    <artifactId>ignite-control-center-agent</artifactId>
+    <version>1.5.0-SNAPSHOT</version>
+
+    <properties>
+        <jetty.version>9.2.12.v20150709</jetty.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-schema-import-db</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty.websocket</groupId>
+            <artifactId>websocket-client</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.beust</groupId>
+            <artifactId>jcommander</artifactId>
+            <version>1.48</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-indexing</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-rest-http</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.ignite</groupId>
+                    <artifactId>ignite-log4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>ignite-web-agent-${project.version}</finalName>
+
+        <plugins>
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.5</version>
+
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.ignite.agent.AgentLauncher</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>2.4</version>
+
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+
+                        <configuration>
+                            <createDependencyReducedPom>false</createDependencyReducedPom>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.4</version>
+                <inherited>false</inherited>
+
+                <executions>
+                    <execution>
+                        <id>release-control-center-agent</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>assembly/release-control-center-agent.xml</descriptor>
+                            </descriptors>
+                            <finalName>ignite-web-agent-${project.version}</finalName>
+                            <outputDirectory>target</outputDirectory>
+                            <appendAssemblyId>false</appendAssemblyId>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java
new file mode 100644
index 0000000..63d02a3
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentConfiguration.java
@@ -0,0 +1,280 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent;
+
+import com.beust.jcommander.Parameter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * Agent configuration.
+ */
+public class AgentConfiguration {
+    /** Default server port. */
+    public static final int DFLT_SERVER_PORT = 3001;
+    /** Default Ignite node HTTP port. */
+    public static final int DFLT_NODE_PORT = 8080;
+    /** Default server URI. */
+    private static final String DFLT_SERVER_URI = "wss://localhost:3001";
+    /** Default Ignite node HTTP URI. */
+    private static final String DFLT_NODE_URI = "http://localhost:8080";
+    /** */
+    @Parameter(names = {"-t", "--token"}, description = "User's security token used to establish connection to Ignite Console.")
+    private String tok;
+
+    /** */
+    @Parameter(names = {"-s", "--server-uri"}, description = "URI for connect to Ignite Console via web-socket protocol" +
+        "           " +
+        "      Default value: wss://localhost:3001")
+    private String srvUri;
+
+    /** */
+    @Parameter(names = {"-n", "--node-uri"}, description = "URI for connect to Ignite node REST server" +
+        "                        " +
+        "      Default value: http://localhost:8080")
+    private String nodeUri;
+
+    /** */
+    @Parameter(names = {"-c", "--config"}, description = "Path to agent property file" +
+        "                                  " +
+        "      Default value: ./default.properties")
+    private String cfgPath;
+
+    /** */
+    @Parameter(names = {"-d", "--driver-folder"}, description = "Path to folder with JDBC drivers" +
+        "                             " +
+        "      Default value: ./jdbc-drivers")
+    private String driversFolder;
+
+    /** */
+    @Parameter(names = { "-tm", "--test-drive-metadata" },
+        description = "Start H2 database with sample tables in same process. " +
+            "JDBC URL for connecting to sample database: jdbc:h2:mem:test-drive-db")
+    private Boolean meta;
+
+    /** */
+    @Parameter(names = { "-ts", "--test-drive-sql" },
+        description = "Create cache and populate it with sample data for use in query")
+    private Boolean sql;
+
+    /** */
+    @Parameter(names = { "-h", "--help" }, help = true, description = "Print this help message")
+    private Boolean help;
+
+    /**
+     * @return Token.
+     */
+    public String token() {
+        return tok;
+    }
+
+    /**
+     * @param tok Token.
+     */
+    public void token(String tok) {
+        this.tok = tok;
+    }
+
+    /**
+     * @return Server URI.
+     */
+    public String serverUri() {
+        return srvUri;
+    }
+
+    /**
+     * @param srvUri URI.
+     */
+    public void serverUri(String srvUri) {
+        this.srvUri = srvUri;
+    }
+
+    /**
+     * @return Node URI.
+     */
+    public String nodeUri() {
+        return nodeUri;
+    }
+
+    /**
+     * @param nodeUri Node URI.
+     */
+    public void nodeUri(String nodeUri) {
+        this.nodeUri = nodeUri;
+    }
+
+    /**
+     * @return Configuration path.
+     */
+    public String configPath() {
+        return cfgPath == null ? "./default.properties" : cfgPath;
+    }
+
+    /**
+     * @return Configured drivers folder.
+     */
+    public String driversFolder() {
+        return driversFolder;
+    }
+
+    /**
+     * @param driversFolder Driver folder.
+     */
+    public void driversFolder(String driversFolder) {
+        this.driversFolder = driversFolder;
+    }
+
+    /**
+     * @return {@code true} If metadata test drive should be started.
+     */
+    public Boolean testDriveMetadata() {
+        return meta != null ? meta : false;
+    }
+
+    /**
+     * @param meta Set to {@code true} if metadata test drive should be started.
+     */
+    public void testDriveMetadata(Boolean meta) {
+        this.meta = meta;
+    }
+
+    /**
+     * @return {@code true} If SQL test drive should be started.
+     */
+    public Boolean testDriveSql() {
+        return sql != null ? sql : false;
+    }
+
+    /**
+     * @param sql Set to {@code true} if SQL test drive should be started.
+     */
+    public void testDriveSql(Boolean sql) {
+        this.sql = sql;
+    }
+
+    /**
+     * @return {@code true} If agent options usage should be printed.
+     */
+    public Boolean help() {
+        return help != null ? help : false;
+    }
+
+    /**
+     * @param cfgUrl URL.
+     */
+    public void load(URL cfgUrl) throws IOException {
+        Properties props = new Properties();
+
+        try (Reader reader = new InputStreamReader(cfgUrl.openStream())) {
+            props.load(reader);
+        }
+
+        String val = (String)props.remove("token");
+
+        if (val != null)
+            token(val);
+
+        val = (String)props.remove("serverURI");
+
+        if (val != null)
+            serverUri(val);
+
+        val = (String)props.remove("nodeURI");
+
+        if (val != null)
+            nodeUri(val);
+
+        val = (String)props.remove("driverFolder");
+
+        if (val != null)
+            driversFolder(val);
+
+        val = (String)props.remove("test-drive-metadata");
+
+        if (val != null)
+            testDriveMetadata(Boolean.valueOf(val));
+
+        val = (String)props.remove("test-drive-sql");
+
+        if (val != null)
+            testDriveSql(Boolean.valueOf(val));
+    }
+
+    /**
+     * @param cmd Command.
+     */
+    public void merge(AgentConfiguration cmd) {
+        if (tok == null)
+            token(cmd.token());
+
+        if (srvUri == null)
+            serverUri(cmd.serverUri());
+
+        if (srvUri == null)
+            serverUri(DFLT_SERVER_URI);
+
+        if (nodeUri == null)
+            nodeUri(cmd.nodeUri());
+
+        if (nodeUri == null)
+            nodeUri(DFLT_NODE_URI);
+
+        if (driversFolder == null)
+            driversFolder(cmd.driversFolder());
+
+        if (testDriveMetadata())
+            testDriveMetadata(true);
+
+        if (testDriveSql())
+            testDriveSql(true);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        if (tok != null)
+            sb.append("User's security token         : ").append(token()).append('\n');
+
+        sb.append("URI to Ignite node REST server: ").append(nodeUri == null ? DFLT_NODE_URI : nodeUri).append('\n');
+        sb.append("URI to Ignite Console server  : ").append(srvUri == null ? DFLT_SERVER_URI : srvUri).append('\n');
+        sb.append("Path to agent property file   : ").append(configPath()).append('\n');
+
+        String drvFld = driversFolder();
+
+        if (drvFld == null) {
+            File agentHome = AgentUtils.getAgentHome();
+
+            if (agentHome != null)
+                drvFld = new File(agentHome, "jdbc-drivers").getPath();
+        }
+
+        sb.append("Path to JDBC drivers folder   : ").append(drvFld).append('\n');
+
+        sb.append("Test-drive for load metadata  : ").append(testDriveMetadata()).append('\n');
+        sb.append("Test-drive for execute query  : ").append(testDriveSql());
+
+        return sb.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
new file mode 100644
index 0000000..c85e25c
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
@@ -0,0 +1,168 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent;
+
+import com.beust.jcommander.JCommander;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.ignite.agent.handlers.RestExecutor;
+import org.apache.ignite.agent.testdrive.AgentMetadataTestDrive;
+import org.apache.ignite.agent.testdrive.AgentSqlTestDrive;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+
+import static org.apache.ignite.agent.AgentConfiguration.DFLT_SERVER_PORT;
+
+/**
+ * Control Center Agent launcher.
+ */
+public class AgentLauncher {
+    /** */
+    private static final Logger log = Logger.getLogger(AgentLauncher.class.getName());
+    /** */
+    private static final int RECONNECT_INTERVAL = 3000;
+
+    /** Static initializer. */
+    static {
+        AgentLoggingConfigurator.configure();
+    }
+
+    /**
+     * @param args Args.
+     */
+    @SuppressWarnings("BusyWait")
+    public static void main(String[] args) throws Exception {
+        log.log(Level.INFO, "Starting Apache Ignite Control Center Agent...");
+
+        AgentConfiguration cfg = new AgentConfiguration();
+
+        JCommander jCommander = new JCommander(cfg, args);
+
+        String osName = System.getProperty("os.name").toLowerCase();
+
+        jCommander.setProgramName("ignite-web-agent." + (osName.contains("win") ? "bat" : "sh"));
+
+        String prop = cfg.configPath();
+
+        AgentConfiguration propCfg = new AgentConfiguration();
+
+        try {
+            propCfg.load(new File(prop).toURI().toURL());
+        }
+        catch (IOException ignore) {
+            log.log(Level.WARNING, "Failed to load agent property file: '" + prop + "'", ignore);
+        }
+
+        cfg.merge(propCfg);
+
+        if (cfg.help()) {
+            jCommander.usage();
+
+            return;
+        }
+
+        System.out.println();
+        System.out.println("Agent configuration:");
+        System.out.println(cfg);
+        System.out.println();
+
+        if (cfg.testDriveSql() && cfg.nodeUri() != null)
+            log.log(Level.WARNING,
+                "URI for connect to Ignite REST server will be ignored because --test-drive-sql option was specified.");
+
+        if (!cfg.testDriveSql() && !cfg.testDriveMetadata()) {
+            System.out.println("To start web-agent in test-drive mode, pass \"-tm\" and \"-ts\" parameters");
+            System.out.println();
+        }
+
+        if (cfg.token() == null) {
+            String webHost= "";
+
+            try {
+                webHost = new URI(cfg.serverUri()).getHost();
+            }
+            catch (URISyntaxException e) {
+                log.log(Level.SEVERE, "Failed to parse Ignite Web Console uri", e);
+
+                return;
+            }
+
+            System.out.println("Security token is required to establish connection to the web console.");
+            System.out.println(String.format("It is available on the Profile page: https://%s/profile", webHost));
+
+            System.out.print("Enter security token: ");
+
+            cfg.token(new String(System.console().readPassword()));
+        }
+
+        if (cfg.testDriveMetadata())
+            AgentMetadataTestDrive.testDrive();
+
+        if (cfg.testDriveSql())
+            AgentSqlTestDrive.testDrive(cfg);
+
+        RestExecutor restExecutor = new RestExecutor(cfg);
+
+        restExecutor.start();
+
+        try {
+            SslContextFactory sslCtxFactory = new SslContextFactory();
+
+            // Workaround for use self-signed certificate:
+            if (Boolean.getBoolean("trust.all"))
+                sslCtxFactory.setTrustAll(true);
+
+            WebSocketClient client = new WebSocketClient(sslCtxFactory);
+
+            client.setMaxIdleTimeout(Long.MAX_VALUE);
+
+            client.start();
+
+            try {
+                while (!Thread.interrupted()) {
+                    AgentSocket agentSock = new AgentSocket(cfg, restExecutor);
+
+                    log.log(Level.INFO, "Connecting to: " + cfg.serverUri());
+
+                    URI uri = URI.create(cfg.serverUri());
+
+                    if (uri.getPort() == -1)
+                        uri = URI.create(cfg.serverUri() + ":" + DFLT_SERVER_PORT);
+
+                    client.connect(agentSock, uri);
+
+                    agentSock.waitForClose();
+
+                    Thread.sleep(RECONNECT_INTERVAL);
+                }
+            }
+            finally {
+                client.stop();
+            }
+        }
+        finally {
+            restExecutor.stop();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java
new file mode 100644
index 0000000..4912b06
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLoggingConfigurator.java
@@ -0,0 +1,90 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.LogManager;
+
+/**
+ * Configurator for java.util.Logger.
+ */
+public class AgentLoggingConfigurator {
+    /** */
+    private static final String CFG_PATH_PROPERTY = "log.config.path";
+
+    /** */
+    private static final String PROPERTIES_FILE = "logging.properties";
+
+    /**
+     * Perform configure.
+     */
+    public static void configure() {
+        try {
+            if (System.getProperty(CFG_PATH_PROPERTY) != null) {
+                File logCfg = new File(System.getProperty(CFG_PATH_PROPERTY));
+
+                if (!logCfg.isFile()) {
+                    System.err.println("Failed to load logging configuration, file not found: " + logCfg);
+
+                    System.exit(1);
+                }
+
+                readConfiguration(logCfg);
+
+                return;
+            }
+
+            File agentHome = AgentUtils.getAgentHome();
+
+            if (agentHome != null) {
+                File logCfg = new File(agentHome, PROPERTIES_FILE);
+
+                if (logCfg.isFile()) {
+                    readConfiguration(logCfg);
+
+                    return;
+                }
+            }
+
+            LogManager.getLogManager().readConfiguration(AgentLauncher.class.getResourceAsStream("/"
+                + PROPERTIES_FILE));
+        }
+        catch (IOException e) {
+            System.err.println("Failed to load logging configuration.");
+
+            e.printStackTrace();
+
+            System.exit(1);
+        }
+    }
+
+    /**
+     * @param file File.
+     */
+    private static void readConfiguration(File file) throws IOException {
+        try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
+            LogManager.getLogManager().readConfiguration(in);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
new file mode 100644
index 0000000..12b87b9
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
@@ -0,0 +1,191 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLHandshakeException;
+import org.apache.ignite.agent.handlers.DatabaseMetadataExtractor;
+import org.apache.ignite.agent.handlers.RestExecutor;
+import org.apache.ignite.agent.remote.Remote;
+import org.apache.ignite.agent.remote.RemoteHandler;
+import org.apache.ignite.agent.remote.WebSocketSender;
+import org.apache.ignite.agent.testdrive.AgentMetadataTestDrive;
+import org.apache.ignite.agent.testdrive.AgentSqlTestDrive;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/**
+ * Handler for web-socket connection.
+ */
+@WebSocket
+public class AgentSocket implements WebSocketSender {
+    /** */
+    public static final Gson GSON = new Gson();
+    /** */
+    public static final JsonParser PARSER = new JsonParser();
+    /** */
+    private static final Logger log = Logger.getLogger(AgentSocket.class.getName());
+    /** */
+    private final CountDownLatch closeLatch = new CountDownLatch(1);
+
+    /** */
+    private final AgentConfiguration cfg;
+
+    /** */
+    private final RestExecutor restExecutor;
+
+    /** */
+    private RemoteHandler remote;
+
+    /** */
+    private Session ses;
+
+    /**
+     * @param cfg Config.
+     */
+    public AgentSocket(AgentConfiguration cfg, RestExecutor restExecutor) {
+        this.cfg = cfg;
+        this.restExecutor = restExecutor;
+    }
+
+    /**
+     * @param statusCode Status code.
+     * @param reason Reason.
+     */
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason) {
+        log.log(Level.WARNING, String.format("Connection closed: %d - %s.", statusCode, reason));
+
+        if (remote != null)
+            remote.close();
+
+        closeLatch.countDown();
+    }
+
+    /**
+     * @param ses Session.
+     */
+    @OnWebSocketConnect
+    public void onConnect(Session ses) {
+        log.log(Level.INFO, "Connection established.");
+
+        this.ses = ses;
+
+        remote = RemoteHandler.wrap(this, this, restExecutor, new DatabaseMetadataExtractor(cfg));
+
+        JsonObject authMsg = new JsonObject();
+
+        authMsg.addProperty("type", "AuthMessage");
+        authMsg.addProperty("token", cfg.token());
+
+        send(authMsg);
+    }
+
+    /**
+     * @param msg Message.
+     * @return Whether or not message was sent.
+     */
+    @Override public boolean send(JsonObject msg) {
+        return send(GSON.toJson(msg));
+    }
+
+    /**
+     * @param msg Message.
+     * @return Whether or not message was sent.
+     */
+    @Override public boolean send(String msg) {
+        try {
+            ses.getRemote().sendString(msg);
+
+            return true;
+        }
+        catch (IOException ignored) {
+            log.log(Level.SEVERE, "Failed to send message to Control Center.");
+
+            return false;
+        }
+    }
+
+    /**
+     * @param ses Session.
+     * @param error Error.
+     */
+    @OnWebSocketError
+    public void onError(Session ses, Throwable error) {
+        if (error instanceof ConnectException)
+            log.log(Level.WARNING, error.getMessage());
+        else if (error instanceof SSLHandshakeException) {
+            log.log(Level.SEVERE, "Failed to establish SSL connection to Ignite Console. Start agent with " +
+                "\"-Dtrust.all=true\" to skip certificate validation in case of using self-signed certificate.", error);
+
+            System.exit(1);
+        }
+        else
+            log.log(Level.SEVERE, "Connection error.", error);
+
+        if (remote != null)
+            remote.close();
+
+        closeLatch.countDown();
+    }
+
+    /**
+     * @param msg Message.
+     */
+    @OnWebSocketMessage
+    public void onMessage(String msg) {
+        JsonElement jsonElement = PARSER.parse(msg);
+
+        remote.onMessage((JsonObject)jsonElement);
+    }
+
+    /**
+     * @param errorMsg Authentication failed message or {@code null} if authentication success.
+     */
+    @Remote
+    public void authResult(String errorMsg) {
+        if (errorMsg != null) {
+            onClose(401, "Authentication failed: " + errorMsg);
+
+            System.exit(1);
+        }
+
+        log.info("Authentication success.");
+    }
+
+    /**
+     * Await socket close.
+     */
+    public void waitForClose() throws InterruptedException {
+        closeLatch.await();
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java
new file mode 100644
index 0000000..d59e2d0
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentUtils.java
@@ -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.
+ *
+ */
+
+package org.apache.ignite.agent;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.ProtectionDomain;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility methods.
+ */
+public class AgentUtils {
+    /** */
+    private static final Logger log = Logger.getLogger(AgentUtils.class.getName());
+
+    /**
+     * Default constructor.
+     */
+    private AgentUtils() {
+        // No-op.
+    }
+
+    /**
+     * @return App folder.
+     */
+    public static File getAgentHome() {
+        try {
+            ProtectionDomain domain = AgentLauncher.class.getProtectionDomain();
+
+            // Should not happen, but to make sure our code is not broken.
+            if (domain == null || domain.getCodeSource() == null || domain.getCodeSource().getLocation() == null) {
+                log.log(Level.WARNING, "Failed to resolve agent jar location!");
+
+                return null;
+            }
+
+            // Resolve path to class-file.
+            URI classesUri = domain.getCodeSource().getLocation().toURI();
+
+            boolean win = System.getProperty("os.name").toLowerCase().contains("win");
+
+            // Overcome UNC path problem on Windows (http://www.tomergabel.com/JavaMishandlesUNCPathsOnWindows.aspx)
+            if (win && classesUri.getAuthority() != null)
+                classesUri = new URI(classesUri.toString().replace("file://", "file:/"));
+
+            return new File(classesUri).getParentFile();
+        }
+        catch (URISyntaxException | SecurityException ignored) {
+            log.log(Level.WARNING, "Failed to resolve agent jar location!");
+
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java
new file mode 100644
index 0000000..be84f48
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/DatabaseMetadataExtractor.java
@@ -0,0 +1,208 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.handlers;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.ignite.agent.AgentConfiguration;
+import org.apache.ignite.agent.AgentUtils;
+import org.apache.ignite.agent.remote.Remote;
+import org.apache.ignite.schema.parser.DbMetadataReader;
+import org.apache.ignite.schema.parser.DbTable;
+
+/**
+ * Remote API to extract database metadata.
+ */
+public class DatabaseMetadataExtractor {
+    /** */
+    private static final Logger log = Logger.getLogger(DatabaseMetadataExtractor.class.getName());
+
+    /** */
+    private final String driversFolder;
+
+    /**
+     * @param cfg Config.
+     */
+    public DatabaseMetadataExtractor(AgentConfiguration cfg) {
+        String driversFolder = cfg.driversFolder();
+
+        if (driversFolder == null) {
+            File agentHome = AgentUtils.getAgentHome();
+
+            if (agentHome != null)
+                driversFolder = new File(agentHome, "jdbc-drivers").getPath();
+        }
+
+        this.driversFolder = driversFolder;
+    }
+
+    /**
+     * @param jdbcDriverJarPath JDBC driver JAR path.
+     * @param jdbcDriverCls JDBC driver class.
+     * @param jdbcUrl JDBC URL.
+     * @param jdbcInfo Properties to connect to database.
+     * @return Connection to database.
+     * @throws SQLException
+     */
+    private Connection connect(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, Properties jdbcInfo) throws SQLException {
+        if (!new File(jdbcDriverJarPath).isAbsolute() && driversFolder != null)
+            jdbcDriverJarPath = new File(driversFolder, jdbcDriverJarPath).getPath();
+
+        return DbMetadataReader.getInstance().connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo);
+    }
+
+    /**
+     * @param jdbcDriverJarPath JDBC driver JAR path.
+     * @param jdbcDriverCls JDBC driver class.
+     * @param jdbcUrl JDBC URL.
+     * @param jdbcInfo Properties to connect to database.
+     * @return Collection of schema names.
+     * @throws SQLException
+     */
+    @Remote
+    public Collection<String> schemas(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl,
+        Properties jdbcInfo) throws SQLException {
+        log.log(Level.INFO, "Collecting database schemas...");
+
+        try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) {
+            Collection<String> schemas = DbMetadataReader.getInstance().schemas(conn);
+
+            log.log(Level.INFO, "Collected schemas: " + schemas.size());
+
+            return schemas;
+        }
+    }
+
+    /**
+     * @param jdbcDriverJarPath JDBC driver JAR path.
+     * @param jdbcDriverCls JDBC driver class.
+     * @param jdbcUrl JDBC URL.
+     * @param jdbcInfo Properties to connect to database.
+     * @param schemas List of schema names to process.
+     * @param tblsOnly If {@code true} then only tables will be processed otherwise views also will be processed.
+     * @return Collection of tables.
+     */
+    @Remote
+    public Collection<DbTable> metadata(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl,
+        Properties jdbcInfo, List<String> schemas, boolean tblsOnly) throws SQLException {
+        log.log(Level.INFO, "Collecting database metadata...");
+
+        try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) {
+            Collection<DbTable> metadata = DbMetadataReader.getInstance().metadata(conn, schemas, tblsOnly);
+
+            log.log(Level.INFO, "Collected metadata: " + metadata.size());
+
+            return metadata;
+        }
+    }
+
+    /**
+     * @param path Path to normalize.
+     * @return Normalized file path.
+     */
+    private String normalizePath(String path) {
+        return path != null ? path.replace('\\', '/') : null;
+    }
+
+    /**
+     * @return Drivers in drivers folder
+     * @see AgentConfiguration#driversFolder
+     */
+    @Remote
+    public List<JdbcDriver> availableDrivers() {
+        String drvFolder = normalizePath(driversFolder);
+
+        log.log(Level.INFO, "Collecting JDBC drivers in folder: " + drvFolder);
+
+        if (drvFolder == null) {
+            log.log(Level.INFO, "JDBC drivers folder not specified, returning empty list");
+
+            return Collections.emptyList();
+        }
+
+        String[] list = new File(drvFolder).list();
+
+        if (list == null) {
+            log.log(Level.INFO, "JDBC drivers folder has no files, returning empty list");
+
+            return Collections.emptyList();
+        }
+
+        List<JdbcDriver> res = new ArrayList<>();
+
+        for (String fileName : list) {
+            if (fileName.endsWith(".jar")) {
+                try {
+                    String spec = normalizePath("jar:file:" + (drvFolder.startsWith("/") ? "" : "/") + drvFolder + '/' + fileName +
+                        "!/META-INF/services/java.sql.Driver");
+
+                    URL url = new URL(spec);
+
+                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
+                        String jdbcDriverCls = reader.readLine();
+
+                        res.add(new JdbcDriver(fileName, jdbcDriverCls));
+
+                        log.log(Level.INFO, "Found: [driver=" + fileName + ", class=" + jdbcDriverCls + "]");
+                    }
+                }
+                catch (IOException e) {
+                    res.add(new JdbcDriver(fileName, null));
+
+                    log.log(Level.INFO, "Found: [driver=" + fileName + "]");
+                    log.log(Level.INFO, "Failed to detect driver class: " + e.getMessage());
+                }
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * Wrapper class for later to be transformed to JSON and send to Web Console.
+     */
+    private static class JdbcDriver {
+        /** */
+        private final String jdbcDriverJar;
+        /** */
+        private final String jdbcDriverClass;
+
+        /**
+         * @param jdbcDriverJar File name of driver jar file.
+         * @param jdbcDriverClass Optional JDBC driver class.
+         */
+        public JdbcDriver(String jdbcDriverJar, String jdbcDriverClass) {
+            this.jdbcDriverJar = jdbcDriverJar;
+            this.jdbcDriverClass = jdbcDriverClass;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java
new file mode 100644
index 0000000..f4c5dff
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/handlers/RestExecutor.java
@@ -0,0 +1,175 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.handlers;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.codec.Charsets;
+import org.apache.http.Header;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.ignite.agent.AgentConfiguration;
+import org.apache.ignite.agent.remote.Remote;
+
+import static org.apache.ignite.agent.AgentConfiguration.DFLT_NODE_PORT;
+
+/**
+ * Executor for REST requests.
+ */
+public class RestExecutor {
+    /** */
+    private final AgentConfiguration cfg;
+
+    /** */
+    private CloseableHttpClient httpClient;
+
+    /**
+     * @param cfg Config.
+     */
+    public RestExecutor(AgentConfiguration cfg) {
+        this.cfg = cfg;
+    }
+
+    /**
+     *
+     */
+    public void start() {
+        httpClient = HttpClientBuilder.create().build();
+    }
+
+    /**
+     *
+     */
+    public void stop() throws IOException {
+        if (httpClient != null)
+            httpClient.close();
+    }
+
+    /**
+     * @param path Path.
+     * @param mtd Method.
+     * @param params Params.
+     * @param headers Headers.
+     * @param body Body.
+     */
+    @Remote
+    public RestResult executeRest(String path, Map<String, String> params, String mtd, Map<String, String> headers,
+        String body) throws IOException, URISyntaxException {
+        URIBuilder builder = new URIBuilder(cfg.nodeUri());
+
+        if (builder.getPort() == -1)
+            builder.setPort(DFLT_NODE_PORT);
+
+        if (path != null) {
+            if (!path.startsWith("/") && !cfg.nodeUri().endsWith("/"))
+                path = '/' +  path;
+
+            builder.setPath(path);
+        }
+
+        if (params != null) {
+            for (Map.Entry<String, String> entry : params.entrySet())
+                builder.addParameter(entry.getKey(), entry.getValue());
+        }
+
+        HttpRequestBase httpReq;
+
+        if ("GET".equalsIgnoreCase(mtd))
+            httpReq = new HttpGet(builder.build());
+        else if ("POST".equalsIgnoreCase(mtd)) {
+            HttpPost post;
+
+            if (body == null) {
+                List<NameValuePair> nvps = builder.getQueryParams();
+
+                builder.clearParameters();
+
+                post = new HttpPost(builder.build());
+
+                if (!nvps.isEmpty())
+                    post.setEntity(new UrlEncodedFormEntity(nvps));
+            }
+            else {
+                post = new HttpPost(builder.build());
+
+                post.setEntity(new StringEntity(body));
+            }
+
+            httpReq = post;
+        }
+        else
+            throw new IOException("Unknown HTTP-method: " + mtd);
+
+        if (headers != null) {
+            for (Map.Entry<String, String> entry : headers.entrySet())
+                httpReq.addHeader(entry.getKey(), entry.getValue());
+        }
+
+        try (CloseableHttpResponse resp = httpClient.execute(httpReq)) {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+            resp.getEntity().writeTo(out);
+
+            Charset charset = Charsets.UTF_8;
+
+            Header encodingHdr = resp.getEntity().getContentEncoding();
+
+            if (encodingHdr != null) {
+                String encoding = encodingHdr.getValue();
+
+                charset = Charsets.toCharset(encoding);
+            }
+
+            return new RestResult(resp.getStatusLine().getStatusCode(), new String(out.toByteArray(), charset));
+        }
+    }
+
+    /**
+     * Request result.
+     */
+    public static class RestResult {
+        /** Status code. */
+        private int code;
+
+        /** Message. */
+        private String message;
+
+        /**
+         * @param code Code.
+         * @param msg Message.
+         */
+        public RestResult(int code, String msg) {
+            this.code = code;
+            message = msg;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
new file mode 100644
index 0000000..aec2f17
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
@@ -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.
+ *
+ */
+
+package org.apache.ignite.agent.remote;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to associate methods with remote NodeJS server commands.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Remote {
+    /**
+     * Whether or not method should be executed synchronously.
+     *
+     * @return {@code true} if method will be executed in separated thread otherwise if method will be executed in handler thread.
+     */
+    boolean async() default true;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java
new file mode 100644
index 0000000..5bbe609
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteHandler.java
@@ -0,0 +1,253 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.remote;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.http.auth.AuthenticationException;
+
+/**
+ * Allow to execute methods remotely from NodeJS server by web-socket command.
+ */
+public class RemoteHandler implements AutoCloseable {
+    /** */
+    public static final Gson GSON = new Gson();
+    /** */
+    public static final Object[] EMPTY_OBJECTS = new Object[0];
+    /** */
+    private static final Logger log = Logger.getLogger(RemoteHandler.class.getName());
+    /** */
+    private static final String INTERNAL_EXCEPTION_TYPE = "org.apache.ignite.agent.AgentException";
+    /** */
+    private final WebSocketSender snd;
+
+    /** */
+    private final Map<String, MethodDescriptor> mtds = new HashMap<>();
+
+    /** */
+    private final ExecutorService executorSrvc = Executors.newFixedThreadPool(Runtime.getRuntime()
+        .availableProcessors());
+
+    /**
+     * @param snd Session.
+     * @param hnds Handlers.
+     */
+    private RemoteHandler(WebSocketSender snd, Object ... hnds) {
+        this.snd = snd;
+
+        for (Object hnd : hnds) {
+            for (Method method : hnd.getClass().getMethods()) {
+                Remote remoteAnn = method.getAnnotation(Remote.class);
+
+                if (remoteAnn != null) {
+                    MethodDescriptor old = mtds.put(method.getName(), new MethodDescriptor(method, hnd,
+                        remoteAnn.async()));
+
+                    if (old != null)
+                        throw new IllegalArgumentException("Duplicated method: " + method.getName());
+                }
+            }
+        }
+    }
+
+    /**
+     * @param hnds Handler.
+     * @param snd Sender.
+     */
+    public static RemoteHandler wrap(WebSocketSender snd, Object ... hnds) {
+        return new RemoteHandler(snd, hnds);
+    }
+
+    /**
+     * @param req Request.
+     */
+    public void onMessage(JsonObject req) {
+        log.log(Level.FINE, "Message: " + req);
+
+        JsonPrimitive reqIdJson = req.getAsJsonPrimitive("reqId");
+
+        final Long reqId = reqIdJson == null ? null : reqIdJson.getAsLong();
+
+        String mtdName = req.getAsJsonPrimitive("mtdName").getAsString();
+
+        final MethodDescriptor desc = mtds.get(mtdName);
+
+        if (desc == null) {
+            sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Unknown method: " + mtdName);
+
+            return;
+        }
+
+        Type[] paramTypes = desc.mtd.getGenericParameterTypes();
+
+        JsonArray argsJson = req.getAsJsonArray("args");
+
+        final Object[] args;
+
+        if (paramTypes.length > 0) {
+            args = new Object[paramTypes.length];
+
+            if (argsJson == null || argsJson.size() != paramTypes.length) {
+                sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Inconsistent parameters");
+
+                return;
+            }
+
+            for (int i = 0; i < paramTypes.length; i++)
+                args[i] = GSON.fromJson(argsJson.get(i), paramTypes[i]);
+        }
+        else {
+            args = EMPTY_OBJECTS;
+
+            if (argsJson != null && argsJson.size() > 0) {
+                sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Inconsistent parameters");
+
+                return;
+            }
+        }
+
+        Runnable run = new Runnable() {
+            @Override public void run() {
+                final Object res;
+
+                try {
+                    res = desc.mtd.invoke(desc.hnd, args);
+                }
+                catch (Throwable e) {
+                    if (e instanceof AuthenticationException) {
+                        close();
+
+                        return;
+                    }
+
+                    if (e instanceof InvocationTargetException)
+                        e = ((InvocationTargetException)e).getTargetException();
+
+                    if (reqId != null)
+                        sendException(reqId, e.getClass().getName(), e.getMessage());
+                    else
+                        log.log(Level.SEVERE, "Exception on execute remote method.", e);
+
+                    return;
+                }
+
+                sendResponse(reqId, res, desc.returnType);
+            }
+        };
+
+        if (desc.async)
+            executorSrvc.submit(run);
+        else
+            run.run();
+    }
+
+    /**
+     * @param reqId Request id.
+     * @param exType Exception class name.
+     * @param exMsg Exception message.
+     */
+    protected void sendException(Long reqId, String exType, String exMsg) {
+        if (reqId == null)
+            return;
+
+        JsonObject res = new JsonObject();
+
+        res.addProperty("type", "CallRes");
+        res.addProperty("reqId", reqId);
+
+        JsonObject exJson = new JsonObject();
+        exJson.addProperty("type", exType);
+        exJson.addProperty("message", exMsg);
+
+        res.add("ex", exJson);
+
+        snd.send(res);
+    }
+
+    /**
+     * @param reqId Request id.
+     * @param res Result.
+     * @param type Type.
+     */
+    private void sendResponse(Long reqId, Object res, Type type) {
+        if (reqId == null)
+            return;
+
+        JsonObject resp = new JsonObject();
+
+        resp.addProperty("type", "CallRes");
+
+        resp.addProperty("reqId", reqId);
+
+        JsonElement resJson = type == void.class ? JsonNull.INSTANCE : GSON.toJsonTree(res, type);
+
+        resp.add("res", resJson);
+
+        snd.send(resp);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void close() {
+        executorSrvc.shutdown();
+    }
+
+    /**
+     *
+     */
+    private static class MethodDescriptor {
+        /** */
+        private final Method mtd;
+
+        /** */
+        private final Object hnd;
+
+        /** */
+        private final Type returnType;
+
+        /** */
+        private final boolean async;
+
+        /**
+         * @param mtd Method.
+         * @param hnd Handler.
+         * @param async Async.
+         */
+        MethodDescriptor(Method mtd, Object hnd, boolean async) {
+            this.mtd = mtd;
+            this.hnd = hnd;
+            this.async = async;
+
+            returnType = mtd.getGenericReturnType();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java
new file mode 100644
index 0000000..b686b27
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/WebSocketSender.java
@@ -0,0 +1,41 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.remote;
+
+import com.google.gson.JsonObject;
+
+/**
+ * Sender for messages to web-socket.
+ */
+public interface WebSocketSender {
+    /**
+     * Send message.
+     * @param msg Message.
+     * @return {@code true} if message sent successfully.
+     */
+    public boolean send(String msg);
+
+    /**
+     * Send message.
+     * @param msg Message.
+     * @return {@code true} if message sent successfully.
+     */
+    public boolean send(JsonObject msg);
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/81962c43/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java
new file mode 100644
index 0000000..09ceb53
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/testdrive/AgentMetadataTestDrive.java
@@ -0,0 +1,90 @@
+/*
+ *
+ *  * 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.
+ *
+ */
+
+package org.apache.ignite.agent.testdrive;
+
+import java.io.File;
+import java.io.FileReader;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.ignite.agent.AgentUtils;
+import org.h2.tools.RunScript;
+import org.h2.tools.Server;
+
+/**
+ * Test drive for metadata load from database.
+ *
+ * H2 database will be started and several tables will be created.
+ */
+public class AgentMetadataTestDrive {
+    /** */
+    private static final Logger log = Logger.getLogger(AgentMetadataTestDrive.class.getName());
+
+    /** */
+    private static final AtomicBoolean initLatch = new AtomicBoolean();
+
+    /**
+     * Execute query.
+     *
+     * @param conn Connection to database.
+     * @param qry Statement to execute.
+     */
+    private static void query(Connection conn, String qry) throws SQLException {
+        try (PreparedStatement ps = conn.prepareStatement(qry)) {
+            ps.executeUpdate();
+        }
+    }
+
+    /**
+     * Start H2 database and populate it with several tables.
+     */
+    public static void testDrive() {
+        if (initLatch.compareAndSet(false, true)) {
+            log.log(Level.FINE, "TEST-DRIVE: Prepare in-memory H2 database...");
+
+            try {
+                Connection conn = DriverManager.getConnection("jdbc:h2:mem:test-drive-db;DB_CLOSE_DELAY=-1", "sa", "");
+
+                File agentHome = AgentUtils.getAgentHome();
+
+                File sqlScript = new File((agentHome != null) ? new File(agentHome, "test-drive") : new File("test-drive"),
+                    "test-drive.sql");
+
+                RunScript.execute(conn, new FileReader(sqlScript));
+                log.log(Level.FINE, "TEST-DRIVE: Sample tables created.");
+
+                conn.close();
+
+                Server.createTcpServer("-tcpDaemon").start();
+
+                log.log(Level.INFO, "TEST-DRIVE: TcpServer stared.");
+
+                log.log(Level.INFO, "TEST-DRIVE: JDBC URL for test drive metadata load: jdbc:h2:mem:test-drive-db");
+            }
+            catch (Exception e) {
+                log.log(Level.SEVERE, "TEST-DRIVE: Failed to start test drive for metadata!", e);
+            }
+        }
+    }
+}