http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
 
b/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
new file mode 100644
index 0000000..79787a2
--- /dev/null
+++ 
b/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.directive.js
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 template from './ui-ace-xml.jade!';
+import controller from './ui-ace-xml.controller';
+
+export default ['igniteUiAceXml', ['GeneratorXml', (generator) => {
+    const link = (scope, $el, attrs, [ctrl, igniteUiAceTabs, formCtrl, 
ngModelCtrl]) => {
+        if (formCtrl && ngModelCtrl)
+            formCtrl.$removeControl(ngModelCtrl);
+
+        if (igniteUiAceTabs && igniteUiAceTabs.onLoad) {
+            scope.onLoad = (editor) => {
+                igniteUiAceTabs.onLoad(editor);
+
+                scope.$watch('master', () => editor.attractAttention = false);
+            };
+        }
+
+        if (igniteUiAceTabs && igniteUiAceTabs.onChange)
+            scope.onChange = igniteUiAceTabs.onChange;
+
+        const render = (data) => {
+            delete ctrl.data;
+
+            if (!data)
+                return;
+
+            return ctrl.generator(scope.master);
+        };
+
+        // Setup generator.
+        if (scope.generator) {
+            const method = scope.generator;
+
+            switch (method) {
+                case 'clusterCaches':
+                    ctrl.generator = (cluster) => {
+                        const caches = _.reduce(scope.detail, (acc, cache) => {
+                            if (_.includes(cluster.caches, cache.value))
+                                acc.push(cache.cache);
+
+                            return acc;
+                        }, []);
+
+                        return generator.clusterCaches(caches, null, true, 
generator.clusterGeneral(cluster)).asString();
+                    };
+
+                    break;
+
+                case 'igfss':
+                    ctrl.generator = (cluster) => {
+                        const igfss = _.reduce(scope.detail, (acc, igfs) => {
+                            if (_.includes(cluster.igfss, igfs.value))
+                                acc.push(igfs.igfs);
+
+                            return acc;
+                        }, []);
+
+                        return generator.igfss(igfss).asString();
+                    };
+
+                    break;
+
+                case 'cacheStore':
+                    ctrl.generator = (cache) => {
+                        const domains = _.reduce(scope.detail, (acc, domain) 
=> {
+                            if (_.includes(cache.domains, domain.value))
+                                acc.push(domain.meta);
+
+                            return acc;
+                        }, []);
+
+                        return generator.cacheStore(cache, domains).asString();
+                    };
+
+                    break;
+
+                default:
+                    ctrl.generator = (data) => 
generator[method](data).asString();
+            }
+        }
+
+        if (typeof attrs.clusterCfg !== 'undefined') {
+            scope.$watch('cfg', (cfg) => {
+                if (typeof cfg !== 'undefined')
+                    return;
+
+                scope.cfg = {};
+            });
+
+            scope.$watch('cfg', (data) => ctrl.data = render(data), true);
+        }
+
+        const noDeepWatch = !(typeof attrs.noDeepWatch !== 'undefined');
+
+        // Setup watchers.
+        scope.$watch('master', (data) => ctrl.data = render(data), 
noDeepWatch);
+    };
+
+    return {
+        priority: 1,
+        restrict: 'E',
+        scope: {
+            master: '=',
+            detail: '=',
+            generator: '@',
+            cfg: '=?clusterCfg'
+        },
+        bindToController: {
+            data: '=?ngModel'
+        },
+        link,
+        template,
+        controller,
+        controllerAs: 'ctrl',
+        require: ['igniteUiAceXml', '?^igniteUiAceTabs', '?^form', '?ngModel']
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.jade
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.jade 
b/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.jade
new file mode 100644
index 0000000..0dd627a
--- /dev/null
+++ b/modules/web-console/src/main/js/app/directives/ui-ace-xml/ui-ace-xml.jade
@@ -0,0 +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
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+div(ng-if='ctrl.data' ignite-ace='{onLoad: onLoad, onChange: onChange, mode: 
"xml"}' ng-model='ctrl.data')

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/filters/byName.filter.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/filters/byName.filter.js 
b/modules/web-console/src/main/js/app/filters/byName.filter.js
new file mode 100644
index 0000000..3b0746f
--- /dev/null
+++ b/modules/web-console/src/main/js/app/filters/byName.filter.js
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 ['byName', [() => (arr, search) => {
+    if (!(arr && arr.length) || !search)
+        return arr;
+
+    return _.filter(arr, ({ name }) => name.indexOf(search) >= 0);
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/filters/hasPojo.filter.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/filters/hasPojo.filter.js 
b/modules/web-console/src/main/js/app/filters/hasPojo.filter.js
new file mode 100644
index 0000000..a179423
--- /dev/null
+++ b/modules/web-console/src/main/js/app/filters/hasPojo.filter.js
@@ -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.
+ */
+
+export default ['hasPojo', [() => ({caches} = []) => _.find(caches, (cache) => 
cache.domains && cache.domains.length)]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/helpers/jade/mixins.jade
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/helpers/jade/mixins.jade 
b/modules/web-console/src/main/js/app/helpers/jade/mixins.jade
new file mode 100644
index 0000000..341d351
--- /dev/null
+++ b/modules/web-console/src/main/js/app/helpers/jade/mixins.jade
@@ -0,0 +1,587 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.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 for advanced options toggle.
+mixin advanced-options-toggle(click, cond, showMessage, hideMessage)
+    .advanced-options
+        i.fa(ng-click=click ng-class='#{cond} ? "fa-chevron-circle-down" : 
"fa-chevron-circle-right"')
+        a(ng-click=click) {{#{cond} ? '#{hideMessage}' : '#{showMessage}'}}
+
+//- Mixin for advanced options toggle with default settings.
+mixin advanced-options-toggle-default
+    +advanced-options-toggle('toggleExpanded()', 'ui.expanded', 'Show advanced 
settings...', 'Hide advanced settings...')
+
+//- Mixin for main table on screen with list of items.
+mixin main-table(title, rows, focusId, click, rowTemplate, searchField)
+    .padding-bottom-dflt(ng-show='#{rows} && #{rows}.length > 0')
+        table.links(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='#{searchField}' placeholder='Filter #{title}...')
+            tbody
+                tr
+                    td
+                        .scrollable-y(ng-show='displayedRows.length > 0' 
style='max-height: 200px')
+                            table
+                                tbody
+                                    tr(ng-repeat='row in displayedRows track 
by row._id' ignite-bs-affix-update)
+                                        td
+                                            a(ng-class='{active: row._id == 
selectedItem._id}' on-click-focus=focusId ng-click=click) #{rowTemplate}
+                        label.placeholder(ng-show='displayedRows.length == 0') 
No #{title} found
+
+//- Mixin with save, remove, clone and undo buttons.
+mixin save-remove-clone-undo-buttons(objectName)
+    -var removeTip = '"Remove current ' + objectName + '"'
+    -var cloneTip = '"Clone current ' + objectName + '"'
+    -var undoTip = '"Undo all changes for current ' + objectName + '"'
+
+    div(ng-show='contentVisible()' style='display: inline-block;')
+        .panel-tip-container(ng-hide='!backupItem || backupItem._id')
+            a.btn.btn-primary(ng-disabled='!ui.inputForm.$dirty' 
ng-click='ui.inputForm.$dirty && saveItem()' bs-tooltip='' 
data-title='{{saveBtnTipText(ui.inputForm.$dirty, "#{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.inputForm.$dirty' ng-click='ui.inputForm.$dirty && saveItem()' 
bs-tooltip='' data-title='{{saveBtnTipText(ui.inputForm.$dirty, 
"#{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='[{ text: "Remove 
All", click: "removeAllItems()" }]' data-placement='bottom-right')
+                span.caret
+        .panel-tip-container(ng-show='backupItem')
+            i.btn.btn-primary.fa.fa-undo(id='undo-item' 
ng-disabled='!ui.inputForm.$dirty' ng-click='ui.inputForm.$dirty && resetAll()' 
bs-tooltip=undoTip data-placement='bottom' data-trigger='hover')
+
+//- Mixin for feedback on specified error.
+mixin error-feedback(visible, error, errorMessage, name)
+    i.fa.fa-exclamation-triangle.form-control-feedback(
+        ng-if=visible
+        bs-tooltip='"#{errorMessage}"'
+        ignite-error=error
+        ignite-error-message=errorMessage
+        name=name
+    )
+
+//- Mixin for feedback on unique violation.
+mixin unique-feedback(visible, errorMessage)
+    +error-feedback(visible, 'igniteUnique', errorMessage)
+
+//- Mixin for feedback on IP address violation.
+mixin ipaddress-feedback(visible, name)
+    +error-feedback(visible, 'ipaddress', 'Invalid address!', name)
+
+//- Mixin for feedback on port of IP address violation.
+mixin ipaddress-port-feedback(visible, name)
+    +error-feedback(visible, 'ipaddressPort', 'Invalid port!', name)
+
+//- Mixin for feedback on port range violation.
+mixin ipaddress-port-range-feedback(visible, name)
+    +error-feedback(visible, 'ipaddressPortRange', 'Invalid port range!', name)
+
+//- Mixin for checkbox.
+mixin checkbox(lbl, model, name, tip)
+    ignite-form-field.checkbox
+        ignite-form-field-input-checkbox(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+        )
+        | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+
+//- Mixin for checkbox with enabled condition.
+mixin checkbox-enabled(lbl, model, name, enabled, tip)
+    ignite-form-field.checkbox
+        ignite-form-field-input-checkbox(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-ng-disabled='!(#{enabled})'
+        )
+        | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+
+//- Mixin for java name field with enabled condition.
+mixin java-class(lbl, model, name, enabled, required, tip)
+    -var errLbl = lbl.substring(0, lbl.length - 1)
+
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-text(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-ng-disabled='!(#{enabled})'
+            data-ng-required=required
+            data-placeholder='Enter fully qualified class name'
+
+            data-java-identifier='true'
+            data-java-package-specified='true'
+            data-java-keywords='true'
+            data-java-built-in-class='true'
+        )
+            +error-feedback('form[ngModelName].$error.javaBuiltInClass', 
'javaBuiltInClass', errLbl + ' should not be the Java built-in class!', name)
+            +error-feedback('form[ngModelName].$error.javaKeywords', 
'javaKeywords', errLbl + ' could not contains reserved Java keyword!', name)
+            +error-feedback('form[ngModelName].$error.javaPackageSpecified', 
'javaPackageSpecified', errLbl + ' does not have package specified!', name)
+            +error-feedback('form[ngModelName].$error.javaIdentifier', 
'javaIdentifier', errLbl + ' is invalid Java identifier!', name)
+
+//- Mixin for text field with enabled condition with options.
+mixin java-class-typeahead(lbl, model, name, options, enabled, required, 
placeholder, tip)
+    -var errLbl = lbl.substring(0, lbl.length - 1)
+
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-datalist(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-options=options
+            data-placeholder=placeholder
+            data-ng-disabled='!(#{enabled})'
+            data-ng-required=required
+
+            data-java-identifier='true'
+            data-java-package-specified='allow-built-in'
+            data-java-keywords='true'
+        )
+            +error-feedback('form[ngModelName].$error.javaKeywords', 
'javaKeywords', errLbl + ' could not contains reserved Java keyword!', name)
+            +error-feedback('form[ngModelName].$error.javaPackageSpecified', 
'javaPackageSpecified', errLbl + ' does not have package specified!', name)
+            +error-feedback('form[ngModelName].$error.javaIdentifier', 
'javaIdentifier', errLbl + ' is invalid Java identifier!', name)
+
+//- Mixin for text field with IP address check.
+mixin text-ip-address(lbl, model, name, enabled, placeholder, tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-text(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-ipaddress='true'
+            data-ng-disabled='!(#{enabled})'
+            data-placeholder=placeholder
+        )
+            +ipaddress-feedback('form[ngModelName].$error.ipaddress', name)
+
+
+//- Mixin for text field with IP address and port check.
+mixin text-ip-address-with-port(lbl, model, name, enabled, placeholder, tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-text(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-ipaddress='true'
+            data-ipaddress-with-port='true'
+            data-ng-disabled='!(#{enabled})'
+            data-placeholder=placeholder
+        )
+            +ipaddress-feedback('form[ngModelName].$error.ipaddress', name)
+            +ipaddress-port-feedback('form[ngModelName].$error.ipaddressPort', 
name)
+
+//- Mixin for text field.
+mixin text-enabled(lbl, model, name, enabled, required, placeholder, tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-text(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-placeholder=placeholder
+            data-ng-disabled='!(#{enabled})'
+            data-ng-required=required
+        )
+
+//- Mixin for text field.
+mixin text(lbl, model, name, required, placeholder, tip)
+    +text-enabled(lbl, model, name, 'true', required, placeholder, tip)
+
+//- Mixin for text field with enabled condition with options.
+mixin text-options(lbl, model, name, options, enabled, required, placeholder, 
tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-datalist(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-options=options
+            data-placeholder=placeholder
+            data-ng-disabled='!(#{enabled})'
+            data-ng-required=required
+        )
+
+//- Mixin for required numeric field.
+mixin number-required(lbl, model, name, enabled, required, placeholder, min, 
tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-number(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-ng-required=required
+            data-ng-disabled='!(#{enabled})'
+            data-placeholder=placeholder
+            data-min=min
+        )
+
+//- Mixin for required numeric field with maximum and minimum limit.
+mixin number-min-max(lbl, model, name, enabled, placeholder, min, max, tip)
+    +number-min-max-step(lbl, model, name, enabled, placeholder, min, max, 
'1', tip)
+
+//- Mixin for required numeric field with maximum and minimum limit.
+mixin number-min-max-step(lbl, model, name, enabled, placeholder, min, max, 
step, tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-input-number(
+            data-id=name
+            data-name=name
+            data-ng-model=model
+            data-ng-disabled='!(#{enabled})'
+            data-placeholder=placeholder
+            data-min=min
+            data-max=max
+            data-step=step
+        )
+
+//- Mixin for numeric field.
+mixin number(lbl, model, name, enabled, placeholder, min, tip)
+    +number-required(lbl, model, name, enabled, 'false', placeholder, min, tip)
+
+//- Mixin for required dropdown field.
+mixin dropdown-required-empty(lbl, model, name, enabled, required, 
placeholder, placeholderEmpty, options, tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-dropdown(
+            data-id=name
+            data-name=name
+            data-options=options
+            data-ng-model=model
+            data-ng-disabled='!(#{enabled})'
+            data-ng-required=required
+            data-placeholder='{{#{options}.length > 0 ? "#{placeholder}" : 
"#{placeholderEmpty}"}}'
+        )
+
+//- Mixin for required dropdown field.
+mixin dropdown-required(lbl, model, name, enabled, required, placeholder, 
options, tip)
+    ignite-form-field
+        ignite-form-field-label
+            | #{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-dropdown(
+            data-id=name
+            data-name=name
+            data-options=options
+            data-ng-model=model
+            data-ng-disabled='!(#{enabled})'
+            data-ng-required=required
+            data-placeholder=placeholder
+        )
+
+//- Mixin for dropdown field.
+mixin dropdown(lbl, model, name, enabled, placeholder, options, tip)
+    +dropdown-required(lbl, model, name, enabled, 'false', placeholder, 
options, tip)
+
+//- Mixin for dropdown-multiple field.
+mixin dropdown-multiple(lbl, model, name, enabled, placeholder, 
placeholderEmpty, options, tip)
+    ignite-form-field
+        ignite-form-field-label
+            | !{lbl}
+        ignite-form-field-tooltip
+            | !{tip}
+        ignite-form-field-dropdown(
+            data-id=name
+            data-name=name
+            data-multiple='true'
+            data-options=options
+            data-ng-model=model
+            data-ng-disabled='!(#{enabled})'
+            data-placeholder='{{ #{options}.length > 0 ? "#{placeholder}" : 
"#{placeholderEmpty}" }}'
+            data-disabled='!#{options}.length'
+        )
+
+//- Mixin for table text field.
+mixin table-text-field(field, items, valid, save, placeholder, newItem)
+    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
+    -var onEnter = valid + ' && (' + save + '); ' + valid + ' && ' + 
resetOnEnter + ';'
+
+    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
+
+    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
+    -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
+
+    ignite-form-field-input-text(
+        data-name=field
+        data-ng-model=field
+        data-ng-required='true'
+        data-placeholder=placeholder
+        data-ignite-unique=items
+        data-ignite-form-field-input-autofocus='true'
+        on-enter=onEnter
+        on-escape=onEscape
+        ng-blur=onBlur
+    )
+        block
+
+//- Mixin for table java class field.
+mixin table-java-class-field(lbl, field, items, valid, save, newItem)
+    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
+    -var onEnter = valid + ' && (' + save + '); ' + valid + ' && ' + 
resetOnEnter + ';'
+
+    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
+
+    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
+    -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
+
+    ignite-form-field-input-text(
+        data-name=field
+        data-ng-model=field
+        data-ng-required='true'
+        data-ignite-unique=items
+        data-ignite-form-field-input-autofocus='true'
+
+        data-java-identifier='true'
+        data-java-package-specified='true'
+        data-java-keywords='true'
+        data-java-built-in-class='true'
+
+        data-placeholder='Enter fully qualified class name'
+
+        on-enter=onEnter
+        on-escape=onEscape
+        ng-blur=onBlur
+    )
+        +error-feedback('form[ngModelName].$error.javaBuiltInClass', 
'javaBuiltInClass', lbl + ' should not be the Java built-in class!')
+        +error-feedback('form[ngModelName].$error.javaKeywords', 
'javaKeywords', lbl + ' could not contains reserved Java keyword!')
+        +error-feedback('form[ngModelName].$error.javaPackageSpecified', 
'javaPackageSpecified', lbl + ' does not have package specified!')
+        +error-feedback('form[ngModelName].$error.javaIdentifier', 
'javaIdentifier', lbl + ' is invalid Java identifier!')
+
+        block
+
+//- Mixin for table java package field.
+mixin table-java-package-field(field, items, valid, save, newItem)
+    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
+    -var onEnter = valid + ' && (' + save + '); ' + valid + ' && ' + 
resetOnEnter + ';'
+
+    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
+
+    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
+    -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
+
+    ignite-form-field-input-text(
+        data-name=field
+        data-ng-model=field
+        data-ng-required='true'
+        data-placeholder='Enter package name'
+        data-java-keywords='true'
+        data-java-package-name='true'
+        data-ignite-unique=items
+        data-ignite-form-field-input-autofocus='true'
+        on-enter=onEnter
+        on-escape=onEscape
+        ng-blur=onBlur
+    )
+        block
+
+//- Mixin for table address field.
+mixin table-address-field(field, items, valid, save, newItem, portRange)
+    -var resetOnEnter = newItem ? '(stopblur = true) && (group.add = [{}])' : 
'(field.edit = false)'
+    -var onEnter = valid + ' && (' + save + '); ' + valid + ' && ' + 
resetOnEnter + ';'
+
+    -var onEscape = newItem ? 'group.add = []' : 'field.edit = false'
+
+    -var resetOnBlur = newItem ? '!stopblur && (group.add = [])' : 'field.edit 
= false'
+    -var onBlur = valid + ' && ( ' + save + '); ' + resetOnBlur + ';'
+
+    ignite-form-field-input-text(
+        data-name=field
+        data-ng-model=field
+        data-ng-required='true'
+        data-placeholder='IP address:port'
+        data-ipaddress='true'
+        data-ipaddress-with-port='true'
+        data-ipaddress-with-port-range=portRange ? 'true' : null
+        data-ignite-unique=items
+        data-ignite-form-field-input-autofocus='true'
+        on-enter=onEnter
+        on-escape=onEscape
+        ng-blur=onBlur
+    )
+        block
+
+//- Mixin for table save button.
+   "||" used instead of "&&" to workaround escaping of "&&" to "&&"
+mixin table-save-button(valid, save, newItem)
+    -var reset = newItem ? 'group.add = []' : 'field.edit = false'
+
+    i.fa.fa-floppy-o(
+        ng-show=valid
+        ng-click='!(#{valid}) || (#{save}); !(#{valid}) || (#{reset});'
+        bs-tooltip
+        data-title='Click icon or press [Enter] to save item'
+    )
+
+//- Mixin for table remove button.
+mixin table-remove-conditional-button(items, show, tip)
+    i.tipField.fa.fa-remove(
+        ng-show='#{show} && !field.edit'
+        bs-tooltip='"#{tip}"'
+        ng-click='#{items}.splice(#{items}.indexOf(model), 1)'
+    )
+
+//- Mixin for table remove button.
+mixin table-remove-button(items, tip)
+    +table-remove-conditional-button(items, 'true', tip)
+
+//- Mixin for cache mode.
+mixin cacheMode(lbl, model, name, placeholder)
+    +dropdown(lbl, model, name, 'true', placeholder,
+        '[\
+            {value: "LOCAL", label: "LOCAL"},\
+            {value: "REPLICATED", label: "REPLICATED"},\
+            {value: "PARTITIONED", label: "PARTITIONED"}\
+        ]',
+        '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>'
+    )
+
+//- Mixin for eviction policy.
+mixin evictionPolicy(model, name, enabled, required, tip)
+    -var kind = model + '.kind'
+    -var policy = model + '[' + kind + ']'
+
+    +dropdown-required('Eviction policy:', kind, name + 'Kind', enabled, 
required, 'Not set',
+        '[\
+            {value: "LRU", label: "LRU"},\
+            {value: "FIFO", label: "FIFO"},\
+            {value: "SORTED", label: "Sorted"},\
+            {value: undefined, label: "Not set"}\
+        ]', tip)
+    span(ng-if=kind)
+        a.customize(ng-show='__.expanded' ng-click='__.expanded = false') Hide 
settings
+        a.customize(ng-hide='__.expanded' ng-click='__.expanded = true') Show 
settings
+        .panel-details(ng-if='__.expanded')
+            .details-row
+                +number('Batch size', policy + '.batchSize', name + 
'batchSize', enabled, '1', '0',
+                    'Number of entries to remove on shrink')
+            .details-row
+                +number('Max memory size', policy + '.maxMemorySize', name + 
'maxMemorySize', enabled, '0', '0',
+                    'Maximum allowed cache size in bytes')
+            .details-row
+                +number('Max size', policy + '.maxSize', name + 'maxSize', 
enabled, '100000', '0',
+                    'Maximum allowed size of cache before entry will start 
getting evicted')
+
+//- Mixin for clusters dropdown.
+mixin clusters(model, tip)
+    +dropdown-multiple('<span>Clusters:</span>' + '<a 
ui-sref="base.configuration.clusters({id: ' + model + '._id})"> (add)</a>',
+        model + '.clusters', 'clusters', 'true', 'Choose clusters', 'No 
clusters configured', 'clusters', tip)
+
+//- Mixin for caches dropdown.
+mixin caches(model, tip)
+    +dropdown-multiple('<span>Caches:</span>' + '<a 
ui-sref="base.configuration.caches({id: ' + model + '._id})"> (add)</a>',
+        model + '.caches', 'caches', 'true', 'Choose caches', 'No caches 
configured', 'caches', tip)
+
+//- Mixin for XML and Java preview.
+mixin preview-xml-java(master, generator, detail)
+    ignite-ui-ace-tabs
+        .preview-panel(ng-init='mode = false')
+            .preview-legend
+                a(ng-class='{active: !mode, inactive: mode}' ng-click='mode = 
false') XML
+                | &nbsp;
+                a(ng-class='{active: mode, inactive: !mode}' ng-click='mode = 
true') Java
+            .preview-content(ng-if='mode')
+                ignite-ui-ace-java(data-master=master data-generator=generator 
ng-model='$parent.data' data-detail=detail)
+            .preview-content(ng-if='!mode')
+                ignite-ui-ace-xml(data-master=master data-generator=generator 
ng-model='$parent.data' data-detail=detail)
+            .preview-content-empty(ng-if='!data')
+                label All Defaults
+
+//- LEGACY mixin for LEGACY tables.
+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')
+
+//- LEGACY mixin for LEGACY tables.
+mixin btn-add(click, tip)
+    i.tipField.fa.fa-plus(ng-click=click bs-tooltip=tip data-trigger = 'hover')
+
+//- LEGACY mixin for LEGACY tables.
+mixin btn-remove(click, tip)
+    i.tipField.fa.fa-remove(ng-click=click bs-tooltip=tip data-trigger='hover')
+
+//- LEGACY mixin for LEGACY tables.
+mixin btn-remove-cond(cond, click, tip)
+    i.tipField.fa.fa-remove(ng-show=cond ng-click=click bs-tooltip=tip 
data-trigger='hover')
+
+//- LEGACY mixin for LEGACY pair values tables.
+mixin table-pair-edit(tbl, prefix, keyPlaceholder, valPlaceholder, 
keyJavaBuiltInTypes, valueJavaBuiltInTypes, focusId, index, divider)
+    -var keyModel = tbl + '.' + prefix + 'Key'
+    -var valModel = tbl +'.' + prefix + 'Value'
+
+    -var keyFocusId = prefix + 'Key' + focusId
+    -var valFocusId = prefix + 'Value' + focusId
+
+    .col-xs-6.col-sm-6.col-md-6
+        .fieldSep !{divider}
+        .input-tip
+            if keyJavaBuiltInTypes
+                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 javaBuiltInClasses' 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(' + tbl + ', ' + index + ')'
+        -var btnSave = 'tablePairSave(tablePairValid, backupItem, ' + tbl + ', 
' + index + ')'
+        -var btnVisibleAndSave = btnVisible + ' && ' + btnSave
+
+        +btn-save(btnVisible, btnSave)
+        .input-tip
+            if valueJavaBuiltInTypes
+                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 javaBuiltInClasses' 
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()')

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/index.js 
b/modules/web-console/src/main/js/app/index.js
new file mode 100644
index 0000000..a8bd741
--- /dev/null
+++ b/modules/web-console/src/main/js/app/index.js
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 _ from 'lodash';
+import ace from 'ace';
+import angular from 'angular';
+import pdfMake from 'pdfmake';
+
+ace.config.set('basePath', '/jspm_packages/github/ajaxorg/[email protected]');
+
+window._ = _;
+window.require = ace.require; // TODO Should be removed after full refactoring 
to directives.
+window.pdfMake = pdfMake;
+
+import 'angular-animate';
+import 'angular-sanitize';
+import 'angular-strap';
+import 'angular-socket-io';
+import 'angular-retina';
+import 'angular-ui-router';
+import 'angular-ui-router-metatags';
+import 'angular-smart-table';
+import 'angular-ui-grid';
+import 'angular-drag-and-drop-lists';
+import 'angular-nvd3';
+import 'angular-tree-control';
+import 'angular-gridster';
+
+import 'bootstrap-carousel';
+import 'file-saver';
+import 'jszip';
+import 'query-command-supported';
+
+import 'public/stylesheets/style.css!';
+
+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 './decorator/select';
+import './decorator/tooltip';
+
+import './modules/Demo/Demo.module';
+import './modules/form/form.module';
+import './services/JavaTypes.service.js';
+import './modules/QueryNotebooks/QueryNotebooks.provider';
+
+import './modules/states/signin.state';
+import './modules/states/logout.state';
+import './modules/states/password.state';
+import './modules/states/configuration.state';
+import './modules/states/sql.state';
+import './modules/states/profile.state';
+import './modules/states/admin.state';
+
+// ignite:modules
+import './modules/user/user.module';
+import './modules/branding/branding.module';
+import './modules/navbar/navbar.module';
+import './modules/configuration/configuration.module';
+import './modules/getting-started/GettingStarted.provider';
+import './modules/dialog/dialog.module';
+import './modules/Version/Version.provider';
+import './modules/ace.module';
+import './modules/socket.module';
+import './modules/loading/loading.module';
+// endignite
+
+// Directives.
+import igniteHideOnStateChange from 
'./directives/hide-on-state-change/hide-on-state-change.directive';
+import igniteInformation from './directives/information/information.directive';
+import igniteUiAceTabs from './directives/ui-ace-tabs.directive';
+import igniteUiAceXml from './directives/ui-ace-xml/ui-ace-xml.directive';
+import igniteUiAceJava from './directives/ui-ace-java/ui-ace-java.directive';
+import igniteUiAcePom from './directives/ui-ace-pom/ui-ace-pom.directive';
+import igniteUiAceDocker from 
'./directives/ui-ace-docker/ui-ace-docker.directive';
+import igniteUiAcePojos from 
'./directives/ui-ace-pojos/ui-ace-pojos.directive';
+import igniteBsAffixUpdate from './directives/bs-affix-update.directive';
+import igniteСentered from './directives/centered/centered.directive.js';
+
+// Services.
+import cleanup from './services/cleanup.service';
+import confirm from './services/confirm.service';
+import IgniteInetAddress from './services/InetAddress.service';
+import IgniteCountries from './services/Countries.service';
+import IgniteChartColors from './services/ChartColors.service';
+import IgniteAgentMonitor from './services/AgentMonitor.service';
+import JavaTypes from './services/JavaTypes.service';
+
+// Providers.
+
+// Filters.
+import hasPojo from './filters/hasPojo.filter';
+import byName from './filters/byName.filter';
+
+// Generators
+import $generatorCommon from 'generator/generator-common';
+import $generatorJava from 'generator/generator-java';
+import $generatorOptional from 'generator/generator-optional';
+import $generatorProperties from 'generator/generator-properties';
+import $generatorReadme from 'generator/generator-readme';
+import $generatorXml from 'generator/generator-xml';
+
+window.$generatorCommon = $generatorCommon;
+window.$generatorJava = $generatorJava;
+window.$generatorOptional = $generatorOptional;
+window.$generatorProperties = $generatorProperties;
+window.$generatorReadme = $generatorReadme;
+window.$generatorXml = $generatorXml;
+
+// Add legacy logic;
+import consoleModule from 'controllers/common-module';
+window.consoleModule = consoleModule;
+
+import 'controllers/admin-controller';
+import 'controllers/caches-controller';
+import 'controllers/clusters-controller';
+import 'controllers/domains-controller';
+import 'controllers/igfs-controller';
+import 'controllers/profile-controller';
+import 'controllers/sql-controller';
+
+// Inject external modules.
+import 'ignite_modules_temp/index';
+
+angular
+.module('ignite-console', [
+    'ngRetina',
+    'btford.socket-io',
+    'ngAnimate',
+    'ngSanitize',
+    'mgcrea.ngStrap',
+    'ui.router',
+    'gridster',
+    // Base modules.
+    'ignite-console.user',
+    'ignite-console.branding',
+    'ignite-console.Form',
+    'ignite-console.QueryNotebooks',
+    'ignite-console.ace',
+    'ignite-console.demo',
+    'ignite-console.socket',
+    // States.
+    'ignite-console.states.login',
+    'ignite-console.states.logout',
+    'ignite-console.states.password',
+    'ignite-console.states.configuration',
+    'ignite-console.states.sql',
+    'ignite-console.states.profile',
+    'ignite-console.states.admin',
+    // Common modules.
+    'ignite-console.dialog',
+    'ignite-console.navbar',
+    'ignite-console.configuration',
+    'ignite-console.getting-started',
+    'ignite-console.version',
+    'ignite-console.loading',
+    // Ignite legacy module.
+    'ignite-console.legacy',
+    // Ignite modules.
+    'ignite-console.modules'
+])
+// Directives.
+.directive(...igniteHideOnStateChange)
+.directive(...igniteInformation)
+.directive(...igniteUiAceTabs)
+.directive(...igniteUiAceXml)
+.directive(...igniteUiAceJava)
+.directive(...igniteUiAcePom)
+.directive(...igniteUiAceDocker)
+.directive(...igniteUiAcePojos)
+.directive(...igniteBsAffixUpdate)
+.directive(...igniteСentered)
+// Services.
+.service(...cleanup)
+.service(...confirm)
+.service(...IgniteInetAddress)
+.service(...IgniteCountries)
+.service(...IgniteChartColors)
+.service(...IgniteAgentMonitor)
+.service(...JavaTypes)
+// Providers.
+// Filters.
+.filter(...hasPojo)
+.filter(...byName)
+.config(['$stateProvider', '$locationProvider', '$urlRouterProvider', 
($stateProvider, $locationProvider, $urlRouterProvider) => {
+    // Set up the states.
+    $stateProvider
+        .state('base', {
+            url: '',
+            abstract: true,
+            templateUrl: '/base.html'
+        })
+        .state('settings', {
+            url: '/settings',
+            abstract: true,
+            templateUrl: '/base.html'
+        });
+
+    $urlRouterProvider.otherwise('/');
+
+    $locationProvider.html5Mode(true);
+}])
+.config(['$animateProvider', ($animateProvider) => {
+    $animateProvider.classNameFilter(/^((?!(fa-spin)).)*$/);
+}])
+.run(['$rootScope', ($root) => {
+    $root._ = _;
+}])
+.run(['$rootScope', '$state', 'MetaTags', 'Auth', 'User', 
'IgniteAgentMonitor', ($root, $state, $meta, Auth, User, agentMonitor) => {
+    $root.$state = $state;
+
+    $root.$meta = $meta;
+
+    if (Auth.authorized) {
+        User.read()
+            .then((user) => $root.$broadcast('user', user))
+            .then(() => Auth.authorized && agentMonitor.init());
+    }
+}])
+.run(['$rootScope', ($root) => {
+    $root.$on('$stateChangeStart', () => {
+        _.forEach(angular.element('.modal'), (m) => 
angular.element(m).scope().$hide());
+    });
+}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js 
b/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js
new file mode 100644
index 0000000..adf5a82
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/Demo/Demo.module.js
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 angular from 'angular';
+
+import DEMO_INFO from 'app/data/demo-info.json!';
+
+angular
+.module('ignite-console.demo', [
+    'ignite-console.socket'
+])
+.config(['$stateProvider', ($stateProvider) => {
+    $stateProvider
+        .state('demo', {
+            abstract: true,
+            template: '<ui-view></ui-view>'
+        })
+        .state('demo.resume', {
+            url: '/demo',
+            controller: ['$state', ($state) => {
+                $state.go('base.configuration.clusters');
+            }],
+            metaTags: {
+            }
+        })
+        .state('demo.reset', {
+            url: '/demo/reset',
+            controller: ['$state', '$http', '$common', ($state, $http, 
$common) => {
+                $http.post('/api/v1/demo/reset')
+                    .then(() => $state.go('base.configuration.clusters'))
+                    .catch((errMsg) => {
+                        $state.go('base.configuration.clusters');
+
+                        $common.showError(errMsg);
+                    });
+            }],
+            metaTags: {
+            }
+        });
+}])
+.provider('Demo', ['$stateProvider', '$httpProvider', 
'igniteSocketFactoryProvider', function($state, $http, socketFactory) {
+    if (/(\/demo.*)/ig.test(location.pathname))
+        sessionStorage.setItem('IgniteDemoMode', 'true');
+
+    const enabled = sessionStorage.getItem('IgniteDemoMode') === 'true';
+
+    if (enabled) {
+        socketFactory.set({query: 'IgniteDemoMode=true'});
+
+        $http.interceptors.push('demoInterceptor');
+    }
+
+    this.$get = ['$rootScope', ($root) => {
+        $root.IgniteDemoMode = enabled;
+
+        return {enabled};
+    }];
+}])
+.factory('demoInterceptor', ['Demo', (Demo) => {
+    const isApiRequest = (url) => /\/api\/v1/ig.test(url);
+
+    return {
+        request(cfg) {
+            if (Demo.enabled && isApiRequest(cfg.url))
+                cfg.headers.IgniteDemoMode = true;
+
+            return cfg;
+        }
+    };
+}])
+.controller('demoController', ['$scope', '$state', '$window', '$confirm', 
($scope, $state, $window, $confirm) => {
+    const _openTab = (stateName) => $window.open($state.href(stateName), 
'_blank');
+
+    $scope.startDemo = () => {
+        if (!$scope.user.demoCreated)
+            return _openTab('demo.reset');
+
+        $confirm.confirm('Would you like to continue with previous demo 
session?', true, false)
+            .then((resume) => {
+                if (resume)
+                    return _openTab('demo.resume');
+
+                _openTab('demo.reset');
+            });
+    };
+
+    $scope.closeDemo = () => {
+        $window.close();
+    };
+}])
+.provider('igniteDemoInfo', [function() {
+    const items = DEMO_INFO;
+
+    this.update = (data) => items[0] = data;
+
+    this.$get = [() => {
+        return items;
+    }];
+}])
+.service('DemoInfo', ['$rootScope', '$modal', '$state', 'igniteDemoInfo', 
'IgniteAgentMonitor', ($rootScope, $modal, $state, igniteDemoInfo, 
agentMonitor) => {
+    const scope = $rootScope.$new();
+
+    function _fillPage() {
+        const model = igniteDemoInfo;
+
+        scope.title = model[0].title;
+        scope.message = model[0].message.join(' ');
+    }
+
+    const dialog = $modal({
+        templateUrl: '/templates/demo-info.html',
+        scope,
+        placement: 'center',
+        show: false,
+        backdrop: 'static'
+    });
+
+    scope.close = () => {
+        dialog.hide();
+    };
+
+    scope.gotoConfiguration = () => {
+        scope.$$postDigest(() => $state.go('base.configuration.clusters'));
+    };
+
+    scope.downloadAgent = () => {
+        const lnk = document.createElement('a');
+
+        lnk.setAttribute('href', '/api/v1/agent/download/zip');
+        lnk.setAttribute('target', '_self');
+        lnk.setAttribute('download', null);
+        lnk.style.display = 'none';
+
+        document.body.appendChild(lnk);
+
+        lnk.click();
+
+        document.body.removeChild(lnk);
+    };
+
+    return {
+        show: () => {
+            _fillPage();
+
+            dialog.$promise
+                .then(dialog.show)
+                .then(() => agentMonitor.awaitAgent())
+                .then(() => scope.hasAgents = true);
+        }
+    };
+}]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js
 
b/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js
new file mode 100644
index 0000000..7e4e523
--- /dev/null
+++ 
b/modules/web-console/src/main/js/app/modules/QueryNotebooks/QueryNotebooks.provider.js
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 angular from 'angular';
+
+angular
+    .module('ignite-console.QueryNotebooks', [
+
+    ])
+    .provider('QueryNotebooks', function() {
+        const _demoNotebook = {
+            name: 'SQL demo',
+            paragraphs: [
+                {
+                    name: 'Query with refresh rate',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT count(*)\nFROM "CarCache".Car',
+                    result: 'bar',
+                    timeLineSpan: '1',
+                    rate: {
+                        value: 3,
+                        unit: 1000,
+                        installed: true
+                    }
+                },
+                {
+                    name: 'Simple query',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT * FROM "CarCache".Car',
+                    result: 'table',
+                    timeLineSpan: '1',
+                    rate: {
+                        value: 30,
+                        unit: 1000,
+                        installed: false
+                    }
+                },
+                {
+                    name: 'Query with aggregates',
+                    cacheName: 'CarCache',
+                    pageSize: 50,
+                    query: 'SELECT p.name, count(*) AS cnt\nFROM 
"ParkingCache".Parking p\nINNER JOIN "CarCache".Car c\n  ON (p.id) = 
(c.parkingId)\nGROUP BY P.NAME',
+                    result: 'table',
+                    timeLineSpan: '1',
+                    rate: {
+                        value: 30,
+                        unit: 1000,
+                        installed: false
+                    }
+                }
+            ],
+            expandedParagraphs: [0, 1, 2]
+        };
+
+        this.$get = ['$q', '$http', '$rootScope', ($q, $http, $root) => {
+            return {
+                read(noteId) {
+                    if ($root.IgniteDemoMode)
+                        return $q.when(angular.copy(_demoNotebook));
+
+                    return $http.post('/api/v1/notebooks/get', {noteId})
+                        .then(({data}) => data)
+                        .catch(({data}) => $q.reject(data));
+                },
+                save(notebook) {
+                    if ($root.IgniteDemoMode)
+                        return $q.when();
+
+                    return $http.post('/api/v1/notebooks/save', notebook)
+                        .then(({data}) => data)
+                        .catch(({data}) => $q.reject(data));
+                },
+                remove(notebook) {
+                    if ($root.IgniteDemoMode)
+                        return $q.reject(`Removing "${notebook.name}" notebook 
is not supported.`);
+
+                    return $http.post('/api/v1/notebooks/remove', {_id: 
notebook._id})
+                        .then(() => {
+                            const idx = _.findIndex($root.notebooks, (item) => 
{
+                                return item._id === notebook._id;
+                            });
+
+                            if (idx >= 0) {
+                                $root.notebooks.splice(idx, 1);
+
+                                $root.rebuildDropdown();
+
+                                if (idx < $root.notebooks.length)
+                                    return $root.notebooks[idx];
+                            }
+
+                            if ($root.notebooks.length > 0)
+                                return $root.notebooks[$root.notebooks.length 
- 1];
+                        })
+                        .catch(({data}) => $q.reject(data));
+                }
+            };
+        }];
+    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/Version/Version.provider.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/Version/Version.provider.js 
b/modules/web-console/src/main/js/app/modules/Version/Version.provider.js
new file mode 100644
index 0000000..84582fb
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/Version/Version.provider.js
@@ -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.
+ */
+
+import angular from 'angular';
+
+angular
+    .module('ignite-console.version', [])
+    .provider('IgniteVersion', function() {
+        const version = {
+            version: '1.6'
+        };
+
+        this.update = (newVersion) => {
+            version.version = newVersion;
+        };
+
+        this.$get = [() => version];
+    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/ace.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/src/main/js/app/modules/ace.module.js 
b/modules/web-console/src/main/js/app/modules/ace.module.js
new file mode 100644
index 0000000..2477965
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/ace.module.js
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 angular from 'angular';
+
+angular
+    .module('ignite-console.ace', [])
+    .constant('igniteAceConfig', {})
+    .directive('igniteAce', ['igniteAceConfig', (aceConfig) => {
+        if (angular.isUndefined(window.ace))
+            throw new Error('ignite-ace need ace to work... (o rly?)');
+
+        /**
+         * Sets editor options such as the wrapping mode or the syntax checker.
+         *
+         * The supported options are:
+         *
+         *   <ul>
+         *     <li>showGutter</li>
+         *     <li>useWrapMode</li>
+         *     <li>onLoad</li>
+         *     <li>theme</li>
+         *     <li>mode</li>
+         *   </ul>
+         *
+         * @param acee
+         * @param session ACE editor session.
+         * @param {object} opts Options to be set.
+         */
+        const setOptions = (acee, session, opts) => {
+            // Sets the ace worker path, if running from concatenated or 
minified source.
+            if (angular.isDefined(opts.workerPath)) {
+                const config = window.ace.require('ace/config');
+
+                config.set('workerPath', opts.workerPath);
+            }
+
+            // Ace requires loading.
+            _.forEach(opts.require, (n) => window.ace.require(n));
+
+            // Boolean options.
+            if (angular.isDefined(opts.showGutter))
+                acee.renderer.setShowGutter(opts.showGutter);
+
+            if (angular.isDefined(opts.useWrapMode))
+                session.setUseWrapMode(opts.useWrapMode);
+
+            if (angular.isDefined(opts.showInvisibles))
+                acee.renderer.setShowInvisibles(opts.showInvisibles);
+
+            if (angular.isDefined(opts.showIndentGuides))
+                acee.renderer.setDisplayIndentGuides(opts.showIndentGuides);
+
+            if (angular.isDefined(opts.useSoftTabs))
+                session.setUseSoftTabs(opts.useSoftTabs);
+
+            if (angular.isDefined(opts.showPrintMargin))
+                acee.setShowPrintMargin(opts.showPrintMargin);
+
+            // Commands.
+            if (angular.isDefined(opts.disableSearch) && opts.disableSearch) {
+                acee.commands.addCommands([{
+                    name: 'unfind',
+                    bindKey: {
+                        win: 'Ctrl-F',
+                        mac: 'Command-F'
+                    },
+                    exec: _.constant(false),
+                    readOnly: true
+                }]);
+            }
+
+            // Base options.
+            if (angular.isString(opts.theme))
+                acee.setTheme('ace/theme/' + opts.theme);
+
+            if (angular.isString(opts.mode))
+                session.setMode('ace/mode/' + opts.mode);
+
+            if (angular.isDefined(opts.firstLineNumber)) {
+                if (angular.isNumber(opts.firstLineNumber))
+                    session.setOption('firstLineNumber', opts.firstLineNumber);
+                else if (angular.isFunction(opts.firstLineNumber))
+                    session.setOption('firstLineNumber', 
opts.firstLineNumber());
+            }
+
+            // Advanced options.
+            if (angular.isDefined(opts.advanced)) {
+                for (const key in opts.advanced) {
+                    if (opts.advanced.hasOwnProperty(key)) {
+                        // Create a javascript object with the key and value.
+                        const obj = {name: key, value: opts.advanced[key]};
+
+                        // Try to assign the option to the ace editor.
+                        acee.setOption(obj.name, obj.value);
+                    }
+                }
+            }
+
+            // Advanced options for the renderer.
+            if (angular.isDefined(opts.rendererOptions)) {
+                for (const key in opts.rendererOptions) {
+                    if (opts.rendererOptions.hasOwnProperty(key)) {
+                        // Create a javascript object with the key and value.
+                        const obj = {name: key, value: 
opts.rendererOptions[key]};
+
+                        // Try to assign the option to the ace editor.
+                        acee.renderer.setOption(obj.name, obj.value);
+                    }
+                }
+            }
+
+            // onLoad callbacks.
+            _.forEach(opts.callbacks, (cb) => {
+                if (angular.isFunction(cb))
+                    cb(acee);
+            });
+        };
+
+        return {
+            restrict: 'EA',
+            require: ['?ngModel', '?^form'],
+            link: (scope, elm, attrs, [ngModel, form]) => {
+                /**
+                 * Corresponds the igniteAceConfig ACE configuration.
+                 *
+                 * @type object
+                 */
+                const options = aceConfig.ace || {};
+
+                /**
+                 * IgniteAceConfig merged with user options via json in 
attribute or data binding.
+                 *
+                 * @type object
+                 */
+                let opts = angular.extend({}, options, 
scope.$eval(attrs.igniteAce));
+
+                /**
+                 * ACE editor.
+                 *
+                 * @type object
+                 */
+                const acee = window.ace.edit(elm[0]);
+
+                /**
+                 * ACE editor session.
+                 *
+                 * @type object
+                 * @see [EditSession]{@link 
http://ace.c9.io/#nav=api&api=edit_session}
+                 */
+                const session = acee.getSession();
+
+                /**
+                 * Reference to a change listener created by the listener 
factory.
+                 *
+                 * @function
+                 * @see listenerFactory.onChange
+                 */
+                let onChangeListener;
+
+                /**
+                 * Creates a change listener which propagates the change event 
and the editor session
+                 * to the callback from the user option onChange.
+                 * It might be exchanged during runtime, if this happens the 
old listener will be unbound.
+                 *
+                 * @param callback Callback function defined in the user 
options.
+                 * @see onChangeListener
+                 */
+                const onChangeFactory = (callback) => {
+                    return (e) => {
+                        const newValue = session.getValue();
+
+                        if (ngModel && newValue !== ngModel.$viewValue &&
+                                // HACK make sure to only trigger the apply 
outside of the
+                                // digest loop 'cause ACE is actually using 
this callback
+                                // for any text transformation !
+                            !scope.$$phase && !scope.$root.$$phase)
+                            scope.$eval(() => ngModel.$setViewValue(newValue));
+
+                        if (angular.isDefined(callback)) {
+                            scope.$evalAsync(() => {
+                                if (angular.isFunction(callback))
+                                    callback([e, acee]);
+                                else
+                                    throw new Error('ignite-ace use a function 
as callback');
+                            });
+                        }
+                    };
+                };
+
+                attrs.$observe('readonly', (value) => acee.setReadOnly(!!value 
|| value === ''));
+
+                // Value Blind.
+                if (ngModel) {
+                    // Remove "ngModel" controller from parent form for 
correct dirty checks.
+                    form && form.$removeControl(ngModel);
+
+                    ngModel.$formatters.push((value) => {
+                        if (angular.isUndefined(value) || value === null)
+                            return '';
+
+                        if (angular.isObject(value) || angular.isArray(value))
+                            throw new Error('ignite-ace cannot use an object 
or an array as a model');
+
+                        return value;
+                    });
+
+                    ngModel.$render = () => 
session.setValue(ngModel.$viewValue);
+
+                    acee.on('change', () => 
ngModel.$setViewValue(acee.getValue()));
+                }
+
+                // Listen for option updates.
+                const updateOptions = (current, previous) => {
+                    if (current === previous)
+                        return;
+
+                    opts = angular.extend({}, options, 
scope.$eval(attrs.igniteAce));
+
+                    opts.callbacks = [opts.onLoad];
+
+                    // Also call the global onLoad handler.
+                    if (opts.onLoad !== options.onLoad)
+                        opts.callbacks.unshift(options.onLoad);
+
+                    // Unbind old change listener.
+                    session.removeListener('change', onChangeListener);
+
+                    // Bind new change listener.
+                    onChangeListener = onChangeFactory(opts.onChange);
+
+                    session.on('change', onChangeListener);
+
+                    setOptions(acee, session, opts);
+                };
+
+                scope.$watch(attrs.igniteAce, updateOptions, /* deep watch */ 
true);
+
+                // Set the options here, even if we try to watch later,
+                // if this line is missing things go wrong (and the tests will 
also fail).
+                updateOptions(options);
+
+                elm.on('$destroy', () => {
+                    acee.session.$stopWorker();
+                    acee.destroy();
+                });
+
+                scope.$watch(() => [elm[0].offsetWidth, elm[0].offsetHeight],
+                    () => {
+                        acee.resize();
+                        acee.renderer.updateFull();
+                    }, true);
+            }
+        };
+    }]);

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/branding.module.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/branding.module.js 
b/modules/web-console/src/main/js/app/modules/branding/branding.module.js
new file mode 100644
index 0000000..bd28c68
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/branding/branding.module.js
@@ -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.
+ */
+
+import angular from 'angular';
+
+import IgniteBranding from './branding.provider';
+
+import igniteHeaderLogo from './header-logo.directive';
+import igniteHeaderTitle from './header-title.directive';
+import igniteTerms from './terms.directive';
+import igniteFeatures from './features.directive';
+import igniteFooter from './footer.directive';
+import ignitePoweredByApache from './powered-by-apache.directive';
+
+angular
+.module('ignite-console.branding', [
+    'ui.router.metatags'
+])
+.provider(...IgniteBranding)
+.config(['UIRouterMetatagsProvider', (UIRouterMetatagsProvider) => {
+    UIRouterMetatagsProvider
+        .setDefaultTitle('Apache Ignite - Management Tool and Configuration 
Wizard')
+        .setTitleSuffix(' – Apache Ignite Web Console')
+        .setDefaultDescription('The Apache Ignite Web Console is an 
interactive management tool and configuration wizard which walks you through 
the creation of config files. Try it now.');
+}])
+.directive(...ignitePoweredByApache)
+.directive(...igniteHeaderLogo)
+.directive(...igniteHeaderTitle)
+.directive(...igniteTerms)
+.directive(...igniteFeatures)
+.directive(...igniteFooter);
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/branding.provider.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/branding.provider.js 
b/modules/web-console/src/main/js/app/modules/branding/branding.provider.js
new file mode 100644
index 0000000..ce14b34
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/branding/branding.provider.js
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 ['IgniteBranding', [function() {
+    let titleSuffix = ' – Apache Ignite Web Console';
+
+    let headerLogo = '/images/ignite-logo.png';
+
+    let headerText = 'Management console for Apache Ignite';
+
+    let showIgniteLogo = false;
+
+    let footerHtml = [
+        '<p>Apache Ignite Web Console</p>',
+        '<p>© 2016 The Apache Software Foundation.</p>',
+        '<p>Apache, Apache Ignite, the Apache feather and the Apache Ignite 
logo are trademarks of The Apache Software Foundation.</p>'
+    ];
+
+    let termsState;
+
+    let featuresHtml = [
+        '<p>Web Console is an interactive management tool which allows 
to:</p>',
+        '<ul>',
+        '   <li>Create and download cluster configurations</li>',
+        '   <li>Automatically import domain model from any RDBMS</li>',
+        '   <li>Connect to cluster and run SQL analytics on it</li>',
+        '</ul>'
+    ];
+
+    /**
+     * Change title suffix.
+     *
+     * @param {String} suffix.
+     */
+    this.titleSuffix = (suffix) => {
+        titleSuffix = suffix;
+    };
+
+    /**
+     * Change logo in header.
+     *
+     * @param {String} url Logo path.
+     */
+    this.headerLogo = (url) => {
+        headerLogo = url;
+
+        showIgniteLogo = true;
+    };
+
+    /**
+     * Change text in header.
+     *
+     * @param {String} text Header text.
+     */
+    this.headerText = (text) => {
+        headerText = text;
+    };
+
+    /**
+     * Change text in features.
+     *
+     * @param {Array.<String>} rows Features text.
+     */
+    this.featuresHtml = (rows) => {
+        featuresHtml = rows;
+    };
+
+    /**
+     * Change text in footer.
+     *
+     * @param {Array.<String>} rows Footer text.
+     */
+    this.footerHtml = (rows) => {
+        footerHtml = rows;
+    };
+
+    /**
+     * Set terms and conditions stage.
+     *
+     * @param {String} state
+     */
+    this.termsState = (state) => {
+        termsState = state;
+    };
+
+    this.$get = [() => {
+        return {
+            titleSuffix,
+            headerLogo,
+            headerText,
+            featuresHtml: featuresHtml.join('\n'),
+            footerHtml: footerHtml.join('\n'),
+            showIgniteLogo,
+            termsState
+        };
+    }];
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/features.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/features.directive.js 
b/modules/web-console/src/main/js/app/modules/branding/features.directive.js
new file mode 100644
index 0000000..9226a3f
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/branding/features.directive.js
@@ -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.
+ */
+
+const template = '<div class="features" ng-bind-html="features.html"></div>';
+
+export default ['igniteFeatures', ['IgniteBranding', (branding) => {
+    function controller() {
+        const ctrl = this;
+
+        ctrl.html = branding.featuresHtml;
+    }
+
+    return {
+        restrict: 'E',
+        template,
+        controller,
+        controllerAs: 'features',
+        replace: true
+    };
+}]];
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/footer.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/footer.directive.js 
b/modules/web-console/src/main/js/app/modules/branding/footer.directive.js
new file mode 100644
index 0000000..f0b1994
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/branding/footer.directive.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 template = '<div class="footer" ng-bind-html="footer.html"></div>';
+
+export default ['igniteFooter', ['IgniteBranding', (branding) => {
+    function controller() {
+        const ctrl = this;
+
+        ctrl.html = branding.footerHtml;
+    }
+
+    return {
+        restrict: 'E',
+        template,
+        controller,
+        controllerAs: 'footer',
+        replace: true
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/header-logo.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/header-logo.directive.js 
b/modules/web-console/src/main/js/app/modules/branding/header-logo.directive.js
new file mode 100644
index 0000000..1bfbc45
--- /dev/null
+++ 
b/modules/web-console/src/main/js/app/modules/branding/header-logo.directive.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 template from './header-logo.jade!';
+
+export default ['igniteHeaderLogo', ['IgniteBranding', (branding) => {
+    function controller() {
+        const ctrl = this;
+
+        ctrl.url = branding.headerLogo;
+    }
+
+    return {
+        restrict: 'E',
+        template,
+        controller,
+        controllerAs: 'logo',
+        replace: true
+    };
+}]];

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/header-logo.jade
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/header-logo.jade 
b/modules/web-console/src/main/js/app/modules/branding/header-logo.jade
new file mode 100644
index 0000000..b807921
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/branding/header-logo.jade
@@ -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.
+
+a(href='/')
+    img.navbar-brand(ng-src='{{logo.url}}' height='40')

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/header-title.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/header-title.directive.js
 
b/modules/web-console/src/main/js/app/modules/branding/header-title.directive.js
new file mode 100644
index 0000000..d560e0a
--- /dev/null
+++ 
b/modules/web-console/src/main/js/app/modules/branding/header-title.directive.js
@@ -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.
+ */
+
+const template = '<h1 class="title">{{::title.text}}</h1>';
+
+export default ['igniteHeaderTitle', ['IgniteBranding', (branding) => {
+    function controller() {
+        const ctrl = this;
+
+        ctrl.text = branding.headerText;
+    }
+
+    return {
+        restrict: 'E',
+        template,
+        controller,
+        controllerAs: 'title',
+        replace: true
+    };
+}]];
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.directive.js
 
b/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.directive.js
new file mode 100644
index 0000000..37f1dec
--- /dev/null
+++ 
b/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.directive.js
@@ -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.
+ */
+
+import templatePoweredByApache from './powered-by-apache.jade!';
+
+export default ['ignitePoweredByApache', ['IgniteBranding', (branding) => {
+    function controller() {
+        const ctrl = this;
+
+        ctrl.show = branding.showIgniteLogo;
+    }
+
+    return {
+        restrict: 'E',
+        template: templatePoweredByApache,
+        controller,
+        controllerAs: 'poweredBy',
+        replace: true
+    };
+}]];
+

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.jade
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.jade 
b/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.jade
new file mode 100644
index 0000000..af9aadf
--- /dev/null
+++ 
b/modules/web-console/src/main/js/app/modules/branding/powered-by-apache.jade
@@ -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.
+
+a(ng-if='poweredBy.show' href='//ignite.apache.org' target='_blank')
+    img(ng-src='/images/pb-ignite.png' height='65')

http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/modules/branding/terms.directive.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/src/main/js/app/modules/branding/terms.directive.js 
b/modules/web-console/src/main/js/app/modules/branding/terms.directive.js
new file mode 100644
index 0000000..0207745
--- /dev/null
+++ b/modules/web-console/src/main/js/app/modules/branding/terms.directive.js
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 ['igniteTerms', ['IgniteBranding', (branding) => {
+    function controller() {
+        const ctrl = this;
+
+        ctrl.termsState = branding.termsState;
+    }
+
+    return {
+        restrict: 'A',
+        controller,
+        controllerAs: 'terms'
+    };
+}]];

Reply via email to