You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ak...@apache.org on 2017/12/06 03:37:41 UTC

[3/5] ignite git commit: IGNITE-6390 Web Console: Added component for cluster selection.

http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/page-queries/template.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-queries/template.tpl.pug b/modules/web-console/frontend/app/components/page-queries/template.tpl.pug
new file mode 100644
index 0000000..b2173f7
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-queries/template.tpl.pug
@@ -0,0 +1,385 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+include /app/helpers/jade/mixins
+
+mixin btn-toolbar(btn, click, tip, focusId)
+    i.btn.btn-default.fa(class=btn ng-click=click bs-tooltip='' data-title=tip ignite-on-click-focus=focusId data-trigger='hover' data-placement='bottom')
+
+mixin btn-toolbar-data(btn, kind, tip)
+    i.btn.btn-default.fa(class=btn ng-click=`setResult(paragraph, '${kind}')` ng-class=`{active: resultEq(paragraph, '${kind}')}` bs-tooltip='' data-title=tip data-trigger='hover' data-placement='bottom')
+
+mixin result-toolbar
+    .btn-group(ng-model='paragraph.result' ng-click='$event.stopPropagation()' style='left: 50%; margin: 0 0 0 -70px;display: block;')
+        +btn-toolbar-data('fa-table', 'table', 'Show data in tabular form')
+        +btn-toolbar-data('fa-bar-chart', 'bar', 'Show bar chart<br/>By default first column - X values, second column - Y values<br/>In case of one column it will be treated as Y values')
+        +btn-toolbar-data('fa-pie-chart', 'pie', 'Show pie chart<br/>By default first column - pie labels, second column - pie values<br/>In case of one column it will be treated as pie values')
+        +btn-toolbar-data('fa-line-chart', 'line', 'Show line chart<br/>By default first column - X values, second column - Y values<br/>In case of one column it will be treated as Y values')
+        +btn-toolbar-data('fa-area-chart', 'area', 'Show area chart<br/>By default first column - X values, second column - Y values<br/>In case of one column it will be treated as Y values')
+
+mixin chart-settings
+    .total.row
+        .col-xs-7
+            .chart-settings-link(ng-show='paragraph.chart && paragraph.chartColumns.length > 0')
+                a(title='Click to show chart settings dialog' ng-click='$event.stopPropagation()' bs-popover data-template-url='{{ $ctrl.chartSettingsTemplateUrl }}' data-placement='bottom' data-auto-close='1' data-trigger='click')
+                    i.fa.fa-bars
+                    | Chart settings
+                div(ng-show='paragraphTimeSpanVisible(paragraph)')
+                    label Show
+                    button.select-manual-caret.btn.btn-default(ng-model='paragraph.timeLineSpan' ng-change='applyChartSettings(paragraph)' bs-options='item for item in timeLineSpans' bs-select data-caret-html='<span class="caret"></span>')
+                    label min
+
+                div
+                    label Duration: #[b {{paragraph.duration | duration}}]
+                    label.margin-left-dflt(ng-show='paragraph.localQueryMode') NodeID8: #[b {{paragraph.resNodeId | id8}}]
+        .col-xs-2
+            +result-toolbar
+
+mixin notebook-rename
+    .docs-header.notebook-header
+        h1.col-sm-6(ng-hide='notebook.edit')
+            label(style='max-width: calc(100% - 60px)') {{notebook.name}}
+            .btn-group(ng-if='!demo')
+                +btn-toolbar('fa-pencil', 'notebook.edit = true;notebook.editName = notebook.name', 'Rename notebook')
+                +btn-toolbar('fa-trash', 'removeNotebook(notebook)', 'Remove notebook')
+        h1.col-sm-6(ng-show='notebook.edit')
+            i.btn.fa.fa-floppy-o(ng-show='notebook.editName' ng-click='renameNotebook(notebook.editName)' bs-tooltip data-title='Save notebook name' data-trigger='hover')
+            .input-tip
+                input.form-control(ng-model='notebook.editName' required ignite-on-enter='renameNotebook(notebook.editName)' ignite-on-escape='notebook.edit = false;')
+        h1.pull-right
+            a.dropdown-toggle(style='margin-right: 20px' data-toggle='dropdown' bs-dropdown='scrollParagraphs' data-placement='bottom-right') Scroll to query
+                span.caret
+            button.btn.btn-default(style='margin-top: 2px' ng-click='addQuery()' ignite-on-click-focus=focusId)
+                i.fa.fa-fw.fa-plus
+                | Add query
+
+            button.btn.btn-default(style='margin-top: 2px' ng-click='addScan()' ignite-on-click-focus=focusId)
+                i.fa.fa-fw.fa-plus
+                | Add scan
+
+mixin notebook-error
+    h2 Failed to load notebook
+    label.col-sm-12 Notebook not accessible any more. Go back to configuration or open to another notebook.
+    button.h3.btn.btn-primary(ui-sref='base.configuration.tabs.advanced.clusters') Back to configuration
+
+mixin paragraph-rename
+    .col-sm-6(ng-hide='paragraph.edit')
+        i.fa(ng-class='paragraphExpanded(paragraph) ? "fa-chevron-circle-down" : "fa-chevron-circle-right"')
+        label {{paragraph.name}}
+
+        .btn-group(ng-hide='notebook.paragraphs.length > 1')
+            +btn-toolbar('fa-pencil', 'paragraph.edit = true; paragraph.editName = paragraph.name; $event.stopPropagation();', 'Rename query', 'paragraph-name-{{paragraph.id}}')
+
+        .btn-group(ng-show='notebook.paragraphs.length > 1' ng-click='$event.stopPropagation();')
+            +btn-toolbar('fa-pencil', 'paragraph.edit = true; paragraph.editName = paragraph.name;', 'Rename query', 'paragraph-name-{{paragraph.id}}')
+            +btn-toolbar('fa-remove', 'removeParagraph(paragraph)', 'Remove query')
+
+    .col-sm-6(ng-show='paragraph.edit')
+        i.tipLabel.fa(style='float: left;' ng-class='paragraphExpanded(paragraph) ? "fa-chevron-circle-down" : "fa-chevron-circle-right"')
+        i.tipLabel.fa.fa-floppy-o(style='float: right;' ng-show='paragraph.editName' ng-click='renameParagraph(paragraph, paragraph.editName); $event.stopPropagation();' bs-tooltip data-title='Save query name' data-trigger='hover')
+        .input-tip
+            input.form-control(id='paragraph-name-{{paragraph.id}}' ng-model='paragraph.editName' required ng-click='$event.stopPropagation();' ignite-on-enter='renameParagraph(paragraph, paragraph.editName)' ignite-on-escape='paragraph.edit = false')
+
+mixin query-settings
+    .panel-top-align
+        label.tipLabel(bs-tooltip data-placement='bottom' data-title='Configure periodical execution of last successfully executed query') Refresh rate:
+            button.btn.btn-default.fa.fa-clock-o.tipLabel(ng-class='{"btn-info": paragraph.rate && paragraph.rate.installed}' bs-popover data-template-url='{{ $ctrl.paragraphRateTemplateUrl }}' data-placement='left' data-auto-close='1' data-trigger='click') {{rateAsString(paragraph)}}
+
+        label.tipLabel(bs-tooltip data-placement='bottom' data-title='Max number of rows to show in query result as one page') Page size:
+            button.btn.btn-default.select-toggle.tipLabel(ng-model='paragraph.pageSize' bs-select bs-options='item for item in pageSizes')
+
+        label.tipLabel(bs-tooltip data-placement='bottom' data-title='Limit query max results to specified number of pages') Max pages:
+            button.btn.btn-default.select-toggle.tipLabel(ng-model='paragraph.maxPages' bs-select bs-options='item.value as item.label for item in maxPages')
+
+        .panel-tip-container
+            .row(ng-if='nonCollocatedJoinsAvailable(paragraph)')
+                label.tipLabel(bs-tooltip data-placement='bottom' data-title='Non-collocated joins is a special mode that allow to join data across cluster without collocation.<br/>\
+                    Nested joins are not supported for now.<br/>\
+                    <b>NOTE</b>: In some cases it may consume more heap memory or may take a long time than collocated joins.' data-trigger='hover')
+                    input(type='checkbox' ng-model='paragraph.nonCollocatedJoins')
+                    span Allow non-collocated joins
+            .row(ng-if='enforceJoinOrderAvailable(paragraph)')
+                label.tipLabel(bs-tooltip data-placement='bottom' data-title='Enforce join order of tables in the query.<br/>\
+                    If <b>set</b>, then query optimizer will not reorder tables within join.<br/>\
+                    <b>NOTE:</b> It is not recommended to enable this property unless you have verified that\
+                    indexes are not selected in optimal order.' data-trigger='hover')
+                    input(type='checkbox' ng-model='paragraph.enforceJoinOrder')
+                    span Enforce join order
+            .row(ng-if='lazyQueryAvailable(paragraph)')
+                label.tipLabel(bs-tooltip data-placement='bottom' data-title='By default Ignite attempts to fetch the whole query result set to memory and send it to the client.<br/>\
+                    For small and medium result sets this provides optimal performance and minimize duration of internal database locks, thus increasing concurrency.<br/>\
+                    If result set is too big to fit in available memory this could lead to excessive GC pauses and even OutOfMemoryError.<br/>\
+                    Use this flag as a hint for Ignite to fetch result set lazily, thus minimizing memory consumption at the cost of moderate performance hit.' data-trigger='hover')
+                    input(type='checkbox' ng-model='paragraph.lazy')
+                    span Lazy result set
+
+mixin query-actions
+    button.btn.btn-primary(ng-disabled='!queryAvailable(paragraph)' ng-click='execute(paragraph)')
+        div
+            i.fa.fa-fw.fa-play(ng-hide='paragraph.executionInProgress(false)')
+            i.fa.fa-fw.fa-refresh.fa-spin(ng-show='paragraph.executionInProgress(false)')
+            span.tipLabelExecute Execute
+    button.btn.btn-primary(ng-disabled='!queryAvailable(paragraph)' ng-click='execute(paragraph, true)')
+        div
+            i.fa.fa-fw.fa-play(ng-hide='paragraph.executionInProgress(true)')
+            i.fa.fa-fw.fa-refresh.fa-spin(ng-show='paragraph.executionInProgress(true)')
+            span.tipLabelExecute Execute on selected node
+
+
+    a.btn.btn-default(ng-disabled='!queryAvailable(paragraph)' ng-click='explain(paragraph)' data-placement='bottom' bs-tooltip='' data-title='{{queryTooltip(paragraph, "explain query")}}') Explain
+
+mixin table-result-heading-query
+    .total.row
+        .col-xs-7
+            grid-column-selector(grid-api='paragraph.gridOptions.api')
+                .fa.fa-bars.icon
+            label Page: #[b {{paragraph.page}}]
+            label.margin-left-dflt Results so far: #[b {{paragraph.rows.length + paragraph.total}}]
+            label.margin-left-dflt Duration: #[b {{paragraph.duration | duration}}]
+            label.margin-left-dflt(ng-show='paragraph.localQueryMode') NodeID8: #[b {{paragraph.resNodeId | id8}}]
+        .col-xs-2
+            div(ng-if='paragraph.qryType === "query"')
+                +result-toolbar
+        .col-xs-3
+            .pull-right
+                .btn-group.panel-tip-container
+                    button.btn.btn-primary.btn--with-icon(
+                        ng-click='exportCsv(paragraph)'
+
+                        ng-disabled='paragraph.loading'
+
+                        bs-tooltip=''
+                        ng-attr-title='{{ queryTooltip(paragraph, "export query results") }}'
+
+                        data-trigger='hover'
+                        data-placement='bottom'
+                    )
+                        svg(ignite-icon='csv' ng-if='!paragraph.csvIsPreparing')
+                        i.fa.fa-fw.fa-refresh.fa-spin(ng-if='paragraph.csvIsPreparing')
+                        span Export
+
+                    -var options = [{ text: 'Export', click: 'exportCsv(paragraph)' }, { text: 'Export all', click: 'exportCsvAll(paragraph)' }, { divider: true }, { text: '<span title="Copy current result page to clipboard">Copy to clipboard</span>', click: 'exportCsvToClipBoard(paragraph)' }]
+                    button.btn.dropdown-toggle.btn-primary(
+                        ng-disabled='paragraph.loading'
+
+                        bs-dropdown=`${JSON.stringify(options)}`
+
+                        data-toggle='dropdown'
+                        data-container='body'
+                        data-placement='bottom-right'
+                        data-html='true'
+                    )
+                        span.caret
+
+
+
+mixin table-result-heading-scan
+    .total.row
+        .col-xs-7
+            grid-column-selector(grid-api='paragraph.gridOptions.api')
+                .fa.fa-bars.icon
+            label Page: #[b {{paragraph.page}}]
+            label.margin-left-dflt Results so far: #[b {{paragraph.rows.length + paragraph.total}}]
+            label.margin-left-dflt Duration: #[b {{paragraph.duration | duration}}]
+            label.margin-left-dflt(ng-show='paragraph.localQueryMode') NodeID8: #[b {{paragraph.resNodeId | id8}}]
+        .col-xs-2
+            div(ng-if='paragraph.qryType === "query"')
+                +result-toolbar
+        .col-xs-3
+            .pull-right
+                .btn-group.panel-tip-container
+                    // TODO: replace this logic for exporting under one component
+                    button.btn.btn-primary.btn--with-icon(
+                        ng-click='exportCsv(paragraph)'
+
+                        ng-disabled='paragraph.loading || paragraph.csvIsPreparing'
+
+                        bs-tooltip=''
+                        ng-attr-title='{{ scanTooltip(paragraph) }}'
+
+                        data-trigger='hover'
+                        data-placement='bottom'
+                    )
+                        svg(ignite-icon='csv' ng-if='!paragraph.csvIsPreparing')
+                        i.fa.fa-fw.fa-refresh.fa-spin(ng-if='paragraph.csvIsPreparing')
+                        span Export
+
+                    -var options = [{ text: "Export", click: 'exportCsv(paragraph)' }, { text: 'Export all', click: 'exportCsvAll(paragraph)' }, { divider: true }, { text: '<span title="Copy current result page to clipboard">Copy to clipboard</span>', click: 'exportCsvToClipBoard(paragraph)' }]
+                    button.btn.dropdown-toggle.btn-primary(
+                        ng-disabled='paragraph.loading || paragraph.csvIsPreparing'
+
+                        bs-dropdown=`${JSON.stringify(options)}`
+
+                        data-toggle='dropdown'
+                        data-container='body'
+                        data-placement='bottom-right'
+                        data-html='true'
+                    )
+                        span.caret
+
+mixin table-result-body
+    .grid(ui-grid='paragraph.gridOptions' ui-grid-resize-columns ui-grid-exporter)
+
+mixin chart-result
+    div(ng-hide='paragraph.scanExplain()')
+        +chart-settings
+        .empty(ng-show='paragraph.chartColumns.length > 0 && !paragraph.chartColumnsConfigured()') Cannot display chart. Please configure axis using #[b Chart settings]
+        .empty(ng-show='paragraph.chartColumns.length == 0') Cannot display chart. Result set must contain Java build-in type columns. Please change query and execute it again.
+        div(ng-show='paragraph.chartColumnsConfigured()')
+            div(ng-show='paragraph.timeLineSupported() || !paragraph.chartTimeLineEnabled()')
+                div(ng-repeat='chart in paragraph.charts')
+                    nvd3(options='chart.options' data='chart.data' api='chart.api')
+            .empty(ng-show='!paragraph.timeLineSupported() && paragraph.chartTimeLineEnabled()') Pie chart does not support 'TIME_LINE' column for X-axis. Please use another column for X-axis or switch to another chart.
+    .empty(ng-show='paragraph.scanExplain()')
+        .row
+            .col-xs-4.col-xs-offset-4
+                +result-toolbar
+        label.margin-top-dflt Charts do not support #[b Explain] and #[b Scan] query
+
+mixin paragraph-scan
+    .panel-heading(bs-collapse-toggle)
+        .row
+            +paragraph-rename
+    .panel-collapse(role='tabpanel' bs-collapse-target)
+        .col-sm-12.sql-controls
+            .col-sm-3
+                +dropdown-required('Cache:', 'paragraph.cacheName', '"cache"', 'true', 'false', 'Choose cache', 'caches')
+            .col-sm-3
+                +text-enabled('Filter:', 'paragraph.filter', '"filter"', true, false, 'Enter filter')
+                    label.btn.btn-default.ignite-form-field__btn(ng-click='paragraph.caseSensitive = !paragraph.caseSensitive')
+                        input(type='checkbox' ng-model='paragraph.caseSensitive')
+                        span(bs-tooltip data-title='Select this checkbox for case sensitive search') Cs
+            label.tipLabel(bs-tooltip data-placement='bottom' data-title='Max number of rows to show in query result as one page') Page size:
+                button.btn.btn-default.select-toggle.tipLabel(ng-model='paragraph.pageSize' bs-select bs-options='item for item in pageSizes')
+
+        .col-sm-12.sql-controls
+            button.btn.btn-primary(ng-disabled='!scanAvailable(paragraph)' ng-click='scan(paragraph)')
+                div
+                    i.fa.fa-fw.fa-play(ng-hide='paragraph.checkScanInProgress(false)')
+                    i.fa.fa-fw.fa-refresh.fa-spin(ng-show='paragraph.checkScanInProgress(false)')
+                    span.tipLabelExecute Scan
+
+            button.btn.btn-primary(ng-disabled='!scanAvailable(paragraph)' ng-click='scan(paragraph, true)')
+                    i.fa.fa-fw.fa-play(ng-hide='paragraph.checkScanInProgress(true)')
+                    i.fa.fa-fw.fa-refresh.fa-spin(ng-show='paragraph.checkScanInProgress(true)')
+                    span.tipLabelExecute Scan on selected node
+
+        .col-sm-12.sql-result(ng-if='paragraph.queryExecuted()' ng-switch='paragraph.resultType()')
+            .error(ng-switch-when='error') Error: {{paragraph.error.message}}
+            .empty(ng-switch-when='empty') Result set is empty. Duration: #[b {{paragraph.duration | duration}}]
+            .table(ng-switch-when='table')
+                +table-result-heading-scan
+                +table-result-body
+            .footer.clearfix()
+                .pull-left
+                    | Showing results for scan of #[b {{ paragraph.queryArgs.cacheName | defaultName }}]
+                    span(ng-if='paragraph.queryArgs.filter') &nbsp; with filter: #[b {{ paragraph.queryArgs.filter }}]
+                    span(ng-if='paragraph.queryArgs.localNid') &nbsp; on node: #[b {{ paragraph.queryArgs.localNid | limitTo:8 }}]
+
+                -var nextVisibleCondition = 'paragraph.resultType() != "error" && paragraph.queryId && paragraph.nonRefresh() && (paragraph.table() || paragraph.chart() && !paragraph.scanExplain())'
+
+                .pull-right(ng-show=`${nextVisibleCondition}` ng-class='{disabled: paragraph.loading}' ng-click='!paragraph.loading && nextPage(paragraph)')
+                    i.fa.fa-chevron-circle-right
+                    a Next
+
+mixin paragraph-query
+    .row.panel-heading(bs-collapse-toggle)
+        +paragraph-rename
+    .panel-collapse(role='tabpanel' bs-collapse-target)
+        .col-sm-12
+            .col-xs-8.col-sm-9(style='border-right: 1px solid #eee')
+                .sql-editor(ignite-ace='{onLoad: aceInit(paragraph), theme: "chrome", mode: "sql", require: ["ace/ext/language_tools"],' +
+                'advanced: {enableSnippets: false, enableBasicAutocompletion: true, enableLiveAutocompletion: true}}'
+                ng-model='paragraph.query')
+            .col-xs-4.col-sm-3
+                div(ng-show='caches.length > 0' style='padding: 5px 10px' st-table='displayedCaches' st-safe-src='caches')
+                    lable.labelField.labelFormField Caches:
+                    i.fa.fa-database.tipField(title='Click to show cache types metadata dialog' bs-popover data-template-url='{{ $ctrl.cacheMetadataTemplateUrl }}' data-placement='bottom' data-trigger='click' data-container='#{{ paragraph.id }}')
+                    .input-tip
+                        input.form-control(type='text' st-search='label' placeholder='Filter caches...')
+                    table.links
+                        tbody.scrollable-y(style='max-height: 15em; display: block;')
+                            tr(ng-repeat='cache in displayedCaches track by cache.name')
+                                td(style='width: 100%')
+                                    input.labelField(id='cache_{{ [paragraph.id, $index].join("_") }}' type='radio' value='{{cache.name}}' ng-model='paragraph.cacheName')
+                                    label(for='cache_{{ [paragraph.id, $index].join("_") }} ' ng-bind-html='cache.label')
+                    .settings-row
+                        .row(ng-if='ddlAvailable(paragraph)')
+                            label.tipLabel.use-cache(bs-tooltip data-placement='bottom'
+                                data-title=
+                                    'Use selected cache as default schema name.<br/>\
+                                    This will allow to execute query on specified cache without specify schema name.<br/>\
+                                    <b>NOTE:</b> In future version of Ignite this feature will be removed.'
+                                data-trigger='hover')
+                                input(type='checkbox' ng-model='paragraph.useAsDefaultSchema')
+                                span Use selected cache as default schema name
+                .empty-caches(ng-show='displayedCaches.length == 0 && caches.length != 0')
+                    label Wrong caches filter
+                .empty-caches(ng-show='caches.length == 0')
+                    label No caches
+        .col-sm-12.sql-controls
+            +query-actions
+
+            .pull-right
+                +query-settings
+        .col-sm-12.sql-result(ng-if='paragraph.queryExecuted()' ng-switch='paragraph.resultType()')
+            .error(ng-switch-when='error')
+                label Error: {{paragraph.error.message}}
+                br
+                a(ng-show='paragraph.resultType() === "error"' ng-click='showStackTrace(paragraph)') Show more
+            .empty(ng-switch-when='empty') Result set is empty. Duration: #[b {{paragraph.duration | duration}}]
+            .table(ng-switch-when='table')
+                +table-result-heading-query
+                +table-result-body
+            .chart(ng-switch-when='chart')
+                +chart-result
+            .footer.clearfix(ng-show='paragraph.resultType() !== "error"')
+                a.pull-left(ng-click='showResultQuery(paragraph)') Show query
+
+                -var nextVisibleCondition = 'paragraph.resultType() !== "error" && paragraph.queryId && paragraph.nonRefresh() && (paragraph.table() || paragraph.chart() && !paragraph.scanExplain())'
+
+                .pull-right(ng-show=`${nextVisibleCondition}` ng-class='{disabled: paragraph.loading}' ng-click='!paragraph.loading && nextPage(paragraph)')
+                    i.fa.fa-chevron-circle-right
+                    a Next
+
+.row
+    .docs-content
+        header
+            h1 Queries
+            cluster-selector
+
+        .row(ng-if='notebook' bs-affix style='margin-bottom: 20px;')
+            +notebook-rename
+
+        ignite-information(data-title='With query notebook you can' style='margin-bottom: 30px')
+            ul
+                li Create any number of queries
+                li Execute and explain SQL queries
+                li Execute scan queries
+                li View data in tabular form and as charts
+
+        div(ng-if='notebookLoadFailed' style='text-align: center')
+            +notebook-error
+
+        div(ng-if='notebook' ignite-loading='sqlLoading' ignite-loading-text='{{ loadingText }}' ignite-loading-position='top')
+            .docs-body.paragraphs
+                .panel-group(bs-collapse ng-model='notebook.expandedParagraphs' data-allow-multiple='true' data-start-collapsed='false')
+
+                    .panel-paragraph(ng-repeat='paragraph in notebook.paragraphs' id='{{paragraph.id}}' ng-form='form_{{paragraph.id}}')
+                        .panel.panel-default(ng-if='paragraph.qryType === "scan"')
+                            +paragraph-scan
+                        .panel.panel-default(ng-if='paragraph.qryType === "query"')
+                            +paragraph-query

http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
index 752b4f0..7668132 100644
--- a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
+++ b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
@@ -38,7 +38,18 @@ class ConnectionState {
         this.state = State.DISCONNECTED;
     }
 
+    updateCluster(cluster) {
+        this.cluster = cluster;
+        this.cluster.connected = !!_.find(this.clusters, {id: this.cluster.id});
+
+        return cluster;
+    }
+
     update(demo, count, clusters) {
+        _.forEach(clusters, (cluster) => {
+            cluster.name = cluster.id;
+        });
+
         this.clusters = clusters;
 
         if (_.isNil(this.cluster))
@@ -142,6 +153,7 @@ export default class IgniteAgentManager {
         };
 
         self.socket.on('connect_error', onDisconnect);
+
         self.socket.on('disconnect', onDisconnect);
 
         self.socket.on('agents:stat', ({clusters, count}) => {
@@ -152,6 +164,8 @@ export default class IgniteAgentManager {
             self.connectionSbj.next(conn);
         });
 
+        self.socket.on('cluster:changed', (cluster) => this.updateCluster(cluster));
+
         self.socket.on('user:notifications', (notification) => this.UserNotifications.notification = notification);
     }
 
@@ -163,6 +177,31 @@ export default class IgniteAgentManager {
         }
     }
 
+    updateCluster(newCluster) {
+        const state = this.connectionSbj.getValue();
+
+        const oldCluster = _.find(state.clusters, (cluster) => cluster.id === newCluster.id);
+
+        if (!_.isNil(oldCluster)) {
+            oldCluster.nids = newCluster.nids;
+            oldCluster.addresses = newCluster.addresses;
+            oldCluster.clusterVersion = newCluster.clusterVersion;
+            oldCluster.active = newCluster.active;
+
+            this.connectionSbj.next(state);
+        }
+    }
+
+    switchCluster(cluster) {
+        const state = this.connectionSbj.getValue();
+
+        state.updateCluster(cluster);
+
+        this.connectionSbj.next(state);
+
+        this.saveToStorage(cluster);
+    }
+
     /**
      * @param states
      * @returns {Promise}
@@ -212,6 +251,8 @@ export default class IgniteAgentManager {
 
         self.connectionSbj.next(conn);
 
+        this.modalSubscription && this.modalSubscription.unsubscribe();
+
         self.modalSubscription = this.connectionSbj.subscribe({
             next: ({state}) => {
                 switch (state) {
@@ -252,6 +293,8 @@ export default class IgniteAgentManager {
 
         self.connectionSbj.next(conn);
 
+        this.modalSubscription && this.modalSubscription.unsubscribe();
+
         self.modalSubscription = this.connectionSbj.subscribe({
             next: ({state}) => {
                 switch (state) {
@@ -633,7 +676,6 @@ export default class IgniteAgentManager {
     }
 
     /**
-     /**
      * @param {String} nid Node id.
      * @param {String} cacheName Cache name.
      * @param {String} filter Filter text.
@@ -664,4 +706,17 @@ export default class IgniteAgentManager {
         return this.queryScan(nid, cacheName, filter, regEx, caseSensitive, near, local, pageSz)
             .then(fetchResult);
     }
+
+    /**
+     * Change cluster active state.
+     *
+     * @returns {Promise}
+     */
+    toggleClusterState() {
+        const state = this.connectionSbj.getValue();
+        const active = !state.cluster.active;
+
+        return this.visorTask('toggleClusterState', null, active)
+            .then(() => state.updateCluster(Object.assign(state.cluster, { active })));
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/modules/sql/Notebook.data.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/Notebook.data.js b/modules/web-console/frontend/app/modules/sql/Notebook.data.js
deleted file mode 100644
index 3f98bed..0000000
--- a/modules/web-console/frontend/app/modules/sql/Notebook.data.js
+++ /dev/null
@@ -1,168 +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.
- */
-
-const DEMO_NOTEBOOK = {
-    name: 'SQL demo',
-    paragraphs: [
-        {
-            name: 'Query with refresh rate',
-            cacheName: 'CarCache',
-            pageSize: 100,
-            limit: 0,
-            query: [
-                'SELECT count(*)',
-                'FROM "CarCache".Car'
-            ].join('\n'),
-            result: 'bar',
-            timeLineSpan: '1',
-            rate: {
-                value: 3,
-                unit: 1000,
-                installed: true
-            }
-        },
-        {
-            name: 'Simple query',
-            cacheName: 'CarCache',
-            pageSize: 100,
-            limit: 0,
-            query: 'SELECT * FROM "CarCache".Car',
-            result: 'table',
-            timeLineSpan: '1',
-            rate: {
-                value: 30,
-                unit: 1000,
-                installed: false
-            }
-        },
-        {
-            name: 'Query with aggregates',
-            cacheName: 'ParkingCache',
-            pageSize: 100,
-            limit: 0,
-            query: [
-                'SELECT p.name, count(*) AS cnt',
-                'FROM "ParkingCache".Parking p',
-                'INNER JOIN "CarCache".Car c',
-                '  ON (p.id) = (c.parkingId)',
-                'GROUP BY P.NAME'
-            ].join('\n'),
-            result: 'table',
-            timeLineSpan: '1',
-            rate: {
-                value: 30,
-                unit: 1000,
-                installed: false
-            }
-        }
-    ],
-    expandedParagraphs: [0, 1, 2]
-};
-
-export default class NotebookData {
-    static $inject = ['$rootScope', '$http', '$q'];
-
-    constructor($root, $http, $q) {
-        this.demo = $root.IgniteDemoMode;
-
-        this.initLatch = null;
-        this.notebooks = null;
-
-        this.$http = $http;
-        this.$q = $q;
-    }
-
-    load() {
-        if (this.demo) {
-            if (this.initLatch)
-                return this.initLatch;
-
-            return this.initLatch = this.$q.when(this.notebooks = [DEMO_NOTEBOOK]);
-        }
-
-        return this.initLatch = this.$http.get('/api/v1/notebooks')
-            .then(({data}) => this.notebooks = data)
-            .catch(({data}) => Promise.reject(data));
-    }
-
-    read() {
-        if (this.initLatch)
-            return this.initLatch;
-
-        return this.load();
-    }
-
-    find(_id) {
-        return this.read()
-            .then(() => {
-                const notebook = this.demo ? this.notebooks[0] : _.find(this.notebooks, {_id});
-
-                if (_.isNil(notebook))
-                    return this.$q.reject('Failed to load notebook.');
-
-                return notebook;
-            });
-    }
-
-    findIndex(notebook) {
-        return this.read()
-            .then(() => _.findIndex(this.notebooks, {_id: notebook._id}));
-    }
-
-    save(notebook) {
-        if (this.demo)
-            return this.$q.when(DEMO_NOTEBOOK);
-
-        return this.$http.post('/api/v1/notebooks/save', notebook)
-            .then(({data}) => {
-                const idx = _.findIndex(this.notebooks, {_id: data._id});
-
-                if (idx >= 0)
-                    this.notebooks[idx] = data;
-                else
-                    this.notebooks.push(data);
-
-                return data;
-            })
-            .catch(({data}) => Promise.reject(data));
-    }
-
-    remove(notebook) {
-        if (this.demo)
-            return this.$q.reject(`Removing "${notebook.name}" notebook is not supported.`);
-
-        const key = {_id: notebook._id};
-
-        return this.$http.post('/api/v1/notebooks/remove', key)
-            .then(() => {
-                const idx = _.findIndex(this.notebooks, key);
-
-                if (idx >= 0) {
-                    this.notebooks.splice(idx, 1);
-
-                    if (idx < this.notebooks.length)
-                        return this.notebooks[idx];
-                }
-
-                if (this.notebooks.length > 0)
-                    return this.notebooks[this.notebooks.length - 1];
-
-                return null;
-            })
-            .catch(({data}) => Promise.reject(data));
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/modules/sql/Notebook.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/Notebook.service.js b/modules/web-console/frontend/app/modules/sql/Notebook.service.js
deleted file mode 100644
index b0bb64f..0000000
--- a/modules/web-console/frontend/app/modules/sql/Notebook.service.js
+++ /dev/null
@@ -1,74 +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 class Notebook {
-    static $inject = ['$state', 'IgniteConfirm', 'IgniteMessages', 'IgniteNotebookData'];
-
-    /**
-     * @param $state
-     * @param confirmModal
-     * @param Messages
-     * @param {NotebookData} NotebookData
-     */
-    constructor($state, confirmModal, Messages, NotebookData) {
-        this.$state = $state;
-        this.confirmModal = confirmModal;
-        this.Messages = Messages;
-        this.NotebookData = NotebookData;
-    }
-
-    read() {
-        return this.NotebookData.read();
-    }
-
-    create(name) {
-        return this.NotebookData.save({name});
-    }
-
-    save(notebook) {
-        return this.NotebookData.save(notebook);
-    }
-
-    find(_id) {
-        return this.NotebookData.find(_id);
-    }
-
-    _openNotebook(idx) {
-        return this.NotebookData.read()
-            .then((notebooks) => {
-                const nextNotebook = notebooks.length > idx ? notebooks[idx] : _.last(notebooks);
-
-                if (nextNotebook)
-                    this.$state.go('base.sql.notebook', {noteId: nextNotebook._id});
-                else
-                    this.$state.go('base.configuration.tabs.advanced.clusters');
-            });
-    }
-
-    remove(notebook) {
-        return this.confirmModal.confirm(`Are you sure you want to remove notebook: "${notebook.name}"?`)
-            .then(() => this.NotebookData.findIndex(notebook))
-            .then((idx) => {
-                this.NotebookData.remove(notebook)
-                    .then(() => {
-                        if (this.$state.includes('base.sql.notebook') && this.$state.params.noteId === notebook._id)
-                            return this._openNotebook(idx);
-                    })
-                    .catch(this.Messages.showError);
-            });
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/modules/sql/notebook.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/notebook.controller.js b/modules/web-console/frontend/app/modules/sql/notebook.controller.js
deleted file mode 100644
index 68d318a..0000000
--- a/modules/web-console/frontend/app/modules/sql/notebook.controller.js
+++ /dev/null
@@ -1,62 +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.
- */
-
-import templateUrl from 'views/sql/notebook-new.tpl.pug';
-
-// Controller that load notebooks in navigation bar .
-export default ['$scope', '$modal', '$state', 'IgniteMessages', 'IgniteNotebook',
-    (scope, $modal, $state, Messages, Notebook) => {
-        // Pre-fetch modal dialogs.
-        const nameModal = $modal({scope, templateUrl, show: false});
-
-        scope.create = (name) => {
-            return Notebook.create(name)
-                .then((notebook) => {
-                    nameModal.hide();
-
-                    $state.go('base.sql.notebook', {noteId: notebook._id});
-                })
-                .catch(Messages.showError);
-        };
-
-        scope.createNotebook = () => nameModal.$promise.then(nameModal.show);
-
-        Notebook.read()
-            .then((notebooks) => {
-                scope.$watchCollection(() => notebooks, (changed) => {
-                    if (_.isEmpty(changed))
-                        return scope.notebooks = [];
-
-                    scope.notebooks = [
-                        {text: 'Create new notebook', click: scope.createNotebook},
-                        {divider: true}
-                    ];
-
-                    _.forEach(changed, (notebook) => scope.notebooks.push({
-                        data: notebook,
-                        action: {
-                            icon: 'fa-trash',
-                            click: (item) => Notebook.remove(item)
-                        },
-                        text: notebook.name,
-                        sref: `base.sql.notebook({noteId:"${notebook._id}"})`
-                    }));
-                });
-            })
-            .catch(Messages.showError);
-    }
-];