ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sboi...@apache.org
Subject [09/45] ignite git commit: Ignite Web Console speed up bundle rebuild and watch. Minor fixes.
Date Wed, 20 Jul 2016 09:29:20 GMT
http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js
index a0bd44c..eb52e51 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js
+++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js
@@ -15,13 +15,13 @@
  * limitations under the License.
  */
 
-import template from './ipc.jade!';
+import templateUrl from './ipc.jade';
 
 export default ['igniteConfigurationIgfsIpc', [() => {
     return {
         scope: true,
         restrict: 'E',
-        template,
+        templateUrl,
         replace: true
     };
 }]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js
index 6a6ca12..810944f 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js
+++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js
@@ -15,13 +15,13 @@
  * limitations under the License.
  */
 
-import template from './misc.jade!';
+import templateUrl from './misc.jade';
 
 export default ['igniteConfigurationIgfsMisc', [() => {
     return {
         scope: true,
         restrict: 'E',
-        template,
+        templateUrl,
         replace: true
     };
 }]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade
index fd42805..dc48d07 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade
+++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade
@@ -32,7 +32,7 @@ mixin table-igfs-path-mode-edit(prefix, focusId, index)
     .col-xs-8.col-sm-8.col-md-8
         .fieldSep /
         .input-tip
-            input.form-control(id=keyFocusId enter-focus-next=valFocusId type='text' ng-model=keyModel placeholder='Path' on-escape='tableReset()')
+            input.form-control(id=keyFocusId ignite-on-enter-focus-move=valFocusId type='text' ng-model=keyModel placeholder='Path' ignite-on-escape='tableReset()')
     .col-xs-4.col-sm-4.col-md-4
         -var arg = keyModel + ', ' + valModel
         -var btnVisible = 'tablePairSaveVisible(tblPathModes, ' + index + ')'
@@ -40,7 +40,7 @@ mixin table-igfs-path-mode-edit(prefix, focusId, index)
         -var btnVisibleAndSave = btnVisible + ' && ' + btnSave
         +btn-save(btnVisible, btnSave)
         .input-tip
-            button.select-toggle.form-control(id=valFocusId bs-select ng-model=valModel data-placeholder='Mode' bs-options='item.value as item.label for item in igfsModes' tabindex='0' on-enter=btnVisibleAndSave on-escape='tableReset()')
+            button.select-toggle.form-control(id=valFocusId bs-select ng-model=valModel data-placeholder='Mode' bs-options='item.value as item.label for item in igfsModes' tabindex='0' ignite-on-enter=btnVisibleAndSave ignite-on-escape='tableReset()')
 
 form.panel.panel-default(name=form novalidate)
     .panel-heading(bs-collapse-toggle='' ng-click='ui.loadPanel("#{form}")')
@@ -75,10 +75,10 @@ form.panel.panel-default(name=form novalidate)
                 .settings-row
                     +checkbox('Colocate metadata', model + '.colocateMetadata', 'colocateMetadata', 'Whether to co-locate metadata on a single node')
                 .settings-row
-                    +checkbox('Relaxed consistency ', model + '.relaxedConsistency', 'relaxedConsistency',
-                        'If value of this flag is <b>true</b>, IGFS will skip expensive consistency checks. It is recommended to set\
-                        this flag to <b>false</b>b> if your application has conflicting operations, or you do not how exactly users will\
-                        use your system.')
+                    +checkbox('Relaxed consistency', model + '.relaxedConsistency', 'relaxedConsistency',
+                        'If value of this flag is <b>true</b>, IGFS will skip expensive consistency checks<br/>\
+                        It is recommended to set this flag to <b>false</b> if your application has conflicting\
+                        operations, or you do not know how exactly users will use your system')
                 .settings-row
                     ignite-form-group(ng-model=pathModes ng-form=pathModesForm)
                         ignite-form-field-label

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js
index 8b3b37b..69179c0 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js
+++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js
@@ -15,13 +15,13 @@
  * limitations under the License.
  */
 
-import template from './secondary.jade!';
+import templateUrl from './secondary.jade';
 
 export default ['igniteConfigurationIgfsSecondary', [() => {
     return {
         scope: true,
         restrict: 'E',
-        template,
+        templateUrl,
         replace: true
     };
 }]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js
index fb67326..be7bf1e 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js
+++ b/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js
@@ -15,13 +15,13 @@
  * limitations under the License.
  */
 
-import ace from 'ace';
+import ace from 'brace';
 
 export default ['previewPanel', ['$interval', '$timeout', ($interval, $timeout) => {
     let animation = {editor: null, stage: 0, start: 0, stop: 0};
     let prevContent = [];
 
-    const Range = ace.require('ace/range').Range;
+    const Range = ace.acequire('ace/range').Range;
 
     const _clearSelection = (editor) => {
         _.forEach(editor.session.getMarkers(false), (marker) => {

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js b/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js
index 866d97f..1b19e3a 100644
--- a/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js
+++ b/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js
@@ -17,15 +17,16 @@
 
 import _ from 'lodash';
 import JSZip from 'jszip';
+import saver from 'file-saver';
 
 export default [
-    '$rootScope', '$scope', '$http', '$common', '$loading', '$filter', 'ConfigurationSummaryResource', 'JavaTypes', 'IgniteVersion', 'GeneratorDocker', 'GeneratorPom',
-    function($root, $scope, $http, $common, $loading, $filter, Resource, JavaTypes, IgniteVersion, docker, pom) {
+    '$rootScope', '$scope', '$http', 'IgniteLegacyUtils', 'IgniteLoading', '$filter', 'ConfigurationSummaryResource', 'JavaTypes', 'IgniteVersion', 'GeneratorDocker', 'GeneratorPom',
+    function($root, $scope, $http, LegacyUtils, Loading, $filter, Resource, JavaTypes, IgniteVersion, docker, pom) {
         const ctrl = this;
 
         $scope.ui = { ready: false };
 
-        $loading.start('summaryPage');
+        Loading.start('summaryPage');
 
         Resource.read().then(({clusters}) => {
             $scope.clusters = clusters;
@@ -38,7 +39,7 @@ export default [
                 return { _id, name };
             });
 
-            $loading.finish('summaryPage');
+            Loading.finish('summaryPage');
 
             $scope.ui.ready = true;
 
@@ -53,7 +54,7 @@ export default [
             return !row || !row._id || _.findIndex(rows, (item) => item._id === row._id) >= 0;
         };
 
-        $scope.widthIsSufficient = $common.widthIsSufficient;
+        $scope.widthIsSufficient = LegacyUtils.widthIsSufficient;
         $scope.dialects = {};
 
         $scope.projectStructureOptions = {
@@ -326,10 +327,8 @@ export default [
 
             $generatorOptional.optionalContent(zip, cluster);
 
-            const blob = zip.generate({type: 'blob', compression: 'DEFLATE', mimeType: 'application/octet-stream'});
-
-            // Download archive.
-            saveAs(blob, cluster.name + '-project.zip');
+            zip.generateAsync({type: 'blob', compression: 'DEFLATE', mimeType: 'application/octet-stream'})
+                .then((blob) => saver.saveAs(blob, cluster.name + '-project.zip'));
         };
 
         /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/signin.state.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/states/signin.state.js b/modules/web-console/src/main/js/app/modules/states/signin.state.js
index 91c84a3..a23a496 100644
--- a/modules/web-console/src/main/js/app/modules/states/signin.state.js
+++ b/modules/web-console/src/main/js/app/modules/states/signin.state.js
@@ -16,6 +16,7 @@
  */
 
 import angular from 'angular';
+import templateUrl from 'views/signin.jade';
 
 angular
 .module('ignite-console.states.login', [
@@ -28,7 +29,7 @@ angular
     $stateProvider
     .state('signin', {
         url: '/',
-        templateUrl: '/signin.html',
+        templateUrl,
         metaTags: {
         }
     });

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/user/Auth.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/user/Auth.service.js b/modules/web-console/src/main/js/app/modules/user/Auth.service.js
index f0aa397..080f4af 100644
--- a/modules/web-console/src/main/js/app/modules/user/Auth.service.js
+++ b/modules/web-console/src/main/js/app/modules/user/Auth.service.js
@@ -15,8 +15,8 @@
  * limitations under the License.
  */
 
-export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common', 'gettingStarted', 'User', 'IgniteAgentMonitor',
-    ($http, $root, $state, $window, $common, gettingStarted, User, agentMonitor) => {
+export default ['Auth', ['$http', '$rootScope', '$state', '$window', 'IgniteLegacyUtils', 'IgniteMessages', 'gettingStarted', 'User', 'IgniteAgentMonitor',
+    ($http, $root, $state, $window, LegacyUtils, Messages, gettingStarted, User, agentMonitor) => {
         let _auth = false;
 
         try {
@@ -44,7 +44,7 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common',
             forgotPassword(userInfo) {
                 return $http.post('/api/v1/password/forgot', userInfo)
                     .success(() => $state.go('password.send'))
-                    .error((err) => $common.showPopoverMessage(null, null, 'forgot_email', $common.errorMessage(err)));
+                    .error((err) => LegacyUtils.showPopoverMessage(null, null, 'forgot_email', Messages.errorMessage(null, err)));
             },
             auth(action, userInfo) {
                 return $http.post('/api/v1/' + action, userInfo)
@@ -61,7 +61,7 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common',
                             agentMonitor.init();
                         });
                     })
-                    .error((err) => $common.showPopoverMessage(null, null, action + '_email', $common.errorMessage(err)));
+                    .error((err) => LegacyUtils.showPopoverMessage(null, null, action + '_email', Messages.errorMessage(null, err)));
             },
             logout() {
                 return $http.post('/api/v1/logout')
@@ -70,7 +70,7 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common',
 
                         $window.open($state.href('signin'), '_self');
                     })
-                    .catch((err) => $common.showError(err));
+                    .catch(Messages.showError);
             }
         };
     }]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/ChartColors.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/ChartColors.service.js b/modules/web-console/src/main/js/app/services/ChartColors.service.js
index ec3f365..843aa5c 100644
--- a/modules/web-console/src/main/js/app/services/ChartColors.service.js
+++ b/modules/web-console/src/main/js/app/services/ChartColors.service.js
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import COLORS from 'app/data/colors.json!';
+import COLORS from 'app/data/colors.json';
 
 export default ['IgniteChartColors', function() {
     return COLORS;

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Clone.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/Clone.service.js b/modules/web-console/src/main/js/app/services/Clone.service.js
new file mode 100644
index 0000000..52a4e4e
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/Clone.service.js
@@ -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.
+ */
+
+// Service for clone objects.
+export default ['IgniteClone', ['$rootScope', '$q', '$modal', ($root, $q, $modal) => {
+    const scope = $root.$new();
+
+    let _names = [];
+    let deferred;
+    let _validator;
+
+    function _nextAvailableName(name) {
+        let num = 1;
+        let tmpName = name;
+
+        while (_.includes(_names, tmpName)) {
+            tmpName = name + '_' + num.toString();
+
+            num++;
+        }
+
+        return tmpName;
+    }
+
+    const cloneModal = $modal({templateUrl: '/templates/clone.html', scope, placement: 'center', show: false});
+
+    scope.ok = function(newName) {
+        if (!_validator || _validator(newName)) {
+            deferred.resolve(_nextAvailableName(newName));
+
+            cloneModal.hide();
+        }
+    };
+
+    cloneModal.confirm = function(oldName, names, validator) {
+        _names = names;
+
+        scope.newName = _nextAvailableName(oldName);
+
+        _validator = validator;
+
+        deferred = $q.defer();
+
+        cloneModal.$promise.then(cloneModal.show);
+
+        return deferred.promise;
+    };
+
+    return cloneModal;
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Confirm.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/Confirm.service.js b/modules/web-console/src/main/js/app/services/Confirm.service.js
new file mode 100644
index 0000000..c4e25a8
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/Confirm.service.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.
+ */
+
+// Confirm popup service.
+export default ['IgniteConfirm', ['$rootScope', '$q', '$modal', '$animate', ($root, $q, $modal, $animate) => {
+    const scope = $root.$new();
+
+    const modal = $modal({templateUrl: '/templates/confirm.html', scope, placement: 'center', show: false});
+
+    let deferred;
+
+    const _hide = (animate) => {
+        $animate.enabled(modal.$element, animate);
+
+        modal.hide();
+    };
+
+    scope.confirmYes = () => {
+        _hide(scope.animate);
+
+        deferred.resolve(true);
+    };
+
+    scope.confirmNo = () => {
+        _hide(scope.animate);
+
+        deferred.resolve(false);
+    };
+
+    scope.confirmCancel = () => {
+        _hide(true);
+
+        deferred.reject('cancelled');
+    };
+
+    /**
+     *
+     * @param {String } content
+     * @param {Boolean} [yesNo]
+     * @param {Boolean} [animate]
+     * @returns {Promise}
+     */
+    modal.confirm = (content, yesNo, animate) => {
+        scope.animate = !!animate;
+        scope.content = content || 'Confirm?';
+        scope.yesNo = !!yesNo;
+
+        deferred = $q.defer();
+
+        modal.$promise.then(modal.show);
+
+        return deferred.promise;
+    };
+
+    return modal;
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/ConfirmBatch.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/ConfirmBatch.service.js b/modules/web-console/src/main/js/app/services/ConfirmBatch.service.js
new file mode 100644
index 0000000..ef66335
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/ConfirmBatch.service.js
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+// Service for confirm or skip several steps.
+export default ['IgniteConfirmBatch', ['$rootScope', '$q', '$modal', ($root, $q, $modal) => {
+    const scope = $root.$new();
+
+    scope.confirmModal = $modal({
+        templateUrl: '/templates/batch-confirm.html',
+        scope,
+        placement: 'center',
+        show: false,
+        backdrop: 'static',
+        keyboard: false
+    });
+
+    const _done = (cancel) => {
+        scope.confirmModal.hide();
+
+        if (cancel)
+            scope.deferred.reject('cancelled');
+        else
+            scope.deferred.resolve();
+    };
+
+    const _nextElement = (skip) => {
+        scope.items[scope.curIx++].skip = skip;
+
+        if (scope.curIx < scope.items.length)
+            scope.content = scope.contentGenerator(scope.items[scope.curIx]);
+        else
+            _done();
+    };
+
+    scope.cancel = () => {
+        _done(true);
+    };
+
+    scope.skip = (applyToAll) => {
+        if (applyToAll) {
+            for (let i = scope.curIx; i < scope.items.length; i++)
+                scope.items[i].skip = true;
+
+            _done();
+        }
+        else
+            _nextElement(true);
+    };
+
+    scope.overwrite = (applyToAll) => {
+        if (applyToAll)
+            _done();
+        else
+            _nextElement(false);
+    };
+
+    return {
+        /**
+         * Show confirm all dialog.
+         *
+         * @param confirmMessageFn Function to generate a confirm message.
+         * @param itemsToConfirm Array of element to process by confirm.
+         */
+        confirm(confirmMessageFn, itemsToConfirm) {
+            scope.deferred = $q.defer();
+
+            scope.contentGenerator = confirmMessageFn;
+
+            scope.items = itemsToConfirm;
+            scope.curIx = 0;
+            scope.content = (scope.items && scope.items.length > 0) ? scope.contentGenerator(scope.items[0]) : null;
+
+            scope.confirmModal.$promise.then(scope.confirmModal.show);
+
+            return scope.deferred.promise;
+        }
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js b/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js
new file mode 100644
index 0000000..74c4764
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+// Service to copy some value to OS clipboard.
+export default ['IgniteCopyToClipboard', ['$window', 'IgniteMessages', ($window, Messages) => {
+    const body = angular.element($window.document.body);
+
+    const textArea = angular.element('<textarea/>');
+
+    textArea.css({
+        position: 'fixed',
+        opacity: '0'
+    });
+
+    return {
+        copy(toCopy) {
+            textArea.val(toCopy);
+
+            body.append(textArea);
+
+            textArea[0].select();
+
+            try {
+                if (document.execCommand('copy'))
+                    Messages.showInfo('Value copied to clipboard');
+                else
+                    window.prompt('Copy to clipboard: Ctrl+C, Enter', toCopy);  // eslint-disable-line no-alert
+            }
+            catch (err) {
+                window.prompt('Copy to clipboard: Ctrl+C, Enter', toCopy);  // eslint-disable-line no-alert
+            }
+
+            textArea.remove();
+        }
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Countries.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/Countries.service.js b/modules/web-console/src/main/js/app/services/Countries.service.js
index 87d11fd..5ad3e6a 100644
--- a/modules/web-console/src/main/js/app/services/Countries.service.js
+++ b/modules/web-console/src/main/js/app/services/Countries.service.js
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import COUNTRIES from 'app/data/countries.json!';
+import COUNTRIES from 'app/data/countries.json';
 
 export default ['IgniteCountries', function() {
     const indexByName = _.keyBy(COUNTRIES, 'name');

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Focus.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/Focus.service.js b/modules/web-console/src/main/js/app/services/Focus.service.js
new file mode 100644
index 0000000..a07e181
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/Focus.service.js
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+// Service to transfer focus for specified element.
+export default ['IgniteFocus', ['$timeout', ($timeout) => {
+    return {
+        move(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(() => {
+                const elem = $('#' + id);
+
+                if (elem.length > 0)
+                    elem[0].focus();
+            }, 100);
+        }
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/JavaTypes.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/JavaTypes.service.js b/modules/web-console/src/main/js/app/services/JavaTypes.service.js
index a755e13..d34d669 100644
--- a/modules/web-console/src/main/js/app/services/JavaTypes.service.js
+++ b/modules/web-console/src/main/js/app/services/JavaTypes.service.js
@@ -16,12 +16,12 @@
  */
 
 // Java built-in class names.
-import JAVA_CLASSES from 'app/data/java-classes.json!';
+import JAVA_CLASSES from 'app/data/java-classes.json';
 
 // Java build-in primitive.
-import JAVA_PRIMITIVES from 'app/data/java-primitives.json!';
+import JAVA_PRIMITIVES from 'app/data/java-primitives.json';
 
-import JAVA_KEYWORDS from 'app/data/java-keywords.json!';
+import JAVA_KEYWORDS from 'app/data/java-keywords.json';
 
 export default ['JavaTypes', function() {
     return {

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/LegacyTable.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/LegacyTable.service.js b/modules/web-console/src/main/js/app/services/LegacyTable.service.js
new file mode 100644
index 0000000..8f3b791
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/LegacyTable.service.js
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+// TODO: Refactor this service for legacy tables with more than one input field.
+export default ['IgniteLegacyTable', ['IgniteLegacyUtils', 'IgniteFocus', (LegacyUtils, Focus) => {
+    function _model(item, field) {
+        return LegacyUtils.getModel(item, field);
+    }
+
+    const table = {name: 'none', editIndex: -1};
+
+    function _tableReset() {
+        delete table.field;
+        table.name = 'none';
+        table.editIndex = -1;
+
+        LegacyUtils.hidePopover();
+    }
+
+    function _tableSaveAndReset() {
+        const field = table.field;
+
+        const save = LegacyUtils.isDefined(field) && LegacyUtils.isDefined(field.save);
+
+        if (!save || !LegacyUtils.isDefined(field) || field.save(field, table.editIndex, true)) {
+            _tableReset();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    function _tableState(field, editIndex, specName) {
+        table.field = field;
+        table.name = specName || field.model;
+        table.editIndex = editIndex;
+    }
+
+    function _tableUI(field) {
+        const ui = field.ui;
+
+        return ui ? ui : field.type;
+    }
+
+    function _tableFocus(focusId, index) {
+        Focus.move((index < 0 ? 'new' : 'cur') + focusId + (index >= 0 ? index : ''));
+    }
+
+    function _tablePairValue(filed, index) {
+        return index < 0 ? {key: filed.newKey, value: filed.newValue} : {key: filed.curKey, value: filed.curValue};
+    }
+
+    function _tableStartEdit(item, tbl, index, save) {
+        _tableState(tbl, index);
+
+        const val = _.get(_model(item, tbl), tbl.model)[index];
+
+        const ui = _tableUI(tbl);
+
+        tbl.save = save;
+
+        if (ui === 'table-pair') {
+            tbl.curKey = val[tbl.keyName];
+            tbl.curValue = val[tbl.valueName];
+
+            _tableFocus('Key' + tbl.focusId, index);
+        }
+        else if (ui === 'table-db-fields') {
+            tbl.curDatabaseFieldName = val.databaseFieldName;
+            tbl.curDatabaseFieldType = val.databaseFieldType;
+            tbl.curJavaFieldName = val.javaFieldName;
+            tbl.curJavaFieldType = val.javaFieldType;
+
+            _tableFocus('DatabaseFieldName' + tbl.focusId, index);
+        }
+        else if (ui === 'table-indexes') {
+            tbl.curIndexName = val.name;
+            tbl.curIndexType = val.indexType;
+            tbl.curIndexFields = val.fields;
+
+            _tableFocus(tbl.focusId, index);
+        }
+    }
+
+    function _tableNewItem(tbl) {
+        _tableState(tbl, -1);
+
+        const ui = _tableUI(tbl);
+
+        if (ui === 'table-pair') {
+            tbl.newKey = null;
+            tbl.newValue = null;
+
+            _tableFocus('Key' + tbl.focusId, -1);
+        }
+        else if (ui === 'table-db-fields') {
+            tbl.newDatabaseFieldName = null;
+            tbl.newDatabaseFieldType = null;
+            tbl.newJavaFieldName = null;
+            tbl.newJavaFieldType = null;
+
+            _tableFocus('DatabaseFieldName' + tbl.focusId, -1);
+        }
+        else if (ui === 'table-indexes') {
+            tbl.newIndexName = null;
+            tbl.newIndexType = 'SORTED';
+            tbl.newIndexFields = null;
+
+            _tableFocus(tbl.focusId, -1);
+        }
+    }
+
+    return {
+        tableState: _tableState,
+        tableReset: _tableReset,
+        tableSaveAndReset: _tableSaveAndReset,
+        tableNewItem: _tableNewItem,
+        tableNewItemActive(tbl) {
+            return table.name === tbl.model && table.editIndex < 0;
+        },
+        tableEditing(tbl, index) {
+            return table.name === tbl.model && table.editIndex === index;
+        },
+        tableEditedRowIndex() {
+            return table.editIndex;
+        },
+        tableField() {
+            return table.field;
+        },
+        tableStartEdit: _tableStartEdit,
+        tableRemove(item, field, index) {
+            _tableReset();
+
+            _.get(_model(item, field), field.model).splice(index, 1);
+        },
+        tablePairValue: _tablePairValue,
+        tablePairSave(pairValid, item, field, index, stopEdit) {
+            const valid = pairValid(item, field, index);
+
+            if (valid) {
+                const pairValue = _tablePairValue(field, index);
+
+                let pairModel = {};
+
+                const container = _.get(item, field.model);
+
+                if (index < 0) {
+                    pairModel[field.keyName] = pairValue.key;
+                    pairModel[field.valueName] = pairValue.value;
+
+                    if (container)
+                        container.push(pairModel);
+                    else
+                        _.set(item, field.model, [pairModel]);
+
+                    if (!stopEdit)
+                        _tableNewItem(field);
+                }
+                else {
+                    pairModel = container[index];
+
+                    pairModel[field.keyName] = pairValue.key;
+                    pairModel[field.valueName] = pairValue.value;
+
+                    if (!stopEdit) {
+                        if (index < container.length - 1)
+                            _tableStartEdit(item, field, index + 1);
+                        else
+                            _tableNewItem(field);
+                    }
+                }
+            }
+
+            return valid;
+        },
+        tablePairSaveVisible(field, index) {
+            const pairValue = _tablePairValue(field, index);
+
+            return !LegacyUtils.isEmptyString(pairValue.key) && !LegacyUtils.isEmptyString(pairValue.value);
+        },
+        tableFocusInvalidField(index, id) {
+            _tableFocus(id, index);
+
+            return false;
+        },
+        tableFieldId(index, id) {
+            return (index < 0 ? 'new' : 'cur') + id + (index >= 0 ? index : '');
+        }
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/LegacyUtils.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/LegacyUtils.service.js b/modules/web-console/src/main/js/app/services/LegacyUtils.service.js
new file mode 100644
index 0000000..c4c83e9
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/LegacyUtils.service.js
@@ -0,0 +1,948 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+// TODO: Refactor this service for legacy tables with more than one input field.
+export default ['IgniteLegacyUtils', [
+    '$alert', '$popover', '$anchorScroll', '$location', '$timeout', '$window', 'IgniteFocus',
+    ($alert, $popover, $anchorScroll, $location, $timeout, $window, Focus) => {
+        $anchorScroll.yOffset = 55;
+
+        function isDefined(v) {
+            return !_.isNil(v);
+        }
+
+        function isEmptyString(s) {
+            if (isDefined(s))
+                return s.trim().length === 0;
+
+            return true;
+        }
+
+        const javaBuiltInClasses = [
+            'BigDecimal', 'Boolean', 'Byte', 'Date', 'Double', 'Float', 'Integer', 'Long', 'Object', 'Short', 'String', 'Time', 'Timestamp', 'UUID'
+        ];
+
+        const javaBuiltInTypes = [
+            'BigDecimal', 'boolean', 'Boolean', 'byte', 'Byte', 'Date', 'double', 'Double', 'float', 'Float',
+            'int', 'Integer', 'long', 'Long', 'Object', 'short', 'Short', 'String', 'Time', 'Timestamp', 'UUID'
+        ];
+
+        const javaBuiltInFullNameClasses = [
+            '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.Object', 'java.lang.Short',
+            'java.lang.String', 'java.sql.Time', 'java.sql.Timestamp', 'java.util.UUID'
+        ];
+
+        /**
+         * @param clsName Class name to check.
+         * @returns {Boolean} 'true' if given class name is a java build-in type.
+         */
+        function isJavaBuiltInClass(clsName) {
+            if (isEmptyString(clsName))
+                return false;
+
+            return _.includes(javaBuiltInClasses, clsName) || _.includes(javaBuiltInFullNameClasses, clsName);
+        }
+
+        const SUPPORTED_JDBC_TYPES = [
+            'BIGINT',
+            'BIT',
+            'BOOLEAN',
+            'BLOB',
+            'CHAR',
+            'CLOB',
+            'DATE',
+            'DECIMAL',
+            'DOUBLE',
+            'FLOAT',
+            'INTEGER',
+            'LONGNVARCHAR',
+            'LONGVARCHAR',
+            'NCHAR',
+            'NUMERIC',
+            'NVARCHAR',
+            'REAL',
+            'SMALLINT',
+            'TIME',
+            'TIMESTAMP',
+            'TINYINT',
+            'VARCHAR'
+        ];
+
+        const ALL_JDBC_TYPES = [
+            {dbName: 'BIT', dbType: -7, javaType: 'Boolean', primitiveType: 'boolean'},
+            {dbName: 'TINYINT', dbType: -6, javaType: 'Byte', primitiveType: 'byte'},
+            {dbName: 'SMALLINT', dbType: 5, javaType: 'Short', primitiveType: 'short'},
+            {dbName: 'INTEGER', dbType: 4, javaType: 'Integer', primitiveType: 'int'},
+            {dbName: 'BIGINT', dbType: -5, javaType: 'Long', primitiveType: 'long'},
+            {dbName: 'FLOAT', dbType: 6, javaType: 'Float', primitiveType: 'float'},
+            {dbName: 'REAL', dbType: 7, javaType: 'Double', primitiveType: 'double'},
+            {dbName: 'DOUBLE', dbType: 8, javaType: 'Double', primitiveType: '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', primitiveType: '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'}
+        ];
+
+        /*eslint-disable */
+        const 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'
+        ];
+        /*eslint-enable */
+
+        const VALID_JAVA_IDENTIFIER = new RegExp('^[a-zA-Z_$][a-zA-Z\\d_$]*$');
+
+        let popover = null;
+
+        function isElementInViewport(el) {
+            const rect = el.getBoundingClientRect();
+
+            return (
+                rect.top >= 0 &&
+                rect.left >= 0 &&
+                rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
+                rect.right <= (window.innerWidth || document.documentElement.clientWidth)
+            );
+        }
+
+        const _showPopoverMessage = (id, message, showTime) => {
+            const body = $('body');
+
+            let el = body.find('#' + id);
+
+            if (!el || el.length === 0)
+                el = body.find('[name="' + id + '"]');
+
+            if (el && el.length > 0) {
+                if (!isElementInViewport(el[0])) {
+                    $location.hash(el[0].id);
+
+                    $anchorScroll();
+                }
+
+                const newPopover = $popover(el, {content: message});
+
+                popover = newPopover;
+
+                $timeout(() => newPopover.$promise.then(() => {
+                    newPopover.show();
+
+                    // Workaround to fix popover location when content is longer than content template.
+                    // https://github.com/mgcrea/angular-strap/issues/1497
+                    $timeout(newPopover.$applyPlacement);
+                }), 400);
+                $timeout(() => newPopover.hide(), showTime || 5000);
+            }
+        };
+
+        function ensureActivePanel(ui, pnl, focusId) {
+            if (ui) {
+                const collapses = $('div.panel-collapse');
+
+                ui.loadPanel(pnl);
+
+                const idx = _.findIndex(collapses, function(collapse) {
+                    return collapse.id === pnl;
+                });
+
+                if (idx >= 0) {
+                    const activePanels = ui.activePanels;
+
+                    if (!_.includes(ui.topPanels, idx)) {
+                        ui.expanded = true;
+
+                        const customExpanded = ui[pnl];
+
+                        if (customExpanded)
+                            ui[customExpanded] = true;
+                    }
+
+                    if (!activePanels || activePanels.length < 1)
+                        ui.activePanels = [idx];
+                    else if (!_.includes(activePanels, idx)) {
+                        const newActivePanels = angular.copy(activePanels);
+
+                        newActivePanels.push(idx);
+
+                        ui.activePanels = newActivePanels;
+                    }
+                }
+
+                if (isDefined(focusId))
+                    Focus.move(focusId);
+            }
+        }
+
+        function showPopoverMessage(ui, panelId, id, message, showTime) {
+            if (popover)
+                popover.hide();
+
+            if (ui) {
+                ensureActivePanel(ui, panelId);
+
+                $timeout(() => _showPopoverMessage(id, message, showTime), ui.isPanelLoaded(panelId) ? 200 : 500);
+            }
+            else
+                _showPopoverMessage(id, message, showTime);
+
+            return false;
+        }
+
+        function isValidJavaIdentifier(msg, ident, elemId, panels, panelId) {
+            if (isEmptyString(ident))
+                return showPopoverMessage(panels, panelId, elemId, msg + ' is invalid!');
+
+            if (_.includes(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;
+        }
+
+        let 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) {
+                const canvas = document.createElement('canvas');
+
+                context = canvas.getContext('2d');
+
+                const 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 (let nameIx = 0; nameIx < names.length; nameIx++) {
+                const s = names[nameIx];
+
+                if (s.length > nameLength) {
+                    let totalLength = s.length;
+
+                    const packages = s.split('.');
+
+                    const packageCnt = packages.length - 1;
+
+                    for (let 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) {
+                        const className = packages[packageCnt];
+
+                        const classNameLen = className.length;
+
+                        let remains = Math.min(nameLength - totalLength + classNameLen, classNameLen);
+
+                        if (remains < 3)
+                            remains = Math.min(3, classNameLen);
+
+                        packages[packageCnt] = className.substring(0, remains) + '...';
+                    }
+
+                    let result = packages[0];
+
+                    for (let 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;
+
+            const fitted = [];
+
+            const widthByName = [];
+
+            const len = names.length;
+
+            let divideTo = len;
+
+            for (let nameIx = 0; nameIx < len; nameIx++) {
+                fitted[nameIx] = false;
+
+                widthByName[nameIx] = nameWidth;
+            }
+
+            // Try to distribute space from short class names to long class names.
+            let remains = 0;
+
+            do {
+                for (let nameIx = 0; nameIx < len; nameIx++) {
+                    if (!fitted[nameIx]) {
+                        const curNameWidth = measureText(names[nameIx]);
+
+                        if (widthByName[nameIx] > curNameWidth) {
+                            fitted[nameIx] = true;
+
+                            remains += widthByName[nameIx] - curNameWidth;
+
+                            divideTo -= 1;
+
+                            widthByName[nameIx] = curNameWidth;
+                        }
+                    }
+                }
+
+                const remainsByName = remains / divideTo;
+
+                for (let nameIx = 0; nameIx < len; nameIx++) {
+                    if (!fitted[nameIx])
+                        widthByName[nameIx] += remainsByName;
+                }
+            }
+            while (remains > 0);
+
+            // Compact class names to available for each space.
+            for (let nameIx = 0; nameIx < len; nameIx++) {
+                const s = names[nameIx];
+
+                if (s.length > (nameLength / 2 | 0)) {
+                    let totalWidth = measureText(s);
+
+                    if (totalWidth > widthByName[nameIx]) {
+                        const packages = s.split('.');
+
+                        const packageCnt = packages.length - 1;
+
+                        for (let 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];
+                            }
+                        }
+
+                        let shortPackage = '';
+
+                        for (let i = 0; i < packageCnt; i++)
+                            shortPackage += packages[i] + '.';
+
+                        const className = packages[packageCnt];
+
+                        const classLen = className.length;
+
+                        let minLen = Math.min(classLen, 3);
+
+                        totalWidth = measureText(shortPackage + className);
+
+                        // Compact class name if shorten package path is very long.
+                        if (totalWidth > widthByName[nameIx]) {
+                            let maxLen = classLen;
+                            let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+
+                            while (middleLen !== minLen && middleLen !== maxLen) {
+                                const middleLenPx = measureText(shortPackage + className.substr(0, middleLen) + '...');
+
+                                if (middleLenPx > widthByName[nameIx])
+                                    maxLen = middleLen;
+                                else
+                                    minLen = middleLen;
+
+                                middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+                            }
+
+                            names[nameIx] = shortPackage + className.substring(0, middleLen) + '...';
+                        }
+                        else
+                            names[nameIx] = shortPackage + className;
+                    }
+                }
+            }
+
+            return names;
+        }
+
+        /**
+         * Compact any string by max number of pixels.
+         *
+         * @param label String to compact.
+         * @param nameWidth Maximum available width in pixels for simple name.
+         * @returns {*} Compacted string.
+         */
+        function compactLabelByPixels(label, nameWidth) {
+            if (nameWidth <= 0)
+                return label;
+
+            const totalWidth = measureText(label);
+
+            if (totalWidth > nameWidth) {
+                let maxLen = label.length;
+                let minLen = Math.min(maxLen, 3);
+                let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+
+                while (middleLen !== minLen && middleLen !== maxLen) {
+                    const middleLenPx = measureText(label.substr(0, middleLen) + '...');
+
+                    if (middleLenPx > nameWidth)
+                        maxLen = middleLen;
+                    else
+                        minLen = middleLen;
+
+                    middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+                }
+
+                return label.substring(0, middleLen) + '...';
+            }
+
+            return label;
+        }
+
+        /**
+         * Calculate available width for text in link to edit element.
+         *
+         * @param index Showed index of element for calculation of maximum width in pixels.
+         * @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) {
+            const idElem = $('#' + id);
+
+            let width = 0;
+
+            switch (idElem.prop('tagName')) {
+                // Detection of available width in presentation table row.
+                case 'TABLE':
+                    const cont = $(idElem.find('tr')[index - 1]).find('td')[0];
+
+                    width = cont.clientWidth;
+
+                    if (width > 0) {
+                        const children = $(cont).children(':not("a")');
+
+                        _.forEach(children, function(child) {
+                            if ('offsetWidth' in child)
+                                width -= $(child).outerWidth(true);
+                        });
+                    }
+
+                    break;
+
+                // Detection of available width in dropdown row.
+                case 'A':
+                    width = idElem.width();
+
+                    $(idElem).children(':not("span")').each(function(ix, child) {
+                        if ('offsetWidth' in child)
+                            width -= child.offsetWidth;
+                    });
+
+                    break;
+
+                default:
+            }
+
+            return width | 0;
+        }
+
+        function getModel(obj, field) {
+            let path = field.path;
+
+            if (!isDefined(path) || !isDefined(obj))
+                return obj;
+
+            path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
+            path = path.replace(/^\./, '');           // strip a leading dot
+
+            const segs = path.split('.');
+            let root = obj;
+
+            while (segs.length > 0) {
+                const pathStep = segs.shift();
+
+                if (typeof root[pathStep] === 'undefined')
+                    root[pathStep] = {};
+
+                root = root[pathStep];
+            }
+
+            return root;
+        }
+
+        function extractDataSource(cache) {
+            if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) {
+                const storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind];
+
+                if (storeFactory.dialect || (storeFactory.connectVia === 'DataSource'))
+                    return storeFactory;
+            }
+
+            return null;
+        }
+
+        const cacheStoreJdbcDialects = [
+            {value: 'Generic', label: 'Generic JDBC'},
+            {value: 'Oracle', label: 'Oracle'},
+            {value: 'DB2', label: 'IBM DB2'},
+            {value: 'SQLServer', label: 'Microsoft SQL Server'},
+            {value: 'MySQL', label: 'MySQL'},
+            {value: 'PostgreSQL', label: 'PostgreSQL'},
+            {value: 'H2', label: 'H2 database'}
+        ];
+
+        function domainForStoreConfigured(domain) {
+            const isEmpty = !isDefined(domain) || (isEmptyString(domain.databaseSchema) &&
+                isEmptyString(domain.databaseTable) &&
+                _.isEmpty(domain.keyFields) &&
+                _.isEmpty(domain.valueFields));
+
+            return !isEmpty;
+        }
+
+        const DS_CHECK_SUCCESS = { checked: true };
+
+        function compareDataSources(firstCache, secondCache) {
+            const firstDs = extractDataSource(firstCache);
+            const secondDs = extractDataSource(secondCache);
+
+            if (firstDs && secondDs) {
+                const firstDB = firstDs.dialect;
+                const secondDB = secondDs.dialect;
+
+                if (firstDs.dataSourceBean === secondDs.dataSourceBean && firstDB !== secondDB)
+                    return {checked: false, firstCache, firstDB, secondCache, secondDB};
+            }
+
+            return DS_CHECK_SUCCESS;
+        }
+
+        function compareSQLSchemaNames(firstCache, secondCache) {
+            const firstName = firstCache.sqlSchema;
+            const secondName = secondCache.sqlSchema;
+
+            if (firstName && secondName && (firstName === secondName))
+                return {checked: false, firstCache, secondCache};
+
+            return DS_CHECK_SUCCESS;
+        }
+
+        function toJavaName(prefix, name) {
+            const javaName = name ? name.replace(/[^A-Za-z_0-9]+/g, '_') : 'dflt';
+
+            return prefix + javaName.charAt(0).toLocaleUpperCase() + javaName.slice(1);
+        }
+
+        return {
+            getModel,
+            mkOptions(options) {
+                return _.map(options, (option) => {
+                    return {value: option, label: isDefined(option) ? option : 'Not set'};
+                });
+            },
+            isDefined,
+            hasProperty(obj, props) {
+                for (const propName in props) {
+                    if (props.hasOwnProperty(propName)) {
+                        if (obj[propName])
+                            return true;
+                    }
+                }
+
+                return false;
+            },
+            isEmptyString,
+            SUPPORTED_JDBC_TYPES,
+            findJdbcType(jdbcType) {
+                const res = _.find(ALL_JDBC_TYPES, function(item) {
+                    return item.dbType === jdbcType;
+                });
+
+                return res ? res : {dbName: 'Unknown', javaType: 'Unknown'};
+            },
+            javaBuiltInClasses,
+            javaBuiltInTypes,
+            isJavaBuiltInClass,
+            isValidJavaIdentifier,
+            isValidJavaClass(msg, ident, allowBuiltInClass, elemId, packageOnly, panels, panelId) {
+                if (isEmptyString(ident))
+                    return showPopoverMessage(panels, panelId, elemId, msg + ' could not be empty!');
+
+                const parts = ident.split('.');
+
+                const len = parts.length;
+
+                if (!allowBuiltInClass && isJavaBuiltInClass(ident))
+                    return showPopoverMessage(panels, panelId, elemId, msg + ' should not be the Java build-in class!');
+
+                if (len < 2 && !isJavaBuiltInClass(ident) && !packageOnly)
+                    return showPopoverMessage(panels, panelId, elemId, msg + ' does not have package specified!');
+
+                for (let i = 0; i < parts.length; i++) {
+                    const part = parts[i];
+
+                    if (!isValidJavaIdentifier(msg, part, elemId, panels, panelId))
+                        return false;
+                }
+
+                return true;
+            },
+            domainForQueryConfigured(domain) {
+                const isEmpty = !isDefined(domain) || (_.isEmpty(domain.fields) &&
+                    _.isEmpty(domain.aliases) &&
+                    _.isEmpty(domain.indexes));
+
+                return !isEmpty;
+            },
+            domainForStoreConfigured,
+            /**
+             * Cut class name by width in pixel or width in symbol count.
+             *
+             * @param id Id of parent table.
+             * @param index Row number in table.
+             * @param maxLength Maximum length in symbols for all names.
+             * @param names Array of class names to compact.
+             * @param divider String to visualy divide items.
+             * @returns {*} Array of compacted class names.
+             */
+            compactJavaName(id, index, maxLength, names, divider) {
+                divider = ' ' + divider + ' ';
+
+                const prefix = index + ') ';
+
+                const nameCnt = names.length;
+
+                const nameLength = ((maxLength - 3 * (nameCnt - 1)) / nameCnt) | 0;
+
+                try {
+                    const nameWidth = (availableWidth(index, id) - measureText(prefix) - (nameCnt - 1) * measureText(divider)) /
+                        nameCnt | 0;
+
+                    // HTML5 calculation of showed message width.
+                    names = compactByMaxPixels(names, nameLength, nameWidth);
+                }
+                catch (err) {
+                    names = compactByMaxCharts(names, nameLength);
+                }
+
+                let result = prefix + names[0];
+
+                for (let nameIx = 1; nameIx < names.length; nameIx++)
+                    result += divider + names[nameIx];
+
+                return result;
+            },
+            /**
+             * Compact text by width in pixels or symbols count.
+             *
+             * @param id Id of parent table.
+             * @param index Row number in table.
+             * @param maxLength Maximum length in symbols for all names.
+             * @param label Text to compact.
+             * @returns Compacted label text.
+             */
+            compactTableLabel(id, index, maxLength, label) {
+                label = index + ') ' + label;
+
+                try {
+                    const nameWidth = availableWidth(index, id) | 0;
+
+                    // HTML5 calculation of showed message width.
+                    label = compactLabelByPixels(label, nameWidth);
+                }
+                catch (err) {
+                    const nameLength = maxLength - 3 | 0;
+
+                    label = label.length > maxLength ? label.substr(0, nameLength) + '...' : label;
+                }
+
+                return label;
+            },
+            widthIsSufficient(id, index, text) {
+                try {
+                    const available = availableWidth(index, id);
+
+                    const required = measureText(text);
+
+                    return !available || available >= Math.floor(required);
+                }
+                catch (err) {
+                    return true;
+                }
+            },
+            ensureActivePanel(panels, id, focusId) {
+                ensureActivePanel(panels, id, focusId);
+            },
+            showPopoverMessage,
+            hidePopover() {
+                if (popover)
+                    popover.hide();
+            },
+            confirmUnsavedChanges(dirty, selectFunc) {
+                if (dirty) {
+                    if ($window.confirm('You have unsaved changes.\n\nAre you sure you want to discard them?'))
+                        selectFunc();
+                }
+                else
+                    selectFunc();
+            },
+            saveBtnTipText(dirty, objectName) {
+                if (dirty)
+                    return 'Save ' + objectName;
+
+                return 'Nothing to save';
+            },
+            download(type, name, data) {
+                const 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);
+            },
+            formUI() {
+                return {
+                    ready: false,
+                    expanded: false,
+                    loadedPanels: [],
+                    loadPanel(pnl) {
+                        if (!_.includes(this.loadedPanels, pnl))
+                            this.loadedPanels.push(pnl);
+                    },
+                    isPanelLoaded(pnl) {
+                        return _.includes(this.loadedPanels, pnl);
+                    }
+                };
+            },
+            getQueryVariable(name) {
+                const attrs = window.location.search.substring(1).split('&');
+                const attr = _.find(attrs, (a) => a === name || (a.indexOf('=') >= 0 && a.substr(0, a.indexOf('=')) === name));
+
+                if (!isDefined(attr))
+                    return null;
+
+                if (attr === name)
+                    return true;
+
+                return attr.substr(attr.indexOf('=') + 1);
+            },
+            cacheStoreJdbcDialects,
+            cacheStoreJdbcDialectsLabel(dialect) {
+                const found = _.find(cacheStoreJdbcDialects, function(dialectVal) {
+                    return dialectVal.value === dialect;
+                });
+
+                return found ? found.label : null;
+            },
+            checkCachesDataSources(caches, checkCacheExt) {
+                let res = DS_CHECK_SUCCESS;
+
+                _.find(caches, function(curCache, curIx) {
+                    if (isDefined(checkCacheExt)) {
+                        if (checkCacheExt._id !== curCache._id) {
+                            res = compareDataSources(checkCacheExt, curCache);
+
+                            return !res.checked;
+                        }
+
+                        return false;
+                    }
+
+                    return _.find(caches, function(checkCache, checkIx) {
+                        if (checkIx < curIx) {
+                            res = compareDataSources(checkCache, curCache);
+
+                            return !res.checked;
+                        }
+
+                        return false;
+                    });
+                });
+
+                return res;
+            },
+            checkCacheSQLSchemas(caches, checkCacheExt) {
+                let res = DS_CHECK_SUCCESS;
+
+                _.find(caches, (curCache, curIx) => {
+                    if (isDefined(checkCacheExt)) {
+                        if (checkCacheExt._id !== curCache._id) {
+                            res = compareSQLSchemaNames(checkCacheExt, curCache);
+
+                            return !res.checked;
+                        }
+
+                        return false;
+                    }
+
+                    return _.find(caches, function(checkCache, checkIx) {
+                        if (checkIx < curIx) {
+                            res = compareSQLSchemaNames(checkCache, curCache);
+
+                            return !res.checked;
+                        }
+
+                        return false;
+                    });
+                });
+
+                return res;
+            },
+            autoCacheStoreConfiguration(cache, domains) {
+                const cacheStoreFactory = isDefined(cache.cacheStoreFactory) &&
+                    isDefined(cache.cacheStoreFactory.kind);
+
+                if (!cacheStoreFactory && _.findIndex(domains, domainForStoreConfigured) >= 0) {
+                    const dflt = !cache.readThrough && !cache.writeThrough;
+
+                    return {
+                        cacheStoreFactory: {
+                            kind: 'CacheJdbcPojoStoreFactory',
+                            CacheJdbcPojoStoreFactory: {
+                                dataSourceBean: toJavaName('ds', cache.name),
+                                dialect: 'Generic'
+                            },
+                            CacheJdbcBlobStoreFactory: { connectVia: 'DataSource' }
+                        },
+                        readThrough: dflt || cache.readThrough,
+                        writeThrough: dflt || cache.writeThrough
+                    };
+                }
+            },
+            autoClusterSwapSpiConfiguration(cluster, caches) {
+                const swapConfigured = cluster.swapSpaceSpi && cluster.swapSpaceSpi.kind;
+
+                if (!swapConfigured && _.find(caches, (cache) => cache.swapEnabled))
+                    return {swapSpaceSpi: {kind: 'FileSwapSpaceSpi'}};
+
+                return null;
+            },
+            randomString(len) {
+                const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+                const possibleLen = possible.length;
+
+                let res = '';
+
+                for (let i = 0; i < len; i++)
+                    res += possible.charAt(Math.floor(Math.random() * possibleLen));
+
+                return res;
+            },
+            checkFieldValidators(ui) {
+                const form = ui.inputForm;
+                const errors = form.$error;
+                const errKeys = Object.keys(errors);
+
+                if (errKeys && errKeys.length > 0) {
+                    const firstErrorKey = errKeys[0];
+
+                    const firstError = errors[firstErrorKey][0];
+                    const actualError = firstError.$error[firstErrorKey][0];
+
+                    const errNameFull = actualError.$name;
+                    const errNameShort = errNameFull.endsWith('TextInput') ? errNameFull.substring(0, errNameFull.length - 9) : errNameFull;
+
+                    const extractErrorMessage = function(errName) {
+                        try {
+                            return errors[firstErrorKey][0].$errorMessages[errName][firstErrorKey];
+                        }
+                        catch (ignored) {
+                            try {
+                                return form[firstError.$name].$errorMessages[errName][firstErrorKey];
+                            }
+                            catch (ignited) {
+                                return false;
+                            }
+                        }
+                    };
+
+                    const msg = extractErrorMessage(errNameFull) || extractErrorMessage(errNameShort) || 'Invalid value!';
+
+                    return showPopoverMessage(ui, firstError.$name, errNameFull, msg);
+                }
+
+                return true;
+            }
+        };
+    }
+]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Messages.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/Messages.service.js b/modules/web-console/src/main/js/app/services/Messages.service.js
new file mode 100644
index 0000000..e679488
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/Messages.service.js
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+// Service to show various information and error messages.
+export default ['IgniteMessages', ['$alert', ($alert) => {
+    // Common instance of alert modal.
+    let msgModal;
+
+    const errorMessage = (prefix, err) => {
+        prefix = prefix || '';
+
+        if (err) {
+            if (err.hasOwnProperty('message'))
+                return prefix + err.message;
+
+            return prefix + err;
+        }
+
+        return prefix + 'Internal error.';
+    };
+
+    const hideAlert = () => {
+        if (msgModal)
+            msgModal.hide();
+    };
+
+    const _showMessage = (err, type, duration, icon) => {
+        hideAlert();
+
+        const title = errorMessage(null, err);
+
+        msgModal = $alert({type, title, duration});
+
+        msgModal.$scope.icon = icon;
+    };
+
+    return {
+        errorMessage,
+        hideAlert,
+        showError(err) {
+            _showMessage(err, 'danger', 10, 'fa-exclamation-triangle');
+
+            return false;
+        },
+        showInfo(err) {
+            _showMessage(err, 'success', 3, 'fa-check-circle-o');
+        }
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js b/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js
new file mode 100644
index 0000000..4c7052b
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+// Service to normalize objects for dirty checks.
+export default ['IgniteModelNormalizer', () => {
+    /**
+     * Normalize object for dirty checks.
+     *
+     * @param original
+     * @param dest
+     * @returns {*}
+     */
+    const normalize = (original, dest) => {
+        if (_.isUndefined(original))
+            return dest;
+
+        if (_.isObject(original)) {
+            _.forOwn(original, (value, key) => {
+                if (/\$\$hashKey/.test(key))
+                    return;
+
+                const attr = normalize(value);
+
+                if (!_.isNil(attr)) {
+                    dest = dest || {};
+                    dest[key] = attr;
+                }
+            });
+        } else if (_.isBoolean(original) && original === true)
+            dest = original;
+        else if ((_.isString(original) && original.length) || _.isNumber(original))
+            dest = original;
+        else if (_.isArray(original) && original.length)
+            dest = _.map(original, (value) => normalize(value, {}));
+
+        return dest;
+    };
+
+    return {
+        normalize,
+        isEqual(prev, cur) {
+            return _.isEqual(prev, normalize(cur));
+        }
+    };
+}];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js b/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js
new file mode 100644
index 0000000..91244b0
--- /dev/null
+++ b/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js
@@ -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.
+ */
+
+const MSG = 'You have unsaved changes.\n\nAre you sure you want to discard them?';
+
+// Service that show confirmation about unsaved changes on user change location.
+export default ['IgniteUnsavedChangesGuard', ['$rootScope', ($root) => {
+    return {
+        install(scope, customDirtyCheck = () => scope.ui.inputForm.$dirty) {
+            scope.$on('$destroy', () => window.onbeforeunload = null);
+
+            const unbind = $root.$on('$stateChangeStart', (event) => {
+                if (_.get(scope, 'ui.inputForm', false) && customDirtyCheck()) {
+                    if (!confirm(MSG)) // eslint-disable-line no-alert
+                        event.preventDefault();
+                    else
+                        unbind();
+                }
+            });
+
+            window.onbeforeunload = () => _.get(scope, 'ui.inputForm.$dirty', false) ? MSG : null;
+        }
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/cleanup.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/cleanup.service.js b/modules/web-console/src/main/js/app/services/cleanup.service.js
deleted file mode 100644
index bec9ea3..0000000
--- a/modules/web-console/src/main/js/app/services/cleanup.service.js
+++ /dev/null
@@ -1,46 +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.
- */
-
-export default ['$cleanup', () => {
-    const cleanup = (original, dist) => {
-        if (_.isUndefined(original))
-            return dist;
-
-        if (_.isObject(original)) {
-            _.forOwn(original, (value, key) => {
-                if (/\$\$hashKey/.test(key))
-                    return;
-
-                const attr = cleanup(value);
-
-                if (!_.isNil(attr)) {
-                    dist = dist || {};
-                    dist[key] = attr;
-                }
-            });
-        } else if (_.isBoolean(original) && original === true)
-            dist = original;
-        else if ((_.isString(original) && original.length) || _.isNumber(original))
-            dist = original;
-        else if (_.isArray(original) && original.length)
-            dist = _.map(original, (value) => cleanup(value, {}));
-
-        return dist;
-    };
-
-    return cleanup;
-}];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/confirm.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/services/confirm.service.js b/modules/web-console/src/main/js/app/services/confirm.service.js
deleted file mode 100644
index bb07cfd..0000000
--- a/modules/web-console/src/main/js/app/services/confirm.service.js
+++ /dev/null
@@ -1,70 +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.
- */
-
-// Confirm popup service.
-export default ['$confirm', ['$modal', '$rootScope', '$q', '$animate', ($modal, $root, $q, $animate) => {
-    const scope = $root.$new();
-
-    const modal = $modal({templateUrl: '/templates/confirm.html', scope, placement: 'center', show: false});
-
-    let deferred;
-
-    const _hide = (animate) => {
-        $animate.enabled(modal.$element, animate);
-
-        modal.hide();
-    };
-
-    scope.confirmYes = () => {
-        _hide(scope.animate);
-
-        deferred.resolve(true);
-    };
-
-    scope.confirmNo = () => {
-        _hide(scope.animate);
-
-        deferred.resolve(false);
-    };
-
-    scope.confirmCancel = () => {
-        _hide(true);
-
-        deferred.reject('cancelled');
-    };
-
-    /**
-     *
-     * @param {String } content
-     * @param {Boolean} [yesNo]
-     * @param {Boolean} [animate]
-     * @returns {Promise}
-     */
-    modal.confirm = (content, yesNo, animate) => {
-        scope.animate = !!animate;
-        scope.content = content || 'Confirm?';
-        scope.yesNo = !!yesNo;
-
-        deferred = $q.defer();
-
-        modal.$promise.then(modal.show);
-
-        return deferred.promise;
-    };
-
-    return modal;
-}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/vendor.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/vendor.js b/modules/web-console/src/main/js/app/vendor.js
new file mode 100644
index 0000000..a8eeea7
--- /dev/null
+++ b/modules/web-console/src/main/js/app/vendor.js
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 'jquery';
+import 'angular';
+import 'angular-animate';
+import 'angular-sanitize';
+import 'angular-strap';
+import 'angular-strap/dist/angular-strap.tpl';
+import 'angular-socket-io';
+import 'angular-retina';
+import 'angular-ui-router';
+import 'ui-router-metatags/dist/ui-router-metatags';
+import 'angular-smart-table';
+import 'angular-ui-grid/ui-grid';
+import 'angular-drag-and-drop-lists';
+import 'angular-nvd3';
+import 'angular-tree-control';
+import 'angular-gridster';
+import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
+import 'bootstrap-sass/assets/javascripts/bootstrap/carousel';
+import 'brace';
+import 'brace/mode/xml';
+import 'brace/mode/sql';
+import 'brace/mode/java';
+import 'brace/mode/dockerfile';
+import 'brace/mode/snippets';
+import 'brace/theme/chrome';
+import 'brace/ext/language_tools';
+import 'brace/ext/searchbox';
+import 'file-saver';
+import 'jszip';
+import 'nvd3';
+import 'query-command-supported';
+import 'angular-gridster/dist/angular-gridster.min.css';
+import 'angular-tree-control/css/tree-control-attribute.css';
+import 'angular-tree-control/css/tree-control.css';
+import 'angular-ui-grid/ui-grid.css';
+import 'angular-motion/dist/angular-motion.css';
+import 'nvd3/build/nv.d3.css';


Mime
View raw message