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:39 UTC
[1/5] ignite git commit: IGNITE-6390 Web Console: Added component for
cluster selection.
Repository: ignite
Updated Branches:
refs/heads/master cbd69d6b3 -> 1367bc98e
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/views/sql/sql.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/sql/sql.tpl.pug b/modules/web-console/frontend/views/sql/sql.tpl.pug
deleted file mode 100644
index 98b4d68..0000000
--- a/modules/web-console/frontend/views/sql/sql.tpl.pug
+++ /dev/null
@@ -1,381 +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.
-
-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') with filter: #[b {{ paragraph.queryArgs.filter }}]
- span(ng-if='paragraph.queryArgs.localNid') 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
- .row(ng-if='notebook' bs-affix style='margin-bottom: 20px;')
- +notebook-rename
-
- ignite-information(data-title='With query notebook you can' style='margin-top: 0; 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/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
----------------------------------------------------------------------
diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
index 8eed3dd..86b9ea5 100644
--- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
+++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
@@ -22,44 +22,51 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
import java.net.ConnectException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import org.apache.ignite.IgniteLogger;
import org.apache.ignite.console.agent.rest.RestExecutor;
import org.apache.ignite.console.agent.rest.RestResult;
import org.apache.ignite.internal.processors.rest.client.message.GridClientNodeBean;
import org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyObjectMapper;
import org.apache.ignite.internal.util.typedef.F;
-import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.internal.util.typedef.internal.LT;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteClosure;
import org.apache.ignite.lang.IgniteProductVersion;
-import org.slf4j.Logger;
+import org.apache.ignite.logger.slf4j.Slf4jLogger;
import org.slf4j.LoggerFactory;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_CLUSTER_NAME;
import static org.apache.ignite.console.agent.AgentUtils.toJSON;
import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_BUILD_VER;
+import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_CLIENT_MODE;
+import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_IPS;
import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_SUCCESS;
+import static org.apache.ignite.internal.visor.util.VisorTaskUtils.sortAddresses;
+import static org.apache.ignite.internal.visor.util.VisorTaskUtils.splitAddresses;
/**
* API to transfer topology from Ignite cluster available by node-uri.
*/
public class ClusterListener {
/** */
- private static final Logger log = LoggerFactory.getLogger(ClusterListener.class);
+ private static final IgniteLogger log = new Slf4jLogger(LoggerFactory.getLogger(ClusterListener.class));
/** */
private static final String EVENT_CLUSTER_CONNECTED = "cluster:connected";
/** */
private static final String EVENT_CLUSTER_TOPOLOGY = "cluster:topology";
-
+
/** */
private static final String EVENT_CLUSTER_DISCONNECTED = "cluster:disconnected";
@@ -79,17 +86,6 @@ public class ClusterListener {
private final BroadcastTask broadcastTask = new BroadcastTask();
/** */
- private static final IgniteClosure<GridClientNodeBean, UUID> NODE2ID = new IgniteClosure<GridClientNodeBean, UUID>() {
- @Override public UUID apply(GridClientNodeBean n) {
- return n.getNodeId();
- }
-
- @Override public String toString() {
- return "Node bean to node ID transformer closure.";
- }
- };
-
- /** */
private static final IgniteClosure<UUID, String> ID2ID8 = new IgniteClosure<UUID, String>() {
@Override public String apply(UUID nid) {
return U.id8(nid).toUpperCase();
@@ -127,7 +123,7 @@ public class ClusterListener {
* @param nids Cluster nodes IDs.
*/
private void clusterConnect(Collection<UUID> nids) {
- log.info("Connection successfully established to cluster with nodes: {}", F.viewReadOnly(nids, ID2ID8));
+ log.info("Connection successfully established to cluster with nodes: " + F.viewReadOnly(nids, ID2ID8));
client.emit(EVENT_CLUSTER_CONNECTED, toJSON(nids));
}
@@ -171,7 +167,7 @@ public class ClusterListener {
@Override public void call(Object... args) {
safeStopRefresh();
- final long timeout = args.length > 1 && args[1] instanceof Long ? (long)args[1] : DFLT_TIMEOUT;
+ final long timeout = args.length > 1 && args[1] instanceof Long ? (long)args[1] : DFLT_TIMEOUT;
refreshTask = pool.scheduleWithFixedDelay(broadcastTask, 0L, timeout, TimeUnit.MILLISECONDS);
}
@@ -194,41 +190,107 @@ public class ClusterListener {
/** */
private static class TopologySnapshot {
/** */
+ private String clusterName;
+
+ /** */
private Collection<UUID> nids;
/** */
- private String clusterVer;
+ private Map<UUID, String> addrs;
+
+ /** */
+ private Map<UUID, Boolean> clients;
+
+ /** */
+ private String clusterVerStr;
+
+ /** */
+ private IgniteProductVersion clusterVer;
+
+ /** */
+ private boolean active;
+
+ /**
+ * Helper method to get attribute.
+ *
+ * @param attrs Map with attributes.
+ * @param name Attribute name.
+ * @return Attribute value.
+ */
+ private static <T> T attribute(Map<String, Object> attrs, String name) {
+ return (T)attrs.get(name);
+ }
/**
* @param nodes Nodes.
*/
TopologySnapshot(Collection<GridClientNodeBean> nodes) {
- nids = F.viewReadOnly(nodes, NODE2ID);
+ int sz = nodes.size();
+
+ nids = new ArrayList<>(sz);
+ addrs = U.newHashMap(sz);
+ clients = U.newHashMap(sz);
+ active = false;
+
+ for (GridClientNodeBean node : nodes) {
+ UUID nid = node.getNodeId();
+
+ nids.add(nid);
- Collection<T2<String, IgniteProductVersion>> vers = F.transform(nodes,
- new IgniteClosure<GridClientNodeBean, T2<String, IgniteProductVersion>>() {
- @Override public T2<String, IgniteProductVersion> apply(GridClientNodeBean bean) {
- String ver = (String)bean.getAttributes().get(ATTR_BUILD_VER);
+ Map<String, Object> attrs = node.getAttributes();
- return new T2<>(ver, IgniteProductVersion.fromString(ver));
- }
- });
+ if (F.isEmpty(clusterName))
+ clusterName = attribute(attrs, IGNITE_CLUSTER_NAME);
- T2<String, IgniteProductVersion> min = Collections.min(vers, new Comparator<T2<String, IgniteProductVersion>>() {
- @SuppressWarnings("ConstantConditions")
- @Override public int compare(T2<String, IgniteProductVersion> o1, T2<String, IgniteProductVersion> o2) {
- return o1.get2().compareTo(o2.get2());
+ Boolean client = attribute(attrs, ATTR_CLIENT_MODE);
+
+ clients.put(nid, client);
+
+ Collection<String> nodeAddrs = client
+ ? splitAddresses((String)attribute(attrs, ATTR_IPS))
+ : node.getTcpAddresses();
+
+ String firstIP = F.first(sortAddresses(nodeAddrs));
+
+ addrs.put(nid, firstIP);
+
+ String nodeVerStr = attribute(attrs, ATTR_BUILD_VER);
+
+ IgniteProductVersion nodeVer = IgniteProductVersion.fromString(nodeVerStr);
+
+ if (clusterVer == null || clusterVer.compareTo(nodeVer) > 0) {
+ clusterVer = nodeVer;
+ clusterVerStr = nodeVerStr;
}
- });
+ }
+ }
- clusterVer = min.get1();
+ /**
+ * @return Cluster name.
+ */
+ public String getClusterName() {
+ return clusterName;
}
/**
* @return Cluster version.
*/
public String getClusterVersion() {
- return clusterVer;
+ return clusterVerStr;
+ }
+
+ /**
+ * @return Cluster active flag.
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * @param active New cluster active state.
+ */
+ public void setActive(boolean active) {
+ this.active = active;
}
/**
@@ -238,14 +300,40 @@ public class ClusterListener {
return nids;
}
- /** */
+ /**
+ * @return Cluster nodes with IPs.
+ */
+ public Map<UUID, String> getAddresses() {
+ return addrs;
+ }
+
+ /**
+ * @return Cluster nodes with client mode flag.
+ */
+ public Map<UUID, Boolean> getClients() {
+ return clients;
+ }
+
+ /**
+ * @return Cluster version.
+ */
+ public IgniteProductVersion clusterVersion() {
+ return clusterVer;
+ }
+
+ /**
+ * @return Collection of short UUIDs.
+ */
Collection<String> nid8() {
return F.viewReadOnly(nids, ID2ID8);
}
- /** */
- boolean differentCluster(TopologySnapshot old) {
- return old == null || F.isEmpty(old.nids) || Collections.disjoint(nids, old.nids);
+ /**
+ * @param prev Previous topology.
+ * @return {@code true} in case if current topology is a new cluster.
+ */
+ boolean differentCluster(TopologySnapshot prev) {
+ return prev == null || F.isEmpty(prev.nids) || Collections.disjoint(nids, prev.nids);
}
}
@@ -264,7 +352,11 @@ public class ClusterListener {
TopologySnapshot newTop = new TopologySnapshot(nodes);
if (newTop.differentCluster(top))
- log.info("Connection successfully established to cluster with nodes: {}", newTop.nid8());
+ log.info("Connection successfully established to cluster with nodes: " + newTop.nid8());
+
+ boolean active = restExecutor.active(newTop.clusterVersion(), F.first(newTop.getNids()));
+
+ newTop.setActive(active);
top = newTop;
@@ -273,7 +365,7 @@ public class ClusterListener {
break;
default:
- log.warn(res.getError());
+ LT.warn(log, res.getError());
clusterDisconnect();
}
@@ -288,7 +380,7 @@ public class ClusterListener {
}
}
}
-
+
/** */
private class BroadcastTask implements Runnable {
/** {@inheritDoc} */
@@ -306,7 +398,7 @@ public class ClusterListener {
if (top.differentCluster(newTop)) {
clusterDisconnect();
- log.info("Connection successfully established to cluster with nodes: {}", newTop.nid8());
+ log.info("Connection successfully established to cluster with nodes: " + newTop.nid8());
watch();
}
@@ -318,7 +410,7 @@ public class ClusterListener {
break;
default:
- log.warn(res.getError());
+ LT.warn(log, res.getError());
clusterDisconnect();
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
----------------------------------------------------------------------
diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
index 36f3885..7fbe6f9 100644
--- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
+++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
@@ -30,6 +30,7 @@ import java.io.StringWriter;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import okhttp3.Dispatcher;
import okhttp3.FormBody;
@@ -44,6 +45,7 @@ import org.apache.ignite.console.demo.AgentClusterDemo;
import org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyObjectMapper;
import org.apache.ignite.internal.util.typedef.internal.LT;
import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteProductVersion;
import org.apache.ignite.logger.slf4j.Slf4jLogger;
import org.slf4j.LoggerFactory;
@@ -59,6 +61,18 @@ import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS
*/
public class RestExecutor {
/** */
+ private static final IgniteProductVersion IGNITE_2_1 = IgniteProductVersion.fromString("2.1.0");
+
+ /** */
+ private static final IgniteProductVersion IGNITE_2_3 = IgniteProductVersion.fromString("2.3.0");
+
+ /** Unique Visor key to get events last order. */
+ private static final String EVT_LAST_ORDER_KEY = "WEB_AGENT_" + UUID.randomUUID().toString();
+
+ /** Unique Visor key to get events throttle counter. */
+ private static final String EVT_THROTTLE_CNTR_KEY = "WEB_AGENT_" + UUID.randomUUID().toString();
+
+ /** */
private static final IgniteLogger log = new Slf4jLogger(LoggerFactory.getLogger(RestExecutor.class));
/** JSON object mapper. */
@@ -208,7 +222,9 @@ public class RestExecutor {
}
/**
- * @param demo Is demo node request.
+ * @param demo {@code true} in case of demo mode.
+ * @param full Flag indicating whether to collect metrics or not.
+ * @throws IOException If failed to collect topology.
*/
public RestResult topology(boolean demo, boolean full) throws IOException {
Map<String, Object> params = new HashMap<>(3);
@@ -221,6 +237,51 @@ public class RestExecutor {
}
/**
+ * @param ver Cluster version.
+ * @param nid Node ID.
+ * @return Cluster active state.
+ * @throws IOException If failed to collect cluster active state.
+ */
+ public boolean active(IgniteProductVersion ver, UUID nid) throws IOException {
+ Map<String, Object> params = new HashMap<>();
+
+ boolean v23 = ver.compareTo(IGNITE_2_3) >= 0;
+
+ if (v23)
+ params.put("cmd", "currentState");
+ else {
+ params.put("cmd", "exe");
+ params.put("name", "org.apache.ignite.internal.visor.compute.VisorGatewayTask");
+ params.put("p1", nid);
+ params.put("p2", "org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTask");
+ params.put("p3", "org.apache.ignite.internal.visor.node.VisorNodeDataCollectorTaskArg");
+ params.put("p4", false);
+ params.put("p5", EVT_LAST_ORDER_KEY);
+ params.put("p6", EVT_THROTTLE_CNTR_KEY);
+
+ if (ver.compareTo(IGNITE_2_1) >= 0)
+ params.put("p7", false);
+ else {
+ params.put("p7", 10);
+ params.put("p8", false);
+ }
+ }
+
+ RestResult res = sendRequest(false, "ignite", params, null, null);
+
+ switch (res.getStatus()) {
+ case STATUS_SUCCESS:
+ if (v23)
+ return Boolean.valueOf(res.getData());
+
+ return res.getData().contains("\"active\":true");
+
+ default:
+ throw new IOException(res.getError());
+ }
+ }
+
+ /**
* REST response holder Java bean.
*/
private static class RestResponseHolder {
[4/5] ignite git commit: IGNITE-6390 Web Console: Added component for
cluster selection.
Posted by ak...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/page-queries/controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-queries/controller.js b/modules/web-console/frontend/app/components/page-queries/controller.js
new file mode 100644
index 0000000..dba0269
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-queries/controller.js
@@ -0,0 +1,1938 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 'rxjs/add/operator/mergeMap';
+import 'rxjs/add/operator/merge';
+import 'rxjs/add/operator/switchMap';
+import 'rxjs/add/operator/exhaustMap';
+import 'rxjs/add/operator/distinctUntilChanged';
+
+import { fromPromise } from 'rxjs/observable/fromPromise';
+import { timer } from 'rxjs/observable/timer';
+import { defer } from 'rxjs/observable/defer';
+
+import paragraphRateTemplateUrl from 'views/sql/paragraph-rate.tpl.pug';
+import cacheMetadataTemplateUrl from 'views/sql/cache-metadata.tpl.pug';
+import chartSettingsTemplateUrl from 'views/sql/chart-settings.tpl.pug';
+import messageTemplateUrl from 'views/templates/message.tpl.pug';
+
+// Time line X axis descriptor.
+const TIME_LINE = {value: -1, type: 'java.sql.Date', label: 'TIME_LINE'};
+
+// Row index X axis descriptor.
+const ROW_IDX = {value: -2, type: 'java.lang.Integer', label: 'ROW_IDX'};
+
+const NON_COLLOCATED_JOINS_SINCE = '1.7.0';
+
+const ENFORCE_JOIN_SINCE = [['1.7.9', '1.8.0'], ['1.8.4', '1.9.0'], '1.9.1'];
+
+const LAZY_QUERY_SINCE = [['2.1.4-p1', '2.2.0'], '2.2.1'];
+
+const DDL_SINCE = [['2.1.6', '2.2.0'], '2.3.0'];
+
+const _fullColName = (col) => {
+ const res = [];
+
+ if (col.schemaName)
+ res.push(col.schemaName);
+
+ if (col.typeName)
+ res.push(col.typeName);
+
+ res.push(col.fieldName);
+
+ return res.join('.');
+};
+
+let paragraphId = 0;
+
+class Paragraph {
+ constructor($animate, $timeout, JavaTypes, paragraph) {
+ const self = this;
+
+ self.id = 'paragraph-' + paragraphId++;
+ self.qryType = paragraph.qryType || 'query';
+ self.maxPages = 0;
+ self.filter = '';
+ self.useAsDefaultSchema = false;
+ self.localQueryMode = false;
+ self.csvIsPreparing = false;
+ self.scanningInProgress = false;
+
+ _.assign(this, paragraph);
+
+ Object.defineProperty(this, 'gridOptions', {value: {
+ enableGridMenu: false,
+ enableColumnMenus: false,
+ flatEntityAccess: true,
+ fastWatch: true,
+ categories: [],
+ rebuildColumns() {
+ if (_.isNil(this.api))
+ return;
+
+ this.categories.length = 0;
+
+ this.columnDefs = _.reduce(self.meta, (cols, col, idx) => {
+ cols.push({
+ displayName: col.fieldName,
+ headerTooltip: _fullColName(col),
+ field: idx.toString(),
+ minWidth: 50,
+ cellClass: 'cell-left',
+ visible: self.columnFilter(col)
+ });
+
+ this.categories.push({
+ name: col.fieldName,
+ visible: self.columnFilter(col),
+ enableHiding: true
+ });
+
+ return cols;
+ }, []);
+
+ $timeout(() => this.api.core.notifyDataChange('column'));
+ },
+ adjustHeight() {
+ if (_.isNil(this.api))
+ return;
+
+ this.data = self.rows;
+
+ const height = Math.min(self.rows.length, 15) * 30 + 47;
+
+ // Remove header height.
+ this.api.grid.element.css('height', height + 'px');
+
+ $timeout(() => this.api.core.handleWindowResize());
+ },
+ onRegisterApi(api) {
+ $animate.enabled(api.grid.element, false);
+
+ this.api = api;
+
+ this.rebuildColumns();
+
+ this.adjustHeight();
+ }
+ }});
+
+ Object.defineProperty(this, 'chartHistory', {value: []});
+
+ Object.defineProperty(this, 'error', {value: {
+ root: {},
+ message: ''
+ }});
+
+ this.setError = (err) => {
+ this.error.root = err;
+ this.error.message = err.message;
+
+ let cause = err;
+
+ while (_.nonNil(cause)) {
+ if (_.nonEmpty(cause.className) &&
+ _.includes(['SQLException', 'JdbcSQLException', 'QueryCancelledException'], JavaTypes.shortClassName(cause.className))) {
+ this.error.message = cause.message || cause.className;
+
+ break;
+ }
+
+ cause = cause.cause;
+ }
+
+ if (_.isEmpty(this.error.message) && _.nonEmpty(err.className)) {
+ this.error.message = 'Internal cluster error';
+
+ if (_.nonEmpty(err.className))
+ this.error.message += ': ' + err.className;
+ }
+ };
+ }
+
+ resultType() {
+ if (_.isNil(this.queryArgs))
+ return null;
+
+ if (_.nonEmpty(this.error.message))
+ return 'error';
+
+ if (_.isEmpty(this.rows))
+ return 'empty';
+
+ return this.result === 'table' ? 'table' : 'chart';
+ }
+
+ nonRefresh() {
+ return _.isNil(this.rate) || _.isNil(this.rate.stopTime);
+ }
+
+ table() {
+ return this.result === 'table';
+ }
+
+ chart() {
+ return this.result !== 'table' && this.result !== 'none';
+ }
+
+ nonEmpty() {
+ return this.rows && this.rows.length > 0;
+ }
+
+ queryExecuted() {
+ return _.nonEmpty(this.meta) || _.nonEmpty(this.error.message);
+ }
+
+ scanExplain() {
+ return this.queryExecuted() && this.queryArgs.type !== 'QUERY';
+ }
+
+ timeLineSupported() {
+ return this.result !== 'pie';
+ }
+
+ chartColumnsConfigured() {
+ return _.nonEmpty(this.chartKeyCols) && _.nonEmpty(this.chartValCols);
+ }
+
+ chartTimeLineEnabled() {
+ return _.nonEmpty(this.chartKeyCols) && _.eq(this.chartKeyCols[0], TIME_LINE);
+ }
+
+ executionInProgress(showLocal = false) {
+ return this.loading && (this.localQueryMode === showLocal);
+ }
+
+ checkScanInProgress(showLocal = false) {
+ return this.scanningInProgress && (this.localQueryMode === showLocal);
+ }
+
+ cancelRefresh($interval) {
+ if (this.rate && this.rate.stopTime) {
+ $interval.cancel(this.rate.stopTime);
+
+ delete this.rate.stopTime;
+ }
+ }
+
+ reset($interval) {
+ this.meta = [];
+ this.chartColumns = [];
+ this.chartKeyCols = [];
+ this.chartValCols = [];
+ this.error.root = {};
+ this.error.message = '';
+ this.rows = [];
+ this.duration = 0;
+
+ this.cancelRefresh($interval);
+ }
+}
+
+// Controller for SQL notebook screen.
+export default class {
+ static $inject = ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$filter', '$modal', '$popover', 'IgniteLoading', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteConfirm', 'AgentManager', 'IgniteChartColors', 'IgniteNotebook', 'IgniteNodes', 'uiGridExporterConstants', 'IgniteVersion', 'IgniteActivitiesData', 'JavaTypes', 'IgniteCopyToClipboard'];
+
+ constructor($root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $filter, $modal, $popover, Loading, LegacyUtils, Messages, Confirm, agentMgr, IgniteChartColors, Notebook, Nodes, uiGridExporterConstants, Version, ActivitiesData, JavaTypes, IgniteCopyToClipboard) {
+ const $ctrl = this;
+
+ Object.assign(this, { $root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $filter, $modal, $popover, Loading, LegacyUtils, Messages, Confirm, agentMgr, IgniteChartColors, Notebook, Nodes, uiGridExporterConstants, Version, ActivitiesData, JavaTypes });
+
+ // Define template urls.
+ $ctrl.paragraphRateTemplateUrl = paragraphRateTemplateUrl;
+ $ctrl.cacheMetadataTemplateUrl = cacheMetadataTemplateUrl;
+ $ctrl.chartSettingsTemplateUrl = chartSettingsTemplateUrl;
+ $ctrl.demoStarted = false;
+
+ const _tryStopRefresh = function(paragraph) {
+ paragraph.cancelRefresh($interval);
+ };
+
+ const _stopTopologyRefresh = () => {
+ if ($scope.notebook && $scope.notebook.paragraphs)
+ $scope.notebook.paragraphs.forEach((paragraph) => _tryStopRefresh(paragraph));
+ };
+
+ $scope.$on('$stateChangeStart', _stopTopologyRefresh);
+
+ $scope.caches = [];
+
+ $scope.pageSizes = [50, 100, 200, 400, 800, 1000];
+ $scope.maxPages = [
+ {label: 'Unlimited', value: 0},
+ {label: '1', value: 1},
+ {label: '5', value: 5},
+ {label: '10', value: 10},
+ {label: '20', value: 20},
+ {label: '50', value: 50},
+ {label: '100', value: 100}
+ ];
+
+ $scope.timeLineSpans = ['1', '5', '10', '15', '30'];
+
+ $scope.aggregateFxs = ['FIRST', 'LAST', 'MIN', 'MAX', 'SUM', 'AVG', 'COUNT'];
+
+ $scope.modes = LegacyUtils.mkOptions(['PARTITIONED', 'REPLICATED', 'LOCAL']);
+
+ $scope.loadingText = $root.IgniteDemoMode ? 'Demo grid is starting. Please wait...' : 'Loading query notebook screen...';
+
+ $scope.timeUnit = [
+ {value: 1000, label: 'seconds', short: 's'},
+ {value: 60000, label: 'minutes', short: 'm'},
+ {value: 3600000, label: 'hours', short: 'h'}
+ ];
+
+ $scope.metadata = [];
+
+ $scope.metaFilter = '';
+
+ $scope.metaOptions = {
+ nodeChildren: 'children',
+ dirSelectable: true,
+ injectClasses: {
+ iExpanded: 'fa fa-minus-square-o',
+ iCollapsed: 'fa fa-plus-square-o'
+ }
+ };
+
+ const maskCacheName = $filter('defaultName');
+
+ // We need max 1800 items to hold history for 30 mins in case of refresh every second.
+ const HISTORY_LENGTH = 1800;
+
+ const MAX_VAL_COLS = IgniteChartColors.length;
+
+ $anchorScroll.yOffset = 55;
+
+ $scope.chartColor = function(index) {
+ return {color: 'white', 'background-color': IgniteChartColors[index]};
+ };
+
+ function _chartNumber(arr, idx, dflt) {
+ if (idx >= 0 && arr && arr.length > idx && _.isNumber(arr[idx]))
+ return arr[idx];
+
+ return dflt;
+ }
+
+ function _min(rows, idx, dflt) {
+ let min = _chartNumber(rows[0], idx, dflt);
+
+ _.forEach(rows, (row) => {
+ const v = _chartNumber(row, idx, dflt);
+
+ if (v < min)
+ min = v;
+ });
+
+ return min;
+ }
+
+ function _max(rows, idx, dflt) {
+ let max = _chartNumber(rows[0], idx, dflt);
+
+ _.forEach(rows, (row) => {
+ const v = _chartNumber(row, idx, dflt);
+
+ if (v > max)
+ max = v;
+ });
+
+ return max;
+ }
+
+ function _sum(rows, idx) {
+ let sum = 0;
+
+ _.forEach(rows, (row) => sum += _chartNumber(row, idx, 0));
+
+ return sum;
+ }
+
+ function _aggregate(rows, aggFx, idx, dflt) {
+ const len = rows.length;
+
+ switch (aggFx) {
+ case 'FIRST':
+ return _chartNumber(rows[0], idx, dflt);
+
+ case 'LAST':
+ return _chartNumber(rows[len - 1], idx, dflt);
+
+ case 'MIN':
+ return _min(rows, idx, dflt);
+
+ case 'MAX':
+ return _max(rows, idx, dflt);
+
+ case 'SUM':
+ return _sum(rows, idx);
+
+ case 'AVG':
+ return len > 0 ? _sum(rows, idx) / len : 0;
+
+ case 'COUNT':
+ return len;
+
+ default:
+ }
+
+ return 0;
+ }
+
+ function _chartLabel(arr, idx, dflt) {
+ if (arr && arr.length > idx && _.isString(arr[idx]))
+ return arr[idx];
+
+ return dflt;
+ }
+
+ function _chartDatum(paragraph) {
+ let datum = [];
+
+ if (paragraph.chartColumnsConfigured()) {
+ paragraph.chartValCols.forEach(function(valCol) {
+ let index = 0;
+ let values = [];
+ const colIdx = valCol.value;
+
+ if (paragraph.chartTimeLineEnabled()) {
+ const aggFx = valCol.aggFx;
+ const colLbl = valCol.label + ' [' + aggFx + ']';
+
+ if (paragraph.charts && paragraph.charts.length === 1)
+ datum = paragraph.charts[0].data;
+
+ const chartData = _.find(datum, {series: valCol.label});
+
+ const leftBound = new Date();
+ leftBound.setMinutes(leftBound.getMinutes() - parseInt(paragraph.timeLineSpan, 10));
+
+ if (chartData) {
+ const lastItem = _.last(paragraph.chartHistory);
+
+ values = chartData.values;
+
+ values.push({
+ x: lastItem.tm,
+ y: _aggregate(lastItem.rows, aggFx, colIdx, index++)
+ });
+
+ while (values.length > 0 && values[0].x < leftBound)
+ values.shift();
+ }
+ else {
+ _.forEach(paragraph.chartHistory, (history) => {
+ if (history.tm >= leftBound) {
+ values.push({
+ x: history.tm,
+ y: _aggregate(history.rows, aggFx, colIdx, index++)
+ });
+ }
+ });
+
+ datum.push({series: valCol.label, key: colLbl, values});
+ }
+ }
+ else {
+ index = paragraph.total;
+
+ values = _.map(paragraph.rows, function(row) {
+ const xCol = paragraph.chartKeyCols[0].value;
+
+ const v = {
+ x: _chartNumber(row, xCol, index),
+ xLbl: _chartLabel(row, xCol, null),
+ y: _chartNumber(row, colIdx, index)
+ };
+
+ index++;
+
+ return v;
+ });
+
+ datum.push({series: valCol.label, key: valCol.label, values});
+ }
+ });
+ }
+
+ return datum;
+ }
+
+ function _xX(d) {
+ return d.x;
+ }
+
+ function _yY(d) {
+ return d.y;
+ }
+
+ function _xAxisTimeFormat(d) {
+ return d3.time.format('%X')(new Date(d));
+ }
+
+ const _intClasses = ['java.lang.Byte', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
+
+ function _intType(cls) {
+ return _.includes(_intClasses, cls);
+ }
+
+ const _xAxisWithLabelFormat = function(paragraph) {
+ return function(d) {
+ const values = paragraph.charts[0].data[0].values;
+
+ const fmt = _intType(paragraph.chartKeyCols[0].type) ? 'd' : ',.2f';
+
+ const dx = values[d];
+
+ if (!dx)
+ return d3.format(fmt)(d);
+
+ const lbl = dx.xLbl;
+
+ return lbl ? lbl : d3.format(fmt)(d);
+ };
+ };
+
+ function _xAxisLabel(paragraph) {
+ return _.isEmpty(paragraph.chartKeyCols) ? 'X' : paragraph.chartKeyCols[0].label;
+ }
+
+ const _yAxisFormat = function(d) {
+ const fmt = d < 1000 ? ',.2f' : '.3s';
+
+ return d3.format(fmt)(d);
+ };
+
+ function _updateCharts(paragraph) {
+ $timeout(() => _.forEach(paragraph.charts, (chart) => chart.api.update()), 100);
+ }
+
+ function _updateChartsWithData(paragraph, newDatum) {
+ $timeout(() => {
+ if (!paragraph.chartTimeLineEnabled()) {
+ const chartDatum = paragraph.charts[0].data;
+
+ chartDatum.length = 0;
+
+ _.forEach(newDatum, (series) => chartDatum.push(series));
+ }
+
+ paragraph.charts[0].api.update();
+ });
+ }
+
+ function _yAxisLabel(paragraph) {
+ const cols = paragraph.chartValCols;
+
+ const tml = paragraph.chartTimeLineEnabled();
+
+ return _.isEmpty(cols) ? 'Y' : _.map(cols, function(col) {
+ let lbl = col.label;
+
+ if (tml)
+ lbl += ' [' + col.aggFx + ']';
+
+ return lbl;
+ }).join(', ');
+ }
+
+ function _barChart(paragraph) {
+ const datum = _chartDatum(paragraph);
+
+ if (_.isEmpty(paragraph.charts)) {
+ const stacked = paragraph.chartsOptions && paragraph.chartsOptions.barChart
+ ? paragraph.chartsOptions.barChart.stacked
+ : true;
+
+ const options = {
+ chart: {
+ type: 'multiBarChart',
+ height: 400,
+ margin: {left: 70},
+ duration: 0,
+ x: _xX,
+ y: _yY,
+ xAxis: {
+ axisLabel: _xAxisLabel(paragraph),
+ tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
+ showMaxMin: false
+ },
+ yAxis: {
+ axisLabel: _yAxisLabel(paragraph),
+ tickFormat: _yAxisFormat
+ },
+ color: IgniteChartColors,
+ stacked,
+ showControls: true,
+ legend: {
+ vers: 'furious',
+ margin: {right: -25}
+ }
+ }
+ };
+
+ paragraph.charts = [{options, data: datum}];
+
+ _updateCharts(paragraph);
+ }
+ else
+ _updateChartsWithData(paragraph, datum);
+ }
+
+ function _pieChartDatum(paragraph) {
+ const datum = [];
+
+ if (paragraph.chartColumnsConfigured() && !paragraph.chartTimeLineEnabled()) {
+ paragraph.chartValCols.forEach(function(valCol) {
+ let index = paragraph.total;
+
+ const values = _.map(paragraph.rows, (row) => {
+ const xCol = paragraph.chartKeyCols[0].value;
+
+ const v = {
+ x: xCol < 0 ? index : row[xCol],
+ y: _chartNumber(row, valCol.value, index)
+ };
+
+ // Workaround for known problem with zero values on Pie chart.
+ if (v.y === 0)
+ v.y = 0.0001;
+
+ index++;
+
+ return v;
+ });
+
+ datum.push({series: paragraph.chartKeyCols[0].label, key: valCol.label, values});
+ });
+ }
+
+ return datum;
+ }
+
+ function _pieChart(paragraph) {
+ let datum = _pieChartDatum(paragraph);
+
+ if (datum.length === 0)
+ datum = [{values: []}];
+
+ paragraph.charts = _.map(datum, function(data) {
+ return {
+ options: {
+ chart: {
+ type: 'pieChart',
+ height: 400,
+ duration: 0,
+ x: _xX,
+ y: _yY,
+ showLabels: true,
+ labelThreshold: 0.05,
+ labelType: 'percent',
+ donut: true,
+ donutRatio: 0.35,
+ legend: {
+ vers: 'furious',
+ margin: {
+ right: -25
+ }
+ }
+ },
+ title: {
+ enable: true,
+ text: data.key
+ }
+ },
+ data: data.values
+ };
+ });
+
+ _updateCharts(paragraph);
+ }
+
+ function _lineChart(paragraph) {
+ const datum = _chartDatum(paragraph);
+
+ if (_.isEmpty(paragraph.charts)) {
+ const options = {
+ chart: {
+ type: 'lineChart',
+ height: 400,
+ margin: { left: 70 },
+ duration: 0,
+ x: _xX,
+ y: _yY,
+ xAxis: {
+ axisLabel: _xAxisLabel(paragraph),
+ tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
+ showMaxMin: false
+ },
+ yAxis: {
+ axisLabel: _yAxisLabel(paragraph),
+ tickFormat: _yAxisFormat
+ },
+ color: IgniteChartColors,
+ useInteractiveGuideline: true,
+ legend: {
+ vers: 'furious',
+ margin: {
+ right: -25
+ }
+ }
+ }
+ };
+
+ paragraph.charts = [{options, data: datum}];
+
+ _updateCharts(paragraph);
+ }
+ else
+ _updateChartsWithData(paragraph, datum);
+ }
+
+ function _areaChart(paragraph) {
+ const datum = _chartDatum(paragraph);
+
+ if (_.isEmpty(paragraph.charts)) {
+ const style = paragraph.chartsOptions && paragraph.chartsOptions.areaChart
+ ? paragraph.chartsOptions.areaChart.style
+ : 'stack';
+
+ const options = {
+ chart: {
+ type: 'stackedAreaChart',
+ height: 400,
+ margin: {left: 70},
+ duration: 0,
+ x: _xX,
+ y: _yY,
+ xAxis: {
+ axisLabel: _xAxisLabel(paragraph),
+ tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
+ showMaxMin: false
+ },
+ yAxis: {
+ axisLabel: _yAxisLabel(paragraph),
+ tickFormat: _yAxisFormat
+ },
+ color: IgniteChartColors,
+ style,
+ legend: {
+ vers: 'furious',
+ margin: {right: -25}
+ }
+ }
+ };
+
+ paragraph.charts = [{options, data: datum}];
+
+ _updateCharts(paragraph);
+ }
+ else
+ _updateChartsWithData(paragraph, datum);
+ }
+
+ function _chartApplySettings(paragraph, resetCharts) {
+ if (resetCharts)
+ paragraph.charts = [];
+
+ if (paragraph.chart() && paragraph.nonEmpty()) {
+ switch (paragraph.result) {
+ case 'bar':
+ _barChart(paragraph);
+ break;
+
+ case 'pie':
+ _pieChart(paragraph);
+ break;
+
+ case 'line':
+ _lineChart(paragraph);
+ break;
+
+ case 'area':
+ _areaChart(paragraph);
+ break;
+
+ default:
+ }
+ }
+ }
+
+ $scope.chartRemoveKeyColumn = function(paragraph, index) {
+ paragraph.chartKeyCols.splice(index, 1);
+
+ _chartApplySettings(paragraph, true);
+ };
+
+ $scope.chartRemoveValColumn = function(paragraph, index) {
+ paragraph.chartValCols.splice(index, 1);
+
+ _chartApplySettings(paragraph, true);
+ };
+
+ $scope.chartAcceptKeyColumn = function(paragraph, item) {
+ const accepted = _.findIndex(paragraph.chartKeyCols, item) < 0;
+
+ if (accepted) {
+ paragraph.chartKeyCols = [item];
+
+ _chartApplySettings(paragraph, true);
+ }
+
+ return false;
+ };
+
+ const _numberClasses = ['java.math.BigDecimal', 'java.lang.Byte', 'java.lang.Double',
+ 'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
+
+ const _numberType = function(cls) {
+ return _.includes(_numberClasses, cls);
+ };
+
+ $scope.chartAcceptValColumn = function(paragraph, item) {
+ const valCols = paragraph.chartValCols;
+
+ const accepted = _.findIndex(valCols, item) < 0 && item.value >= 0 && _numberType(item.type);
+
+ if (accepted) {
+ if (valCols.length === MAX_VAL_COLS - 1)
+ valCols.shift();
+
+ valCols.push(item);
+
+ _chartApplySettings(paragraph, true);
+ }
+
+ return false;
+ };
+
+ $scope.scrollParagraphs = [];
+
+ $scope.rebuildScrollParagraphs = function() {
+ $scope.scrollParagraphs = $scope.notebook.paragraphs.map(function(paragraph) {
+ return {
+ text: paragraph.name,
+ click: 'scrollToParagraph("' + paragraph.id + '")'
+ };
+ });
+ };
+
+ $scope.scrollToParagraph = (id) => {
+ const idx = _.findIndex($scope.notebook.paragraphs, {id});
+
+ if (idx >= 0) {
+ if (!_.includes($scope.notebook.expandedParagraphs, idx))
+ $scope.notebook.expandedParagraphs = $scope.notebook.expandedParagraphs.concat([idx]);
+
+ if ($scope.notebook.paragraphs[idx].ace)
+ setTimeout(() => $scope.notebook.paragraphs[idx].ace.focus());
+ }
+
+ $location.hash(id);
+
+ $anchorScroll();
+ };
+
+ const _hideColumn = (col) => col.fieldName !== '_KEY' && col.fieldName !== '_VAL';
+
+ const _allColumn = () => true;
+
+ $scope.aceInit = function(paragraph) {
+ return function(editor) {
+ editor.setAutoScrollEditorIntoView(true);
+ editor.$blockScrolling = Infinity;
+
+ const renderer = editor.renderer;
+
+ renderer.setHighlightGutterLine(false);
+ renderer.setShowPrintMargin(false);
+ renderer.setOption('fontFamily', 'monospace');
+ renderer.setOption('fontSize', '14px');
+ renderer.setOption('minLines', '5');
+ renderer.setOption('maxLines', '15');
+
+ editor.setTheme('ace/theme/chrome');
+
+ Object.defineProperty(paragraph, 'ace', { value: editor });
+ };
+ };
+
+ /**
+ * Update caches list.
+ */
+ const _refreshFn = () => {
+ return agentMgr.topology(true)
+ .then((nodes) => {
+ $scope.caches = _.sortBy(_.reduce(nodes, (cachesAcc, node) => {
+ _.forEach(node.caches, (cache) => {
+ let item = _.find(cachesAcc, {name: cache.name});
+
+ if (_.isNil(item)) {
+ cache.label = maskCacheName(cache.name, true);
+ cache.value = cache.name;
+
+ cache.nodes = [];
+
+ cachesAcc.push(item = cache);
+ }
+
+ item.nodes.push({
+ nid: node.nodeId.toUpperCase(),
+ ip: _.head(node.attributes['org.apache.ignite.ips'].split(', ')),
+ version: node.attributes['org.apache.ignite.build.ver'],
+ gridName: node.attributes['org.apache.ignite.ignite.name'],
+ os: `${node.attributes['os.name']} ${node.attributes['os.arch']} ${node.attributes['os.version']}`,
+ client: node.attributes['org.apache.ignite.cache.client']
+ });
+ });
+
+ return cachesAcc;
+ }, []), (cache) => cache.label.toLowerCase());
+
+ // Reset to first cache in case of stopped selected.
+ const cacheNames = _.map($scope.caches, (cache) => cache.value);
+
+ _.forEach($scope.notebook.paragraphs, (paragraph) => {
+ if (!_.includes(cacheNames, paragraph.cacheName))
+ paragraph.cacheName = _.head(cacheNames);
+ });
+
+ // Await for demo caches.
+ if (!$ctrl.demoStarted && $root.IgniteDemoMode && _.nonEmpty(cacheNames)) {
+ $ctrl.demoStarted = true;
+
+ Loading.finish('sqlLoading');
+
+ _.forEach($scope.notebook.paragraphs, (paragraph) => $scope.execute(paragraph));
+ }
+ })
+ .catch((err) => Messages.showError(err));
+ };
+
+ const _startWatch = () => {
+ const awaitClusters$ = fromPromise(
+ agentMgr.startClusterWatch('Back to Configuration', 'base.configuration.tabs.advanced.clusters'));
+
+ const currentCluster$ = agentMgr.connectionSbj
+ .distinctUntilChanged((n, o) => n.cluster === o.cluster);
+
+ const finishLoading$ = defer(() => {
+ if (!$root.IgniteDemoMode)
+ Loading.finish('sqlLoading');
+ }).take(1);
+
+ const refreshCaches = (period) => {
+ return timer(0, period).exhaustMap(() => _refreshFn()).merge(finishLoading$);
+ };
+
+ this.refresh$ = awaitClusters$
+ .mergeMap(() => currentCluster$)
+ .do(() => Loading.start('sqlLoading'))
+ .do(() => {
+ _.forEach($scope.notebook.paragraphs, (paragraph) => {
+ paragraph.reset($interval);
+ });
+ })
+ .switchMap(() => refreshCaches(5000))
+ .subscribe();
+ };
+
+ Notebook.find($state.params.noteId)
+ .then((notebook) => {
+ $scope.notebook = _.cloneDeep(notebook);
+
+ $scope.notebook_name = $scope.notebook.name;
+
+ if (!$scope.notebook.expandedParagraphs)
+ $scope.notebook.expandedParagraphs = [];
+
+ if (!$scope.notebook.paragraphs)
+ $scope.notebook.paragraphs = [];
+
+ $scope.notebook.paragraphs = _.map($scope.notebook.paragraphs,
+ (paragraph) => new Paragraph($animate, $timeout, JavaTypes, paragraph));
+
+ if (_.isEmpty($scope.notebook.paragraphs))
+ $scope.addQuery();
+ else
+ $scope.rebuildScrollParagraphs();
+ })
+ .then(() => _startWatch())
+ .catch(() => {
+ $scope.notebookLoadFailed = true;
+
+ Loading.finish('sqlLoading');
+ });
+
+ $scope.renameNotebook = (name) => {
+ if (!name)
+ return;
+
+ if ($scope.notebook.name !== name) {
+ const prevName = $scope.notebook.name;
+
+ $scope.notebook.name = name;
+
+ Notebook.save($scope.notebook)
+ .then(() => $scope.notebook.edit = false)
+ .catch((err) => {
+ $scope.notebook.name = prevName;
+
+ Messages.showError(err);
+ });
+ }
+ else
+ $scope.notebook.edit = false;
+ };
+
+ $scope.removeNotebook = (notebook) => Notebook.remove(notebook);
+
+ $scope.renameParagraph = function(paragraph, newName) {
+ if (!newName)
+ return;
+
+ if (paragraph.name !== newName) {
+ paragraph.name = newName;
+
+ $scope.rebuildScrollParagraphs();
+
+ Notebook.save($scope.notebook)
+ .then(() => paragraph.edit = false)
+ .catch(Messages.showError);
+ }
+ else
+ paragraph.edit = false;
+ };
+
+ $scope.addParagraph = (paragraph, sz) => {
+ if ($scope.caches && $scope.caches.length > 0)
+ paragraph.cacheName = _.head($scope.caches).value;
+
+ $scope.notebook.paragraphs.push(paragraph);
+
+ $scope.notebook.expandedParagraphs.push(sz);
+
+ $scope.rebuildScrollParagraphs();
+
+ $location.hash(paragraph.id);
+ };
+
+ $scope.addQuery = function() {
+ const sz = $scope.notebook.paragraphs.length;
+
+ ActivitiesData.post({ action: '/queries/add/query' });
+
+ const paragraph = new Paragraph($animate, $timeout, JavaTypes, {
+ name: 'Query' + (sz === 0 ? '' : sz),
+ query: '',
+ pageSize: $scope.pageSizes[1],
+ timeLineSpan: $scope.timeLineSpans[0],
+ result: 'none',
+ rate: {
+ value: 1,
+ unit: 60000,
+ installed: false
+ },
+ qryType: 'query'
+ });
+
+ $scope.addParagraph(paragraph, sz);
+
+ $timeout(() => {
+ $anchorScroll();
+
+ paragraph.ace.focus();
+ });
+ };
+
+ $scope.addScan = function() {
+ const sz = $scope.notebook.paragraphs.length;
+
+ ActivitiesData.post({ action: '/queries/add/scan' });
+
+ const paragraph = new Paragraph($animate, $timeout, JavaTypes, {
+ name: 'Scan' + (sz === 0 ? '' : sz),
+ query: '',
+ pageSize: $scope.pageSizes[1],
+ timeLineSpan: $scope.timeLineSpans[0],
+ result: 'none',
+ rate: {
+ value: 1,
+ unit: 60000,
+ installed: false
+ },
+ qryType: 'scan'
+ });
+
+ $scope.addParagraph(paragraph, sz);
+ };
+
+ function _saveChartSettings(paragraph) {
+ if (!_.isEmpty(paragraph.charts)) {
+ const chart = paragraph.charts[0].api.getScope().chart;
+
+ if (!LegacyUtils.isDefined(paragraph.chartsOptions))
+ paragraph.chartsOptions = {barChart: {stacked: true}, areaChart: {style: 'stack'}};
+
+ switch (paragraph.result) {
+ case 'bar':
+ paragraph.chartsOptions.barChart.stacked = chart.stacked();
+
+ break;
+
+ case 'area':
+ paragraph.chartsOptions.areaChart.style = chart.style();
+
+ break;
+
+ default:
+ }
+ }
+ }
+
+ $scope.setResult = function(paragraph, new_result) {
+ if (paragraph.result === new_result)
+ return;
+
+ _saveChartSettings(paragraph);
+
+ paragraph.result = new_result;
+
+ if (paragraph.chart())
+ _chartApplySettings(paragraph, true);
+ };
+
+ $scope.resultEq = function(paragraph, result) {
+ return (paragraph.result === result);
+ };
+
+ $scope.removeParagraph = function(paragraph) {
+ Confirm.confirm('Are you sure you want to remove query: "' + paragraph.name + '"?')
+ .then(function() {
+ $scope.stopRefresh(paragraph);
+
+ const paragraph_idx = _.findIndex($scope.notebook.paragraphs, function(item) {
+ return paragraph === item;
+ });
+
+ const panel_idx = _.findIndex($scope.expandedParagraphs, function(item) {
+ return paragraph_idx === item;
+ });
+
+ if (panel_idx >= 0)
+ $scope.expandedParagraphs.splice(panel_idx, 1);
+
+ $scope.notebook.paragraphs.splice(paragraph_idx, 1);
+
+ $scope.rebuildScrollParagraphs();
+
+ Notebook.save($scope.notebook)
+ .catch(Messages.showError);
+ });
+ };
+
+ $scope.paragraphExpanded = function(paragraph) {
+ const paragraph_idx = _.findIndex($scope.notebook.paragraphs, function(item) {
+ return paragraph === item;
+ });
+
+ const panel_idx = _.findIndex($scope.notebook.expandedParagraphs, function(item) {
+ return paragraph_idx === item;
+ });
+
+ return panel_idx >= 0;
+ };
+
+ const _columnFilter = function(paragraph) {
+ return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn;
+ };
+
+ const _notObjectType = function(cls) {
+ return LegacyUtils.isJavaBuiltInClass(cls);
+ };
+
+ function _retainColumns(allCols, curCols, acceptableType, xAxis, unwantedCols) {
+ const retainedCols = [];
+
+ const availableCols = xAxis ? allCols : _.filter(allCols, function(col) {
+ return col.value >= 0;
+ });
+
+ if (availableCols.length > 0) {
+ curCols.forEach(function(curCol) {
+ const col = _.find(availableCols, {label: curCol.label});
+
+ if (col && acceptableType(col.type)) {
+ col.aggFx = curCol.aggFx;
+
+ retainedCols.push(col);
+ }
+ });
+
+ // If nothing was restored, add first acceptable column.
+ if (_.isEmpty(retainedCols)) {
+ let col;
+
+ if (unwantedCols)
+ col = _.find(availableCols, (avCol) => !_.find(unwantedCols, {label: avCol.label}) && acceptableType(avCol.type));
+
+ if (!col)
+ col = _.find(availableCols, (avCol) => acceptableType(avCol.type));
+
+ if (col)
+ retainedCols.push(col);
+ }
+ }
+
+ return retainedCols;
+ }
+
+ const _rebuildColumns = function(paragraph) {
+ _.forEach(_.groupBy(paragraph.meta, 'fieldName'), function(colsByName, fieldName) {
+ const colsByTypes = _.groupBy(colsByName, 'typeName');
+
+ const needType = _.keys(colsByTypes).length > 1;
+
+ _.forEach(colsByTypes, function(colsByType, typeName) {
+ _.forEach(colsByType, function(col, ix) {
+ col.fieldName = (needType && !LegacyUtils.isEmptyString(typeName) ? typeName + '.' : '') + fieldName + (ix > 0 ? ix : '');
+ });
+ });
+ });
+
+ paragraph.gridOptions.rebuildColumns();
+
+ paragraph.chartColumns = _.reduce(paragraph.meta, (acc, col, idx) => {
+ if (_notObjectType(col.fieldTypeName)) {
+ acc.push({
+ label: col.fieldName,
+ type: col.fieldTypeName,
+ aggFx: $scope.aggregateFxs[0],
+ value: idx.toString()
+ });
+ }
+
+ return acc;
+ }, []);
+
+ if (paragraph.chartColumns.length > 0) {
+ paragraph.chartColumns.push(TIME_LINE);
+ paragraph.chartColumns.push(ROW_IDX);
+ }
+
+ // We could accept onl not object columns for X axis.
+ paragraph.chartKeyCols = _retainColumns(paragraph.chartColumns, paragraph.chartKeyCols, _notObjectType, true);
+
+ // We could accept only numeric columns for Y axis.
+ paragraph.chartValCols = _retainColumns(paragraph.chartColumns, paragraph.chartValCols, _numberType, false, paragraph.chartKeyCols);
+ };
+
+ $scope.toggleSystemColumns = function(paragraph) {
+ if (paragraph.disabledSystemColumns)
+ return;
+
+ paragraph.columnFilter = _columnFilter(paragraph);
+
+ paragraph.chartColumns = [];
+
+ _rebuildColumns(paragraph);
+ };
+
+ const _showLoading = (paragraph, enable) => paragraph.loading = enable;
+
+ /**
+ * @param {Object} paragraph Query
+ * @param {Boolean} clearChart Flag is need clear chart model.
+ * @param {{columns: Array, rows: Array, responseNodeId: String, queryId: int, hasMore: Boolean}} res Query results.
+ * @private
+ */
+ const _processQueryResult = (paragraph, clearChart, res) => {
+ const prevKeyCols = paragraph.chartKeyCols;
+ const prevValCols = paragraph.chartValCols;
+
+ if (!_.eq(paragraph.meta, res.columns)) {
+ paragraph.meta = [];
+
+ paragraph.chartColumns = [];
+
+ if (!LegacyUtils.isDefined(paragraph.chartKeyCols))
+ paragraph.chartKeyCols = [];
+
+ if (!LegacyUtils.isDefined(paragraph.chartValCols))
+ paragraph.chartValCols = [];
+
+ if (res.columns.length) {
+ const _key = _.find(res.columns, {fieldName: '_KEY'});
+ const _val = _.find(res.columns, {fieldName: '_VAL'});
+
+ paragraph.disabledSystemColumns = !(_key && _val) ||
+ (res.columns.length === 2 && _key && _val) ||
+ (res.columns.length === 1 && (_key || _val));
+ }
+
+ paragraph.columnFilter = _columnFilter(paragraph);
+
+ paragraph.meta = res.columns;
+
+ _rebuildColumns(paragraph);
+ }
+
+ paragraph.page = 1;
+
+ paragraph.total = 0;
+
+ paragraph.duration = res.duration;
+
+ paragraph.queryId = res.hasMore ? res.queryId : null;
+
+ paragraph.resNodeId = res.responseNodeId;
+
+ paragraph.setError({message: ''});
+
+ // Prepare explain results for display in table.
+ if (paragraph.queryArgs.query && paragraph.queryArgs.query.startsWith('EXPLAIN') && res.rows) {
+ paragraph.rows = [];
+
+ res.rows.forEach((row, i) => {
+ const line = res.rows.length - 1 === i ? row[0] : row[0] + '\n';
+
+ line.replace(/\"/g, '').split('\n').forEach((ln) => paragraph.rows.push([ln]));
+ });
+ }
+ else
+ paragraph.rows = res.rows;
+
+ paragraph.gridOptions.adjustHeight(paragraph.rows.length);
+
+ const chartHistory = paragraph.chartHistory;
+
+ // Clear history on query change.
+ if (clearChart) {
+ chartHistory.length = 0;
+
+ _.forEach(paragraph.charts, (chart) => chart.data.length = 0);
+ }
+
+ // Add results to history.
+ chartHistory.push({tm: new Date(), rows: paragraph.rows});
+
+ // Keep history size no more than max length.
+ while (chartHistory.length > HISTORY_LENGTH)
+ chartHistory.shift();
+
+ _showLoading(paragraph, false);
+
+ if (_.isNil(paragraph.result) || paragraph.result === 'none' || paragraph.scanExplain())
+ paragraph.result = 'table';
+ else if (paragraph.chart()) {
+ let resetCharts = clearChart;
+
+ if (!resetCharts) {
+ const curKeyCols = paragraph.chartKeyCols;
+ const curValCols = paragraph.chartValCols;
+
+ resetCharts = !prevKeyCols || !prevValCols ||
+ prevKeyCols.length !== curKeyCols.length ||
+ prevValCols.length !== curValCols.length;
+ }
+
+ _chartApplySettings(paragraph, resetCharts);
+ }
+ };
+
+ const _closeOldQuery = (paragraph) => {
+ const nid = paragraph.resNodeId;
+
+ if (paragraph.queryId && _.find($scope.caches, ({nodes}) => _.find(nodes, {nid: nid.toUpperCase()})))
+ return agentMgr.queryClose(nid, paragraph.queryId);
+
+ return $q.when();
+ };
+
+ /**
+ * @param {String} name Cache name.
+ * @return {Array.<String>} Nids
+ */
+ const cacheNodes = (name) => {
+ return _.find($scope.caches, {name}).nodes;
+ };
+
+ /**
+ * @param {String} name Cache name.
+ * @param {Boolean} local Local query.
+ * @return {String} Nid
+ */
+ const _chooseNode = (name, local) => {
+ if (_.isEmpty(name))
+ return Promise.resolve(null);
+
+ const nodes = _.filter(cacheNodes(name), (node) => !node.client);
+
+ if (local) {
+ return Nodes.selectNode(nodes, name)
+ .then((selectedNids) => _.head(selectedNids));
+ }
+
+ return Promise.resolve(nodes[_.random(0, nodes.length - 1)].nid);
+ };
+
+ const _executeRefresh = (paragraph) => {
+ const args = paragraph.queryArgs;
+
+ agentMgr.awaitCluster()
+ .then(() => _closeOldQuery(paragraph))
+ .then(() => args.localNid || _chooseNode(args.cacheName, false))
+ .then((nid) => agentMgr.querySql(nid, args.cacheName, args.query, args.nonCollocatedJoins,
+ args.enforceJoinOrder, false, !!args.localNid, args.pageSize, args.lazy))
+ .then((res) => _processQueryResult(paragraph, false, res))
+ .catch((err) => paragraph.setError(err));
+ };
+
+ const _tryStartRefresh = function(paragraph) {
+ _tryStopRefresh(paragraph);
+
+ if (_.get(paragraph, 'rate.installed') && paragraph.queryExecuted()) {
+ $scope.chartAcceptKeyColumn(paragraph, TIME_LINE);
+
+ _executeRefresh(paragraph);
+
+ const delay = paragraph.rate.value * paragraph.rate.unit;
+
+ paragraph.rate.stopTime = $interval(_executeRefresh, delay, 0, false, paragraph);
+ }
+ };
+
+ const addLimit = (query, limitSize) =>
+ `SELECT * FROM (
+ ${query}
+ ) LIMIT ${limitSize}`;
+
+ $scope.nonCollocatedJoinsAvailable = (paragraph) => {
+ const cache = _.find($scope.caches, {name: paragraph.cacheName});
+
+ if (cache)
+ return !!_.find(cache.nodes, (node) => Version.since(node.version, NON_COLLOCATED_JOINS_SINCE));
+
+ return false;
+ };
+
+ $scope.enforceJoinOrderAvailable = (paragraph) => {
+ const cache = _.find($scope.caches, {name: paragraph.cacheName});
+
+ if (cache)
+ return !!_.find(cache.nodes, (node) => Version.since(node.version, ...ENFORCE_JOIN_SINCE));
+
+ return false;
+ };
+
+ $scope.lazyQueryAvailable = (paragraph) => {
+ const cache = _.find($scope.caches, {name: paragraph.cacheName});
+
+ if (cache)
+ return !!_.find(cache.nodes, (node) => Version.since(node.version, ...LAZY_QUERY_SINCE));
+
+ return false;
+ };
+
+ $scope.ddlAvailable = (paragraph) => {
+ const cache = _.find($scope.caches, {name: paragraph.cacheName});
+
+ if (cache)
+ return !!_.find(cache.nodes, (node) => Version.since(node.version, ...DDL_SINCE));
+
+ return false;
+ };
+
+ $scope.execute = (paragraph, local = false) => {
+ const nonCollocatedJoins = !!paragraph.nonCollocatedJoins;
+ const enforceJoinOrder = !!paragraph.enforceJoinOrder;
+ const lazy = !!paragraph.lazy;
+
+ $scope.queryAvailable(paragraph) && _chooseNode(paragraph.cacheName, local)
+ .then((nid) => {
+ Notebook.save($scope.notebook)
+ .catch(Messages.showError);
+
+ paragraph.localQueryMode = local;
+ paragraph.prevQuery = paragraph.queryArgs ? paragraph.queryArgs.query : paragraph.query;
+
+ _showLoading(paragraph, true);
+
+ return _closeOldQuery(paragraph)
+ .then(() => {
+ const args = paragraph.queryArgs = {
+ type: 'QUERY',
+ cacheName: ($scope.ddlAvailable(paragraph) && !paragraph.useAsDefaultSchema) ? null : paragraph.cacheName,
+ query: paragraph.query,
+ pageSize: paragraph.pageSize,
+ maxPages: paragraph.maxPages,
+ nonCollocatedJoins,
+ enforceJoinOrder,
+ localNid: local ? nid : null,
+ lazy
+ };
+
+ const qry = args.maxPages ? addLimit(args.query, args.pageSize * args.maxPages) : paragraph.query;
+
+ ActivitiesData.post({ action: '/queries/execute' });
+
+ return agentMgr.querySql(nid, args.cacheName, qry, nonCollocatedJoins, enforceJoinOrder, false, local, args.pageSize, lazy);
+ })
+ .then((res) => {
+ _processQueryResult(paragraph, true, res);
+
+ _tryStartRefresh(paragraph);
+ })
+ .catch((err) => {
+ paragraph.setError(err);
+
+ _showLoading(paragraph, false);
+
+ $scope.stopRefresh(paragraph);
+ })
+ .then(() => paragraph.ace.focus());
+ });
+ };
+
+ const _cancelRefresh = (paragraph) => {
+ if (paragraph.rate && paragraph.rate.stopTime) {
+ delete paragraph.queryArgs;
+
+ paragraph.rate.installed = false;
+
+ $interval.cancel(paragraph.rate.stopTime);
+
+ delete paragraph.rate.stopTime;
+ }
+ };
+
+ $scope.explain = (paragraph) => {
+ if (!$scope.queryAvailable(paragraph))
+ return;
+
+ Notebook.save($scope.notebook)
+ .catch(Messages.showError);
+
+ _cancelRefresh(paragraph);
+
+ _showLoading(paragraph, true);
+
+ _closeOldQuery(paragraph)
+ .then(() => _chooseNode(paragraph.cacheName, false))
+ .then((nid) => {
+ const args = paragraph.queryArgs = {
+ type: 'EXPLAIN',
+ cacheName: paragraph.cacheName,
+ query: 'EXPLAIN ' + paragraph.query,
+ pageSize: paragraph.pageSize
+ };
+
+ ActivitiesData.post({ action: '/queries/explain' });
+
+ return agentMgr.querySql(nid, args.cacheName, args.query, false, !!paragraph.enforceJoinOrder, false, false, args.pageSize, false);
+ })
+ .then((res) => _processQueryResult(paragraph, true, res))
+ .catch((err) => {
+ paragraph.setError(err);
+
+ _showLoading(paragraph, false);
+ })
+ .then(() => paragraph.ace.focus());
+ };
+
+ $scope.scan = (paragraph, local = false) => {
+ const cacheName = paragraph.cacheName;
+ const caseSensitive = !!paragraph.caseSensitive;
+ const filter = paragraph.filter;
+ const pageSize = paragraph.pageSize;
+
+ paragraph.localQueryMode = local;
+
+ $scope.scanAvailable(paragraph) && _chooseNode(cacheName, local)
+ .then((nid) => {
+ paragraph.scanningInProgress = true;
+
+ Notebook.save($scope.notebook)
+ .catch(Messages.showError);
+
+ _cancelRefresh(paragraph);
+
+ _showLoading(paragraph, true);
+
+ _closeOldQuery(paragraph)
+ .then(() => {
+ paragraph.queryArgs = {
+ type: 'SCAN',
+ cacheName,
+ filter,
+ regEx: false,
+ caseSensitive,
+ near: false,
+ pageSize,
+ localNid: local ? nid : null
+ };
+
+ ActivitiesData.post({ action: '/queries/scan' });
+
+ return agentMgr.queryScan(nid, cacheName, filter, false, caseSensitive, false, local, pageSize);
+ })
+ .then((res) => _processQueryResult(paragraph, true, res))
+ .catch((err) => {
+ paragraph.setError(err);
+
+ _showLoading(paragraph, false);
+ })
+ .then(() => paragraph.scanningInProgress = false);
+ });
+ };
+
+ function _updatePieChartsWithData(paragraph, newDatum) {
+ $timeout(() => {
+ _.forEach(paragraph.charts, function(chart) {
+ const chartDatum = chart.data;
+
+ chartDatum.length = 0;
+
+ _.forEach(newDatum, function(series) {
+ if (chart.options.title.text === series.key)
+ _.forEach(series.values, (v) => chartDatum.push(v));
+ });
+ });
+
+ _.forEach(paragraph.charts, (chart) => chart.api.update());
+ });
+ }
+
+ $scope.nextPage = (paragraph) => {
+ _showLoading(paragraph, true);
+
+ paragraph.queryArgs.pageSize = paragraph.pageSize;
+
+ agentMgr.queryNextPage(paragraph.resNodeId, paragraph.queryId, paragraph.pageSize)
+ .then((res) => {
+ paragraph.page++;
+
+ paragraph.total += paragraph.rows.length;
+
+ paragraph.duration = res.duration;
+
+ paragraph.rows = res.rows;
+
+ if (paragraph.chart()) {
+ if (paragraph.result === 'pie')
+ _updatePieChartsWithData(paragraph, _pieChartDatum(paragraph));
+ else
+ _updateChartsWithData(paragraph, _chartDatum(paragraph));
+ }
+
+ paragraph.gridOptions.adjustHeight(paragraph.rows.length);
+
+ _showLoading(paragraph, false);
+
+ if (!res.hasMore)
+ delete paragraph.queryId;
+ })
+ .catch((err) => {
+ paragraph.setError(err);
+
+ _showLoading(paragraph, false);
+ })
+ .then(() => paragraph.ace && paragraph.ace.focus());
+ };
+
+ const _export = (fileName, columnDefs, meta, rows, toClipBoard = false) => {
+ let csvContent = '';
+
+ const cols = [];
+ const excludedCols = [];
+
+ _.forEach(meta, (col, idx) => {
+ if (columnDefs[idx].visible)
+ cols.push(_fullColName(col));
+ else
+ excludedCols.push(idx);
+ });
+
+ csvContent += cols.join(';') + '\n';
+
+ _.forEach(rows, (row) => {
+ cols.length = 0;
+
+ if (Array.isArray(row)) {
+ _.forEach(row, (elem, idx) => {
+ if (_.includes(excludedCols, idx))
+ return;
+
+ cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem));
+ });
+ }
+ else {
+ _.forEach(columnDefs, (col) => {
+ if (col.visible) {
+ const elem = row[col.fieldName];
+
+ cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem));
+ }
+ });
+ }
+
+ csvContent += cols.join(';') + '\n';
+ });
+
+ if (toClipBoard)
+ IgniteCopyToClipboard.copy(csvContent);
+ else
+ LegacyUtils.download('text/csv', fileName, csvContent);
+ };
+
+ /**
+ * Generate file name with query results.
+ *
+ * @param paragraph {Object} Query paragraph .
+ * @param all {Boolean} All result export flag.
+ * @returns {string}
+ */
+ const exportFileName = (paragraph, all) => {
+ const args = paragraph.queryArgs;
+
+ if (args.type === 'SCAN')
+ return `export-scan-${args.cacheName}-${paragraph.name}${all ? '-all' : ''}.csv`;
+
+ return `export-query-${paragraph.name}${all ? '-all' : ''}.csv`;
+ };
+
+ $scope.exportCsvToClipBoard = (paragraph) => {
+ _export(exportFileName(paragraph, false), paragraph.gridOptions.columnDefs, paragraph.meta, paragraph.rows, true);
+ };
+
+ $scope.exportCsv = function(paragraph) {
+ _export(exportFileName(paragraph, false), paragraph.gridOptions.columnDefs, paragraph.meta, paragraph.rows);
+
+ // paragraph.gridOptions.api.exporter.csvExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE);
+ };
+
+ $scope.exportPdf = function(paragraph) {
+ paragraph.gridOptions.api.exporter.pdfExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE);
+ };
+
+ $scope.exportCsvAll = (paragraph) => {
+ paragraph.csvIsPreparing = true;
+
+ const args = paragraph.queryArgs;
+
+ return Promise.resolve(args.localNid || _chooseNode(args.cacheName, false))
+ .then((nid) => args.type === 'SCAN'
+ ? agentMgr.queryScanGetAll(nid, args.cacheName, args.query, !!args.regEx, !!args.caseSensitive, !!args.near, !!args.localNid)
+ : agentMgr.querySqlGetAll(nid, args.cacheName, args.query, !!args.nonCollocatedJoins, !!args.enforceJoinOrder, false, !!args.localNid, !!args.lazy))
+ .then((res) => _export(exportFileName(paragraph, true), paragraph.gridOptions.columnDefs, res.columns, res.rows))
+ .catch(Messages.showError)
+ .then(() => {
+ paragraph.csvIsPreparing = false;
+
+ return paragraph.ace && paragraph.ace.focus();
+ });
+ };
+
+ // $scope.exportPdfAll = function(paragraph) {
+ // $http.post('/api/v1/agent/query/getAll', {query: paragraph.query, cacheName: paragraph.cacheName})
+ // .then(({data}) {
+ // _export(paragraph.name + '-all.csv', data.meta, data.rows);
+ // })
+ // .catch(Messages.showError);
+ // };
+
+ $scope.rateAsString = function(paragraph) {
+ if (paragraph.rate && paragraph.rate.installed) {
+ const idx = _.findIndex($scope.timeUnit, function(unit) {
+ return unit.value === paragraph.rate.unit;
+ });
+
+ if (idx >= 0)
+ return ' ' + paragraph.rate.value + $scope.timeUnit[idx].short;
+
+ paragraph.rate.installed = false;
+ }
+
+ return '';
+ };
+
+ $scope.startRefresh = function(paragraph, value, unit) {
+ paragraph.rate.value = value;
+ paragraph.rate.unit = unit;
+ paragraph.rate.installed = true;
+
+ if (paragraph.queryExecuted() && !paragraph.scanExplain())
+ _tryStartRefresh(paragraph);
+ };
+
+ $scope.stopRefresh = function(paragraph) {
+ paragraph.rate.installed = false;
+
+ _tryStopRefresh(paragraph);
+ };
+
+ $scope.paragraphTimeSpanVisible = function(paragraph) {
+ return paragraph.timeLineSupported() && paragraph.chartTimeLineEnabled();
+ };
+
+ $scope.paragraphTimeLineSpan = function(paragraph) {
+ if (paragraph && paragraph.timeLineSpan)
+ return paragraph.timeLineSpan.toString();
+
+ return '1';
+ };
+
+ $scope.applyChartSettings = function(paragraph) {
+ _chartApplySettings(paragraph, true);
+ };
+
+ $scope.queryAvailable = function(paragraph) {
+ return paragraph.query && !paragraph.loading;
+ };
+
+ $scope.queryTooltip = function(paragraph, action) {
+ if ($scope.queryAvailable(paragraph))
+ return;
+
+ if (paragraph.loading)
+ return 'Waiting for server response';
+
+ return 'Input text to ' + action;
+ };
+
+ $scope.scanAvailable = function(paragraph) {
+ return $scope.caches.length && !(paragraph.loading || paragraph.csvIsPreparing);
+ };
+
+ $scope.scanTooltip = function(paragraph) {
+ if ($scope.scanAvailable(paragraph))
+ return;
+
+ if (paragraph.loading)
+ return 'Waiting for server response';
+
+ return 'Select cache to export scan results';
+ };
+
+ $scope.clickableMetadata = function(node) {
+ return node.type.slice(0, 5) !== 'index';
+ };
+
+ $scope.dblclickMetadata = function(paragraph, node) {
+ paragraph.ace.insert(node.name);
+
+ setTimeout(() => paragraph.ace.focus(), 1);
+ };
+
+ $scope.importMetadata = function() {
+ Loading.start('loadingCacheMetadata');
+
+ $scope.metadata = [];
+
+ agentMgr.metadata()
+ .then((metadata) => {
+ $scope.metadata = _.sortBy(_.filter(metadata, (meta) => {
+ const cache = _.find($scope.caches, { name: meta.cacheName });
+
+ if (cache) {
+ meta.name = (cache.sqlSchema || '"' + meta.cacheName + '"') + '.' + meta.typeName;
+ meta.displayName = (cache.sqlSchema || meta.maskedName) + '.' + meta.typeName;
+
+ if (cache.sqlSchema)
+ meta.children.unshift({type: 'plain', name: 'cacheName: ' + meta.maskedName, maskedName: meta.maskedName});
+
+ meta.children.unshift({type: 'plain', name: 'mode: ' + cache.mode, maskedName: meta.maskedName});
+ }
+
+ return cache;
+ }), 'name');
+ })
+ .catch(Messages.showError)
+ .then(() => Loading.finish('loadingCacheMetadata'));
+ };
+
+ $scope.showResultQuery = function(paragraph) {
+ if (!_.isNil(paragraph)) {
+ const scope = $scope.$new();
+
+ if (paragraph.queryArgs.type === 'SCAN') {
+ scope.title = 'SCAN query';
+
+ const filter = paragraph.queryArgs.filter;
+
+ if (_.isEmpty(filter))
+ scope.content = [`SCAN query for cache: <b>${maskCacheName(paragraph.queryArgs.cacheName, true)}</b>`];
+ else
+ scope.content = [`SCAN query for cache: <b>${maskCacheName(paragraph.queryArgs.cacheName, true)}</b> with filter: <b>${filter}</b>`];
+ }
+ else if (paragraph.queryArgs.query .startsWith('EXPLAIN ')) {
+ scope.title = 'Explain query';
+ scope.content = paragraph.queryArgs.query.split(/\r?\n/);
+ }
+ else {
+ scope.title = 'SQL query';
+ scope.content = paragraph.queryArgs.query.split(/\r?\n/);
+ }
+
+ // Attach duration and selected node info
+ scope.meta = `Duration: ${$filter('duration')(paragraph.duration)}.`;
+ scope.meta += paragraph.localQueryMode ? ` Node ID8: ${_.id8(paragraph.resNodeId)}` : '';
+
+ // Show a basic modal from a controller
+ $modal({scope, templateUrl: messageTemplateUrl, show: true});
+ }
+ };
+
+ $scope.showStackTrace = function(paragraph) {
+ if (!_.isNil(paragraph)) {
+ const scope = $scope.$new();
+
+ scope.title = 'Error details';
+ scope.content = [];
+
+ const tab = ' ';
+
+ const addToTrace = (item) => {
+ if (_.nonNil(item)) {
+ const clsName = _.isEmpty(item.className) ? '' : '[' + JavaTypes.shortClassName(item.className) + '] ';
+
+ scope.content.push((scope.content.length > 0 ? tab : '') + clsName + (item.message || ''));
+
+ addToTrace(item.cause);
+
+ _.forEach(item.suppressed, (sup) => addToTrace(sup));
+ }
+ };
+
+ addToTrace(paragraph.error.root);
+
+ // Show a basic modal from a controller
+ $modal({scope, templateUrl: messageTemplateUrl, show: true});
+ }
+ };
+ }
+
+ $onInit() {
+
+ }
+
+ $onDestroy() {
+ this.refresh$.unsubscribe();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/page-queries/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-queries/index.js b/modules/web-console/frontend/app/components/page-queries/index.js
new file mode 100644
index 0000000..4b553eb
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-queries/index.js
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 './style.scss';
+import angular from 'angular';
+
+import templateUrl from './template.tpl.pug';
+
+import NotebookData from './Notebook.data';
+import Notebook from './Notebook.service';
+import notebook from './notebook.controller';
+import controller from './controller';
+
+export default angular.module('ignite-console.sql', [
+ 'ui.router'
+])
+.component('pageQueries', {
+ controller,
+ templateUrl
+})
+.config(['$stateProvider', ($stateProvider) => {
+ // set up the states
+ $stateProvider
+ .state('base.sql', {
+ url: '/queries',
+ abstract: true,
+ template: '<ui-view></ui-view>'
+ })
+ .state('base.sql.notebook', {
+ url: '/notebook/{noteId}',
+ component: 'pageQueries',
+ permission: 'query',
+ tfMetaTags: {
+ title: 'Query notebook'
+ }
+ })
+ .state('base.sql.demo', {
+ url: '/demo',
+ component: 'pageQueries',
+ permission: 'query',
+ tfMetaTags: {
+ title: 'SQL demo'
+ }
+ });
+}])
+.service('IgniteNotebookData', NotebookData)
+.service('IgniteNotebook', Notebook)
+.controller('notebookController', notebook);
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/page-queries/notebook.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-queries/notebook.controller.js b/modules/web-console/frontend/app/components/page-queries/notebook.controller.js
new file mode 100644
index 0000000..68d318a
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-queries/notebook.controller.js
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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);
+ }
+];
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/page-queries/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-queries/style.scss b/modules/web-console/frontend/app/components/page-queries/style.scss
new file mode 100644
index 0000000..70136fd
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-queries/style.scss
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+page-queries {
+ .docs-content > header {
+ margin: 0;
+ margin-bottom: 30px;
+
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ h1 {
+ margin: 0;
+ margin-right: 8px;
+ }
+ }
+
+ .affix + .block-information {
+ margin-top: 90px;
+ }
+}
[3/5] ignite git commit: IGNITE-6390 Web Console: Added component for
cluster selection.
Posted by ak...@apache.org.
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') with filter: #[b {{ paragraph.queryArgs.filter }}]
+ span(ng-if='paragraph.queryArgs.localNid') 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);
- }
-];
[5/5] ignite git commit: IGNITE-6390 Web Console: Added component for
cluster selection.
Posted by ak...@apache.org.
IGNITE-6390 Web Console: Added component for cluster selection.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/1367bc98
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/1367bc98
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/1367bc98
Branch: refs/heads/master
Commit: 1367bc98eb08233f9e47ba45335f9dda1fbb7bbd
Parents: cbd69d6
Author: Dmitriy Shabalin <ds...@gridgain.com>
Authored: Wed Dec 6 10:36:42 2017 +0700
Committer: Alexey Kuznetsov <ak...@apache.org>
Committed: Wed Dec 6 10:36:42 2017 +0700
----------------------------------------------------------------------
.../internal/visor/util/VisorTaskUtils.java | 139 ++
.../commands/tasks/VisorTasksCommand.scala | 1 +
.../scala/org/apache/ignite/visor/visor.scala | 49 -
modules/web-console/backend/app/agentSocket.js | 2 +-
.../web-console/backend/app/agentsHandler.js | 51 +-
.../web-console/backend/app/browsersHandler.js | 7 +
modules/web-console/backend/app/mongo.js | 1 +
modules/web-console/backend/package.json | 3 +-
modules/web-console/frontend/app/app.js | 8 +-
.../app/components/bs-select-menu/style.scss | 4 +-
.../cluster-select/cluster-select.controller.js | 64 -
.../cluster-select/cluster-select.pug | 47 -
.../cluster-select/cluster-select.scss | 30 -
.../app/components/cluster-select/index.js | 29 -
.../components/cluster-selector/component.js | 25 +
.../components/cluster-selector/controller.js | 62 +
.../app/components/cluster-selector/index.js | 23 +
.../app/components/cluster-selector/style.scss | 66 +
.../components/cluster-selector/template.pug | 75 +
.../app/components/list-editable/controller.js | 2 +-
.../components/page-queries/Notebook.data.js | 168 ++
.../components/page-queries/Notebook.service.js | 74 +
.../app/components/page-queries/controller.js | 1938 ++++++++++++++++++
.../app/components/page-queries/index.js | 62 +
.../page-queries/notebook.controller.js | 62 +
.../app/components/page-queries/style.scss | 36 +
.../components/page-queries/template.tpl.pug | 385 ++++
.../app/modules/agent/AgentManager.service.js | 57 +-
.../frontend/app/modules/sql/Notebook.data.js | 168 --
.../app/modules/sql/Notebook.service.js | 74 -
.../app/modules/sql/notebook.controller.js | 62 -
.../frontend/app/modules/sql/sql.controller.js | 1887 -----------------
.../frontend/app/modules/sql/sql.module.js | 61 -
.../frontend/app/primitives/switcher/index.pug | 2 +-
.../frontend/app/primitives/switcher/index.scss | 69 +-
.../frontend/views/includes/header-right.pug | 2 -
.../web-console/frontend/views/sql/sql.tpl.pug | 381 ----
.../console/agent/handlers/ClusterListener.java | 178 +-
.../ignite/console/agent/rest/RestExecutor.java | 63 +-
39 files changed, 3477 insertions(+), 2940 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
index ace451c..fda801c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
@@ -23,8 +23,10 @@ import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.URL;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
@@ -69,6 +71,7 @@ import org.apache.ignite.internal.visor.log.VisorLogFile;
import org.apache.ignite.lang.IgniteClosure;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.spi.eventstorage.NoopEventStorageSpi;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static java.lang.System.getProperty;
@@ -1113,4 +1116,140 @@ public class VisorTaskUtils {
public static boolean joinTimedOut(String msg) {
return msg != null && msg.startsWith("Join process timed out.");
}
+
+ /**
+ * Special wrapper over address that can be sorted in following order:
+ * IPv4, private IPv4, IPv4 local host, IPv6.
+ * Lower addresses first.
+ */
+ private static class SortableAddress implements Comparable<SortableAddress> {
+ /** */
+ private int type;
+
+ /** */
+ private BigDecimal bits;
+
+ /** */
+ private String addr;
+
+ /**
+ * Constructor.
+ *
+ * @param addr Address as string.
+ */
+ private SortableAddress(String addr) {
+ this.addr = addr;
+
+ if (addr.indexOf(':') > 0)
+ type = 4; // IPv6
+ else {
+ try {
+ InetAddress inetAddr = InetAddress.getByName(addr);
+
+ if (inetAddr.isLoopbackAddress())
+ type = 3; // localhost
+ else if (inetAddr.isSiteLocalAddress())
+ type = 2; // private IPv4
+ else
+ type = 1; // other IPv4
+ }
+ catch (UnknownHostException ignored) {
+ type = 5;
+ }
+ }
+
+ bits = BigDecimal.valueOf(0L);
+
+ try {
+ String[] octets = addr.contains(".") ? addr.split(".") : addr.split(":");
+
+ int len = octets.length;
+
+ for (int i = 0; i < len; i++) {
+ long oct = F.isEmpty(octets[i]) ? 0 : Long.valueOf( octets[i]);
+ long pow = Double.valueOf(Math.pow(256, octets.length - 1 - i)).longValue();
+
+ bits = bits.add(BigDecimal.valueOf(oct * pow));
+ }
+ }
+ catch (Exception ignore) {
+ // No-op.
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public int compareTo(@NotNull SortableAddress o) {
+ return (type == o.type ? bits.compareTo(o.bits) : Integer.compare(type, o.type));
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ SortableAddress other = (SortableAddress)o;
+
+ return addr != null ? addr.equals(other.addr) : other.addr == null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return addr != null ? addr.hashCode() : 0;
+ }
+
+ /**
+ * @return Address.
+ */
+ public String address() {
+ return addr;
+ }
+ }
+
+ /**
+ * Sort addresses: IPv4 & real addresses first.
+ *
+ * @param addrs Addresses to sort.
+ * @return Sorted list.
+ */
+ public static Collection<String> sortAddresses(Collection<String> addrs) {
+ if (F.isEmpty(addrs))
+ return Collections.emptyList();
+
+ int sz = addrs.size();
+
+ List<SortableAddress> sorted = new ArrayList<>(sz);
+
+ for (String addr : addrs)
+ sorted.add(new SortableAddress(addr));
+
+ Collections.sort(sorted);
+
+ Collection<String> res = new ArrayList<>(sz);
+
+ for (SortableAddress sa : sorted)
+ res.add(sa.address());
+
+ return res;
+ }
+
+ /**
+ * Split addresses.
+ *
+ * @param s String with comma separted addresses.
+ * @return Collection of addresses.
+ */
+ public static Collection<String> splitAddresses(String s) {
+ if (F.isEmpty(s))
+ return Collections.emptyList();
+
+ String[] addrs = s.split(",");
+
+ for (int i = 0; i < addrs.length; i++)
+ addrs[i] = addrs[i].trim();
+
+ return Arrays.asList(addrs);
+ }
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/tasks/VisorTasksCommand.scala
----------------------------------------------------------------------
diff --git a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/tasks/VisorTasksCommand.scala b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/tasks/VisorTasksCommand.scala
index 4d9b795..0d6753e 100644
--- a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/tasks/VisorTasksCommand.scala
+++ b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/tasks/VisorTasksCommand.scala
@@ -32,6 +32,7 @@ import java.util.UUID
import org.apache.ignite.internal.visor.event.{VisorGridEvent, VisorGridJobEvent, VisorGridTaskEvent}
import org.apache.ignite.internal.visor.node.VisorNodeEventsCollectorTask
import org.apache.ignite.internal.visor.node.VisorNodeEventsCollectorTaskArg
+import org.apache.ignite.internal.visor.util.VisorTaskUtils._
import scala.collection.JavaConversions._
import scala.language.implicitConversions
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
----------------------------------------------------------------------
diff --git a/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala b/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
index ffc7a00..1a46316 100644
--- a/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
+++ b/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
@@ -2694,53 +2694,4 @@ object visor extends VisorTag {
else
Long.MaxValue
}
-
- /**
- * Sort addresses to properly display in Visor.
- *
- * @param addrs Addresses to sort.
- * @return Sorted list.
- */
- def sortAddresses(addrs: Iterable[String]) = {
- def ipToLong(ip: String) = {
- try {
- val octets = if (ip.contains(".")) ip.split('.') else ip.split(':')
-
- var dec = BigDecimal.valueOf(0L)
-
- for (i <- octets.indices) dec += octets(i).toLong * math.pow(256, octets.length - 1 - i).toLong
-
- dec
- }
- catch {
- case _: Exception => BigDecimal.valueOf(0L)
- }
- }
-
- /**
- * Sort addresses to properly display in Visor.
- *
- * @param addr Address to detect type for.
- * @return IP class type for sorting in order: public addresses IPv4 + private IPv4 + localhost + IPv6.
- */
- def addrType(addr: String) = {
- if (addr.contains(':'))
- 4 // IPv6
- else {
- try {
- InetAddress.getByName(addr) match {
- case ip if ip.isLoopbackAddress => 3 // localhost
- case ip if ip.isSiteLocalAddress => 2 // private IPv4
- case _ => 1 // other IPv4
- }
- }
- catch {
- case ignore: UnknownHostException => 5
- }
- }
- }
-
- addrs.map(addr => (addrType(addr), ipToLong(addr), addr)).toSeq.
- sortWith((l, r) => if (l._1 == r._1) l._2.compare(r._2) < 0 else l._1 < r._1).map(_._3)
- }
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/backend/app/agentSocket.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/agentSocket.js b/modules/web-console/backend/app/agentSocket.js
index 75dcd53..6e4518a 100644
--- a/modules/web-console/backend/app/agentSocket.js
+++ b/modules/web-console/backend/app/agentSocket.js
@@ -88,7 +88,7 @@ module.exports.factory = function(_) {
class AgentSocket {
/**
* @param {Socket} socket Socket for interaction.
- * @param {String} tokens Active tokens.
+ * @param {Array.<String>} tokens Agent tokens.
* @param {String} demoEnabled Demo enabled.
*/
constructor(socket, tokens, demoEnabled) {
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/backend/app/agentsHandler.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/agentsHandler.js b/modules/web-console/backend/app/agentsHandler.js
index 112793a..844ce1e 100644
--- a/modules/web-console/backend/app/agentsHandler.js
+++ b/modules/web-console/backend/app/agentsHandler.js
@@ -17,6 +17,8 @@
'use strict';
+const uuid = require('uuid/v4');
+
// Fire me up!
/**
@@ -82,19 +84,14 @@ module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo,
class Cluster {
constructor(top) {
- let d = new Date().getTime();
-
- this.id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
- const r = (d + Math.random() * 16) % 16 | 0;
-
- d = Math.floor(d / 16);
-
- return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
- });
+ const clusterName = top.clusterName;
+ this.id = _.isEmpty(clusterName) ? `Cluster ${uuid().substring(0, 8).toUpperCase()}` : clusterName;
this.nids = top.nids;
-
+ this.addresses = top.addresses;
+ this.clients = top.clients;
this.clusterVersion = top.clusterVersion;
+ this.active = top.active;
}
isSameCluster(top) {
@@ -103,8 +100,18 @@ module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo,
update(top) {
this.clusterVersion = top.clusterVersion;
-
this.nids = top.nids;
+ this.addresses = top.addresses;
+ this.clients = top.clients;
+ this.clusterVersion = top.clusterVersion;
+ this.active = top.active;
+ }
+
+ same(top) {
+ return _.difference(this.nids, top.nids).length === 0 &&
+ _.isEqual(this.addresses, top.addresses) &&
+ this.clusterVersion === top.clusterVersion &&
+ this.active === top.active;
}
}
@@ -192,10 +199,13 @@ module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo,
}
getOrCreateCluster(top) {
- const cluster = _.find(this.clusters, (c) => c.isSameCluster(top));
+ let cluster = _.find(this.clusters, (c) => c.isSameCluster(top));
+
+ if (_.isNil(cluster)) {
+ cluster = new Cluster(top);
- if (_.isNil(cluster))
- this.clusters.push(new Cluster(top));
+ this.clusters.push(cluster);
+ }
return cluster;
}
@@ -230,8 +240,17 @@ module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo,
this._browsersHnd.agentStats(token);
});
}
- else
- cluster.update(top);
+ else {
+ const changed = !cluster.same(top);
+
+ if (changed) {
+ cluster.update(top);
+
+ _.forEach(tokens, (token) => {
+ this._browsersHnd.clusterChanged(token, cluster);
+ });
+ }
+ }
});
sock.on('cluster:collector', (top) => {
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/backend/app/browsersHandler.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/browsersHandler.js b/modules/web-console/backend/app/browsersHandler.js
index 8b1385d..7ae247b 100644
--- a/modules/web-console/backend/app/browsersHandler.js
+++ b/modules/web-console/backend/app/browsersHandler.js
@@ -124,6 +124,12 @@ module.exports = {
.then((stat) => _.forEach(socks, (sock) => sock.emit('agents:stat', stat)));
}
+ clusterChanged(token, cluster) {
+ const socks = this._browserSockets.get(token);
+
+ _.forEach(socks, (sock) => sock.emit('cluster:changed', cluster));
+ }
+
emitNotification(sock) {
sock.emit('user:notifications', this.notification);
}
@@ -224,6 +230,7 @@ module.exports = {
this.registerVisorTask('queryClose', internalVisor('query.VisorQueryCleanupTask'), 'java.util.Map', 'java.util.UUID', 'java.util.Set');
this.registerVisorTask('queryCloseX2', internalVisor('query.VisorQueryCleanupTask'), internalVisor('query.VisorQueryCleanupTaskArg'));
+ this.registerVisorTask('toggleClusterState', internalVisor('misc.VisorChangeGridActiveStateTask'), internalVisor('misc.VisorChangeGridActiveStateTaskArg'));
// Return command result from grid to browser.
sock.on('node:visor', (clusterId, taskId, nids, ...args) => {
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/backend/app/mongo.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/mongo.js b/modules/web-console/backend/app/mongo.js
index 81076af..e0d0a0f 100644
--- a/modules/web-console/backend/app/mongo.js
+++ b/modules/web-console/backend/app/mongo.js
@@ -79,6 +79,7 @@ const defineSchema = (passportMongo, mongoose) => {
DUPLICATE_KEY_ERROR: 11000,
DUPLICATE_KEY_UPDATE_ERROR: 11001
};
+
// Define Account model.
result.Account = mongoose.model('Account', AccountSchema);
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/backend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/package.json b/modules/web-console/backend/package.json
index f0b2b5e..ba442f9 100644
--- a/modules/web-console/backend/package.json
+++ b/modules/web-console/backend/package.json
@@ -68,7 +68,8 @@
"passport-local": "1.0.0",
"passport-local-mongoose": "4.0.0",
"passport.socketio": "3.7.0",
- "socket.io": "1.7.3"
+ "socket.io": "1.7.3",
+ "uuid": "3.1.0"
},
"devDependencies": {
"chai": "4.1.0",
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/app.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.js
index ca678fc..f367d3e 100644
--- a/modules/web-console/frontend/app/app.js
+++ b/modules/web-console/frontend/app/app.js
@@ -22,7 +22,6 @@ import './app.config';
import './modules/form/form.module';
import './modules/agent/agent.module';
-import './modules/sql/sql.module';
import './modules/nodes/nodes.module';
import './modules/demo/Demo.module';
@@ -113,7 +112,6 @@ import resetPassword from './controllers/reset-password.controller';
// Components
import igniteListOfRegisteredUsers from './components/list-of-registered-users';
import IgniteActivitiesUserDialog from './components/activities-user-dialog';
-import clusterSelect from './components/cluster-select';
import './components/input-dialog';
import webConsoleHeader from './components/web-console-header';
import webConsoleFooter from './components/web-console-footer';
@@ -123,12 +121,14 @@ import userNotifications from './components/user-notifications';
import pageConfigure from './components/page-configure';
import pageConfigureBasic from './components/page-configure-basic';
import pageConfigureAdvanced from './components/page-configure-advanced';
+import pageQueries from './components/page-queries';
import gridColumnSelector from './components/grid-column-selector';
import gridItemSelected from './components/grid-item-selected';
import bsSelectMenu from './components/bs-select-menu';
import protectFromBsSelectRender from './components/protect-from-bs-select-render';
import uiGridHovering from './components/ui-grid-hovering';
import listEditable from './components/list-editable';
+import clusterSelector from './components/cluster-selector';
import igniteServices from './services';
@@ -168,7 +168,6 @@ angular.module('ignite-console', [
'ignite-console.branding',
'ignite-console.socket',
'ignite-console.agent',
- 'ignite-console.sql',
'ignite-console.nodes',
'ignite-console.demo',
// States.
@@ -197,6 +196,7 @@ angular.module('ignite-console', [
pageConfigure.name,
pageConfigureBasic.name,
pageConfigureAdvanced.name,
+ pageQueries.name,
gridColumnSelector.name,
gridItemSelected.name,
bsSelectMenu.name,
@@ -205,6 +205,7 @@ angular.module('ignite-console', [
AngularStrapTooltip.name,
AngularStrapSelect.name,
listEditable.name,
+ clusterSelector.name,
// Ignite modules.
IgniteModules.name
])
@@ -231,7 +232,6 @@ angular.module('ignite-console', [
.directive('igniteOnFocusOut', igniteOnFocusOut)
.directive('igniteRestoreInputFocus', igniteRestoreInputFocus)
.directive('igniteListOfRegisteredUsers', igniteListOfRegisteredUsers)
-.directive('igniteClusterSelect', clusterSelect)
.directive('btnIgniteLinkDashedSuccess', btnIgniteLink)
.directive('btnIgniteLinkDashedSecondary', btnIgniteLink)
// Services.
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/bs-select-menu/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/bs-select-menu/style.scss b/modules/web-console/frontend/app/components/bs-select-menu/style.scss
index 870b1bf..ccf33a3 100644
--- a/modules/web-console/frontend/app/components/bs-select-menu/style.scss
+++ b/modules/web-console/frontend/app/components/bs-select-menu/style.scss
@@ -88,7 +88,7 @@
}
& > li > .bssm-item-button__active {
- background-color: #eeeeee;
+ background-color: #e5f2f9;
}
}
-}
\ No newline at end of file
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-select/cluster-select.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-select/cluster-select.controller.js b/modules/web-console/frontend/app/components/cluster-select/cluster-select.controller.js
deleted file mode 100644
index a2d8e1e..0000000
--- a/modules/web-console/frontend/app/components/cluster-select/cluster-select.controller.js
+++ /dev/null
@@ -1,64 +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 {
- static $inject = ['AgentManager'];
-
- constructor(agentMgr) {
- const ctrl = this;
-
- ctrl.counter = 1;
-
- ctrl.cluster = null;
- ctrl.clusters = [];
-
- agentMgr.connectionSbj.subscribe({
- next: ({cluster, clusters}) => {
- if (_.isEmpty(clusters))
- return ctrl.clusters.length = 0;
-
- const removed = _.differenceBy(ctrl.clusters, clusters, 'id');
-
- if (_.nonEmpty(removed))
- _.pullAll(ctrl.clusters, removed);
-
- const added = _.differenceBy(clusters, ctrl.clusters, 'id');
-
- _.forEach(added, (cluster) => {
- ctrl.clusters.push({
- id: cluster.id,
- connected: true,
- click: () => {
- if (cluster.id === _.get(ctrl, 'cluster.id'))
- return;
-
- if (_.get(ctrl, 'cluster.connected')) {
- agentMgr.saveToStorage(cluster);
-
- window.open(window.location.href, '_blank');
- }
- else
- ctrl.cluster = _.find(ctrl.clusters, {id: cluster.id});
- }
- });
- });
-
- ctrl.cluster = cluster;
- }
- });
- }
-}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-select/cluster-select.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-select/cluster-select.pug b/modules/web-console/frontend/app/components/cluster-select/cluster-select.pug
deleted file mode 100644
index eb46e26..0000000
--- a/modules/web-console/frontend/app/components/cluster-select/cluster-select.pug
+++ /dev/null
@@ -1,47 +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.
-
--var clusterName = 'Cluster {{ ctrl.cluster.id | id8 }}'
-
-ul.nav
- li.disabled(ng-if='ctrl.clusters.length === 0')
- a(ng-if='!ctrl.cluster')
- i.icon-cluster
- label.padding-left-dflt(bs-tooltip='' data-placement='bottom' data-title='Check that Web Agent(s) started and connected to cluster(s)') No clusters available
- a(ng-if='ctrl.cluster')
- i.icon-danger
- label.padding-left-dflt(bs-tooltip='' data-placement='bottom' data-title='Connection to cluster was lost') #{clusterName}
-
- li(ng-if='ctrl.clusters.length === 1 && ctrl.cluster.connected')
- a
- i.icon-cluster
- label.padding-left-dflt #{clusterName}
-
- li(ng-if='ctrl.clusters.length > 1 || ctrl.clusters.length === 1 && !ctrl.cluster.connected')
- a.dropdown-toggle(bs-dropdown='' data-placement='bottom-left' data-trigger='hover focus' data-container='self' ng-click='$event.stopPropagation()' aria-haspopup='true' aria-expanded='expanded')
- i(ng-class='{"icon-cluster": ctrl.cluster.connected, "icon-danger": !ctrl.cluster.connected}')
- label.padding-left-dflt #{clusterName}
- span.caret
-
- ul.dropdown-menu(role='menu')
- li(ng-repeat='item in ctrl.clusters' ng-class='{active: ctrl.cluster === item}')
- div(ng-click='item.click()')
- i.icon-cluster.pull-left(style='margin: 0; padding-left: 10px;')
- div: a Cluster {{ item.id | id8 }}
-
-i.icon-help(bs-tooltip='' data-placement='bottom' data-html=true
- data-title='Multi-Cluster Support<br/>\
- <a href="https://apacheignite-tools.readme.io/docs/multi-cluster-support" target="_blank">More info</a>')
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-select/cluster-select.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-select/cluster-select.scss b/modules/web-console/frontend/app/components/cluster-select/cluster-select.scss
deleted file mode 100644
index 189ef50..0000000
--- a/modules/web-console/frontend/app/components/cluster-select/cluster-select.scss
+++ /dev/null
@@ -1,30 +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.
- */
-
-ignite-cluster-select {
- @import "./../../../public/stylesheets/variables.scss";
-
- display: flex;
- flex-direction: row;
- align-items: center;
-
- .icon-help {
- margin-left: 4px;
-
- color: $text-color;
- }
-}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-select/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-select/index.js b/modules/web-console/frontend/app/components/cluster-select/index.js
deleted file mode 100644
index 607b0db..0000000
--- a/modules/web-console/frontend/app/components/cluster-select/index.js
+++ /dev/null
@@ -1,29 +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 template from './cluster-select.pug';
-import './cluster-select.scss';
-import controller from './cluster-select.controller';
-
-export default [() => {
- return {
- restrict: 'E',
- template,
- controller,
- controllerAs: 'ctrl'
- };
-}];
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-selector/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-selector/component.js b/modules/web-console/frontend/app/components/cluster-selector/component.js
new file mode 100644
index 0000000..f6141d9
--- /dev/null
+++ b/modules/web-console/frontend/app/components/cluster-selector/component.js
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import template from './template.pug';
+import controller from './controller';
+import './style.scss';
+
+export default {
+ template,
+ controller
+};
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-selector/controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-selector/controller.js b/modules/web-console/frontend/app/components/cluster-selector/controller.js
new file mode 100644
index 0000000..6a86357
--- /dev/null
+++ b/modules/web-console/frontend/app/components/cluster-selector/controller.js
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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 {
+ static $inject = ['$scope', 'AgentManager', 'IgniteConfirm'];
+
+ constructor($scope, agentMgr, Confirm) {
+ Object.assign(this, { $scope, agentMgr, Confirm });
+
+ this.clusters = [];
+ this.isDemo = agentMgr.isDemoMode();
+ }
+
+ $onInit() {
+ this.clusters$ = this.agentMgr.connectionSbj
+ .do(({ cluster, clusters }) => {
+ this.cluster = cluster;
+ this.clusters = clusters;
+ })
+ .subscribe(() => {});
+ }
+
+ $onDestroy() {
+ this.clusters$.unsubscribe();
+ }
+
+ change() {
+ this.agentMgr.switchCluster(this.cluster);
+ }
+
+ toggle($event) {
+ $event.preventDefault();
+
+ const toggleClusterState = () => {
+ this.inProgress = true;
+
+ return this.agentMgr.toggleClusterState()
+ .finally(() => this.inProgress = false);
+ };
+
+ if (this.cluster.active) {
+ return this.Confirm.confirm('Are you sure you want to deactivate cluster?')
+ .then(() => toggleClusterState());
+ }
+
+ return toggleClusterState();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-selector/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-selector/index.js b/modules/web-console/frontend/app/components/cluster-selector/index.js
new file mode 100644
index 0000000..2bdbe44
--- /dev/null
+++ b/modules/web-console/frontend/app/components/cluster-selector/index.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.
+ */
+
+import angular from 'angular';
+import component from './component';
+
+export default angular
+ .module('ignite-console.cluster-selector', [])
+ .component('clusterSelector', component);
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-selector/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-selector/style.scss b/modules/web-console/frontend/app/components/cluster-selector/style.scss
new file mode 100644
index 0000000..966be99
--- /dev/null
+++ b/modules/web-console/frontend/app/components/cluster-selector/style.scss
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.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.
+ */
+
+cluster-selector {
+ @import "./../../../public/stylesheets/variables.scss";
+
+ position: relative;
+ top: 2px;
+
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ & > .btn-ignite {
+ border-radius: 9px;
+ min-height: 0;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 17px;
+ padding-top: 0;
+ padding-bottom: 0;
+
+ button {
+ font-weight: normal;
+ margin: 0 !important;
+ }
+ }
+
+ .cluster-selector--state {
+ width: 85px;
+ }
+
+ div {
+ margin: 0 10px 0 20px;
+ font-family: Roboto;
+ font-size: 12px;
+ }
+
+ div:last-child {
+ margin-left: 10px;
+ color: #EE2B27;
+ }
+
+ [ignite-icon='info'] {
+ margin-left: 7px;
+ color: $ignite-brand-success;
+ }
+
+ .bs-select-menu {
+ color: $text-color;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/cluster-selector/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/cluster-selector/template.pug b/modules/web-console/frontend/app/components/cluster-selector/template.pug
new file mode 100644
index 0000000..c97a698
--- /dev/null
+++ b/modules/web-console/frontend/app/components/cluster-selector/template.pug
@@ -0,0 +1,75 @@
+//-
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.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
+
+button.btn-ignite.btn-ignite--success(
+ data-ng-if='$ctrl.isDemo'
+)
+ | Demo cluster
+
+button.btn-ignite.btn-ignite--primary(
+ data-ng-if='!$ctrl.isDemo && $ctrl.clusters.length == 0'
+)
+ | No clusters available
+
+button.btn-ignite.btn-ignite--primary(
+ data-ng-if='!$ctrl.isDemo && $ctrl.clusters.length == 1'
+)
+ | {{ $ctrl.cluster.name }}
+
+div.btn-ignite.btn-ignite--primary(
+ data-ng-if='!$ctrl.isDemo && $ctrl.clusters.length > 1'
+
+ data-ng-model='$ctrl.cluster'
+
+ bs-select=''
+ bs-options='item as item.name for item in $ctrl.clusters'
+ data-trigger='hover focus'
+ data-container='self'
+
+ data-ng-change='$ctrl.change()'
+
+ protect-from-bs-select-render
+)
+ span(ng-if='!$ctrl.cluster') No clusters available
+ span(ng-if='$ctrl.cluster') {{ $ctrl.cluster.name }}
+ span.icon-right.fa.fa-caret-down
+
+svg(
+ ng-if='!$ctrl.isDemo'
+ ignite-icon='info'
+ bs-tooltip=''
+ data-title='Multi-Cluster Support<br/>\
+ <a href="https://apacheignite-tools.readme.io/docs/multi-cluster-support" target="_blank">More info</a>'
+ data-placement='bottom'
+)
+
+.cluster-selector--state(ng-if='!$ctrl.isDemo && $ctrl.cluster')
+ | Cluster {{ $ctrl.cluster.active ? 'active' : 'inactive' }}
+
++switcher()(
+ ng-if='!$ctrl.isDemo && $ctrl.cluster'
+ ng-click='$ctrl.toggle($event)'
+ ng-checked='$ctrl.cluster.active'
+ ng-disabled='$ctrl.inProgress'
+
+ tip='Toggle cluster active state'
+ is-in-progress='{{ $ctrl.inProgress }}'
+)
+
+div(ng-if='$ctrl.inProgress')
+ | {{ !$ctrl.cluster.active ? 'Activating...' : 'Deactivating...' }}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/list-editable/controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-editable/controller.js b/modules/web-console/frontend/app/components/list-editable/controller.js
index bc864ce..7757d96 100644
--- a/modules/web-console/frontend/app/components/list-editable/controller.js
+++ b/modules/web-console/frontend/app/components/list-editable/controller.js
@@ -21,7 +21,7 @@ export default class {
static $inject = ['$animate', '$element', '$transclude'];
constructor($animate, $element, $transclude) {
- $animate.enabled(false, $element);
+ $animate.enabled($element, false);
this.hasItemView = $transclude.isSlotFilled('itemView');
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/components/page-queries/Notebook.data.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-queries/Notebook.data.js b/modules/web-console/frontend/app/components/page-queries/Notebook.data.js
new file mode 100644
index 0000000..3f98bed
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-queries/Notebook.data.js
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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/components/page-queries/Notebook.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-queries/Notebook.service.js b/modules/web-console/frontend/app/components/page-queries/Notebook.service.js
new file mode 100644
index 0000000..b0bb64f
--- /dev/null
+++ b/modules/web-console/frontend/app/components/page-queries/Notebook.service.js
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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);
+ });
+ }
+}
[2/5] ignite git commit: IGNITE-6390 Web Console: Added component for
cluster selection.
Posted by ak...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/modules/sql/sql.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/sql.controller.js b/modules/web-console/frontend/app/modules/sql/sql.controller.js
deleted file mode 100644
index a2ad912..0000000
--- a/modules/web-console/frontend/app/modules/sql/sql.controller.js
+++ /dev/null
@@ -1,1887 +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 paragraphRateTemplateUrl from 'views/sql/paragraph-rate.tpl.pug';
-import cacheMetadataTemplateUrl from 'views/sql/cache-metadata.tpl.pug';
-import chartSettingsTemplateUrl from 'views/sql/chart-settings.tpl.pug';
-import messageTemplateUrl from 'views/templates/message.tpl.pug';
-
-// Time line X axis descriptor.
-const TIME_LINE = {value: -1, type: 'java.sql.Date', label: 'TIME_LINE'};
-
-// Row index X axis descriptor.
-const ROW_IDX = {value: -2, type: 'java.lang.Integer', label: 'ROW_IDX'};
-
-const NON_COLLOCATED_JOINS_SINCE = '1.7.0';
-
-const ENFORCE_JOIN_SINCE = [['1.7.9', '1.8.0'], ['1.8.4', '1.9.0'], '1.9.1'];
-
-const LAZY_QUERY_SINCE = [['2.1.4-p1', '2.2.0'], '2.2.1'];
-
-const DDL_SINCE = [['2.1.6', '2.2.0'], '2.3.0'];
-
-const _fullColName = (col) => {
- const res = [];
-
- if (col.schemaName)
- res.push(col.schemaName);
-
- if (col.typeName)
- res.push(col.typeName);
-
- res.push(col.fieldName);
-
- return res.join('.');
-};
-
-let paragraphId = 0;
-
-class Paragraph {
- constructor($animate, $timeout, JavaTypes, paragraph) {
- const self = this;
-
- self.id = 'paragraph-' + paragraphId++;
- self.qryType = paragraph.qryType || 'query';
- self.maxPages = 0;
- self.filter = '';
- self.useAsDefaultSchema = false;
- self.localQueryMode = false;
- self.csvIsPreparing = false;
- self.scanningInProgress = false;
-
- _.assign(this, paragraph);
-
- Object.defineProperty(this, 'gridOptions', {value: {
- enableGridMenu: false,
- enableColumnMenus: false,
- flatEntityAccess: true,
- fastWatch: true,
- categories: [],
- rebuildColumns() {
- if (_.isNil(this.api))
- return;
-
- this.categories.length = 0;
-
- this.columnDefs = _.reduce(self.meta, (cols, col, idx) => {
- cols.push({
- displayName: col.fieldName,
- headerTooltip: _fullColName(col),
- field: idx.toString(),
- minWidth: 50,
- cellClass: 'cell-left',
- visible: self.columnFilter(col)
- });
-
- this.categories.push({
- name: col.fieldName,
- visible: self.columnFilter(col),
- enableHiding: true
- });
-
- return cols;
- }, []);
-
- $timeout(() => this.api.core.notifyDataChange('column'));
- },
- adjustHeight() {
- if (_.isNil(this.api))
- return;
-
- this.data = self.rows;
-
- const height = Math.min(self.rows.length, 15) * 30 + 47;
-
- // Remove header height.
- this.api.grid.element.css('height', height + 'px');
-
- $timeout(() => this.api.core.handleWindowResize());
- },
- onRegisterApi(api) {
- $animate.enabled(api.grid.element, false);
-
- this.api = api;
-
- this.rebuildColumns();
-
- this.adjustHeight();
- }
- }});
-
- Object.defineProperty(this, 'chartHistory', {value: []});
-
- Object.defineProperty(this, 'error', {value: {
- root: {},
- message: ''
- }});
-
- this.setError = (err) => {
- this.error.root = err;
- this.error.message = err.message;
-
- let cause = err;
-
- while (_.nonNil(cause)) {
- if (_.nonEmpty(cause.className) &&
- _.includes(['SQLException', 'JdbcSQLException', 'QueryCancelledException'], JavaTypes.shortClassName(cause.className))) {
- this.error.message = cause.message || cause.className;
-
- break;
- }
-
- cause = cause.cause;
- }
-
- if (_.isEmpty(this.error.message) && _.nonEmpty(err.className)) {
- this.error.message = 'Internal cluster error';
-
- if (_.nonEmpty(err.className))
- this.error.message += ': ' + err.className;
- }
- };
- }
-
- resultType() {
- if (_.isNil(this.queryArgs))
- return null;
-
- if (_.nonEmpty(this.error.message))
- return 'error';
-
- if (_.isEmpty(this.rows))
- return 'empty';
-
- return this.result === 'table' ? 'table' : 'chart';
- }
-
- nonRefresh() {
- return _.isNil(this.rate) || _.isNil(this.rate.stopTime);
- }
-
- table() {
- return this.result === 'table';
- }
-
- chart() {
- return this.result !== 'table' && this.result !== 'none';
- }
-
- nonEmpty() {
- return this.rows && this.rows.length > 0;
- }
-
- queryExecuted() {
- return _.nonEmpty(this.meta) || _.nonEmpty(this.error.message);
- }
-
- scanExplain() {
- return this.queryExecuted() && this.queryArgs.type !== 'QUERY';
- }
-
- timeLineSupported() {
- return this.result !== 'pie';
- }
-
- chartColumnsConfigured() {
- return _.nonEmpty(this.chartKeyCols) && _.nonEmpty(this.chartValCols);
- }
-
- chartTimeLineEnabled() {
- return _.nonEmpty(this.chartKeyCols) && _.eq(this.chartKeyCols[0], TIME_LINE);
- }
-
- executionInProgress(showLocal = false) {
- return this.loading && (this.localQueryMode === showLocal);
- }
-
- checkScanInProgress(showLocal = false) {
- return this.scanningInProgress && (this.localQueryMode === showLocal);
- }
-}
-
-// Controller for SQL notebook screen.
-export default ['$rootScope', '$scope', '$http', '$q', '$timeout', '$interval', '$animate', '$location', '$anchorScroll', '$state', '$filter', '$modal', '$popover', 'IgniteLoading', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteConfirm', 'AgentManager', 'IgniteChartColors', 'IgniteNotebook', 'IgniteNodes', 'uiGridExporterConstants', 'IgniteVersion', 'IgniteActivitiesData', 'JavaTypes', 'IgniteCopyToClipboard',
- function($root, $scope, $http, $q, $timeout, $interval, $animate, $location, $anchorScroll, $state, $filter, $modal, $popover, Loading, LegacyUtils, Messages, Confirm, agentMgr, IgniteChartColors, Notebook, Nodes, uiGridExporterConstants, Version, ActivitiesData, JavaTypes, IgniteCopyToClipboard) {
- const $ctrl = this;
-
- // Define template urls.
- $ctrl.paragraphRateTemplateUrl = paragraphRateTemplateUrl;
- $ctrl.cacheMetadataTemplateUrl = cacheMetadataTemplateUrl;
- $ctrl.chartSettingsTemplateUrl = chartSettingsTemplateUrl;
-
- $ctrl.demoStarted = false;
-
- let stopTopology = null;
-
- const _tryStopRefresh = function(paragraph) {
- if (paragraph.rate && paragraph.rate.stopTime) {
- $interval.cancel(paragraph.rate.stopTime);
-
- delete paragraph.rate.stopTime;
- }
- };
-
- const _stopTopologyRefresh = () => {
- $interval.cancel(stopTopology);
-
- if ($scope.notebook && $scope.notebook.paragraphs)
- $scope.notebook.paragraphs.forEach((paragraph) => _tryStopRefresh(paragraph));
- };
-
- $scope.$on('$stateChangeStart', _stopTopologyRefresh);
-
- $scope.caches = [];
-
- $scope.pageSizes = [50, 100, 200, 400, 800, 1000];
- $scope.maxPages = [
- {label: 'Unlimited', value: 0},
- {label: '1', value: 1},
- {label: '5', value: 5},
- {label: '10', value: 10},
- {label: '20', value: 20},
- {label: '50', value: 50},
- {label: '100', value: 100}
- ];
-
- $scope.timeLineSpans = ['1', '5', '10', '15', '30'];
-
- $scope.aggregateFxs = ['FIRST', 'LAST', 'MIN', 'MAX', 'SUM', 'AVG', 'COUNT'];
-
- $scope.modes = LegacyUtils.mkOptions(['PARTITIONED', 'REPLICATED', 'LOCAL']);
-
- $scope.loadingText = $root.IgniteDemoMode ? 'Demo grid is starting. Please wait...' : 'Loading query notebook screen...';
-
- $scope.timeUnit = [
- {value: 1000, label: 'seconds', short: 's'},
- {value: 60000, label: 'minutes', short: 'm'},
- {value: 3600000, label: 'hours', short: 'h'}
- ];
-
- $scope.metadata = [];
-
- $scope.metaFilter = '';
-
- $scope.metaOptions = {
- nodeChildren: 'children',
- dirSelectable: true,
- injectClasses: {
- iExpanded: 'fa fa-minus-square-o',
- iCollapsed: 'fa fa-plus-square-o'
- }
- };
-
- const maskCacheName = $filter('defaultName');
-
- // We need max 1800 items to hold history for 30 mins in case of refresh every second.
- const HISTORY_LENGTH = 1800;
-
- const MAX_VAL_COLS = IgniteChartColors.length;
-
- $anchorScroll.yOffset = 55;
-
- $scope.chartColor = function(index) {
- return {color: 'white', 'background-color': IgniteChartColors[index]};
- };
-
- function _chartNumber(arr, idx, dflt) {
- if (idx >= 0 && arr && arr.length > idx && _.isNumber(arr[idx]))
- return arr[idx];
-
- return dflt;
- }
-
- function _min(rows, idx, dflt) {
- let min = _chartNumber(rows[0], idx, dflt);
-
- _.forEach(rows, (row) => {
- const v = _chartNumber(row, idx, dflt);
-
- if (v < min)
- min = v;
- });
-
- return min;
- }
-
- function _max(rows, idx, dflt) {
- let max = _chartNumber(rows[0], idx, dflt);
-
- _.forEach(rows, (row) => {
- const v = _chartNumber(row, idx, dflt);
-
- if (v > max)
- max = v;
- });
-
- return max;
- }
-
- function _sum(rows, idx) {
- let sum = 0;
-
- _.forEach(rows, (row) => sum += _chartNumber(row, idx, 0));
-
- return sum;
- }
-
- function _aggregate(rows, aggFx, idx, dflt) {
- const len = rows.length;
-
- switch (aggFx) {
- case 'FIRST':
- return _chartNumber(rows[0], idx, dflt);
-
- case 'LAST':
- return _chartNumber(rows[len - 1], idx, dflt);
-
- case 'MIN':
- return _min(rows, idx, dflt);
-
- case 'MAX':
- return _max(rows, idx, dflt);
-
- case 'SUM':
- return _sum(rows, idx);
-
- case 'AVG':
- return len > 0 ? _sum(rows, idx) / len : 0;
-
- case 'COUNT':
- return len;
-
- default:
- }
-
- return 0;
- }
-
- function _chartLabel(arr, idx, dflt) {
- if (arr && arr.length > idx && _.isString(arr[idx]))
- return arr[idx];
-
- return dflt;
- }
-
- function _chartDatum(paragraph) {
- let datum = [];
-
- if (paragraph.chartColumnsConfigured()) {
- paragraph.chartValCols.forEach(function(valCol) {
- let index = 0;
- let values = [];
- const colIdx = valCol.value;
-
- if (paragraph.chartTimeLineEnabled()) {
- const aggFx = valCol.aggFx;
- const colLbl = valCol.label + ' [' + aggFx + ']';
-
- if (paragraph.charts && paragraph.charts.length === 1)
- datum = paragraph.charts[0].data;
-
- const chartData = _.find(datum, {series: valCol.label});
-
- const leftBound = new Date();
- leftBound.setMinutes(leftBound.getMinutes() - parseInt(paragraph.timeLineSpan, 10));
-
- if (chartData) {
- const lastItem = _.last(paragraph.chartHistory);
-
- values = chartData.values;
-
- values.push({
- x: lastItem.tm,
- y: _aggregate(lastItem.rows, aggFx, colIdx, index++)
- });
-
- while (values.length > 0 && values[0].x < leftBound)
- values.shift();
- }
- else {
- _.forEach(paragraph.chartHistory, (history) => {
- if (history.tm >= leftBound) {
- values.push({
- x: history.tm,
- y: _aggregate(history.rows, aggFx, colIdx, index++)
- });
- }
- });
-
- datum.push({series: valCol.label, key: colLbl, values});
- }
- }
- else {
- index = paragraph.total;
-
- values = _.map(paragraph.rows, function(row) {
- const xCol = paragraph.chartKeyCols[0].value;
-
- const v = {
- x: _chartNumber(row, xCol, index),
- xLbl: _chartLabel(row, xCol, null),
- y: _chartNumber(row, colIdx, index)
- };
-
- index++;
-
- return v;
- });
-
- datum.push({series: valCol.label, key: valCol.label, values});
- }
- });
- }
-
- return datum;
- }
-
- function _xX(d) {
- return d.x;
- }
-
- function _yY(d) {
- return d.y;
- }
-
- function _xAxisTimeFormat(d) {
- return d3.time.format('%X')(new Date(d));
- }
-
- const _intClasses = ['java.lang.Byte', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
-
- function _intType(cls) {
- return _.includes(_intClasses, cls);
- }
-
- const _xAxisWithLabelFormat = function(paragraph) {
- return function(d) {
- const values = paragraph.charts[0].data[0].values;
-
- const fmt = _intType(paragraph.chartKeyCols[0].type) ? 'd' : ',.2f';
-
- const dx = values[d];
-
- if (!dx)
- return d3.format(fmt)(d);
-
- const lbl = dx.xLbl;
-
- return lbl ? lbl : d3.format(fmt)(d);
- };
- };
-
- function _xAxisLabel(paragraph) {
- return _.isEmpty(paragraph.chartKeyCols) ? 'X' : paragraph.chartKeyCols[0].label;
- }
-
- const _yAxisFormat = function(d) {
- const fmt = d < 1000 ? ',.2f' : '.3s';
-
- return d3.format(fmt)(d);
- };
-
- function _updateCharts(paragraph) {
- $timeout(() => _.forEach(paragraph.charts, (chart) => chart.api.update()), 100);
- }
-
- function _updateChartsWithData(paragraph, newDatum) {
- $timeout(() => {
- if (!paragraph.chartTimeLineEnabled()) {
- const chartDatum = paragraph.charts[0].data;
-
- chartDatum.length = 0;
-
- _.forEach(newDatum, (series) => chartDatum.push(series));
- }
-
- paragraph.charts[0].api.update();
- });
- }
-
- function _yAxisLabel(paragraph) {
- const cols = paragraph.chartValCols;
-
- const tml = paragraph.chartTimeLineEnabled();
-
- return _.isEmpty(cols) ? 'Y' : _.map(cols, function(col) {
- let lbl = col.label;
-
- if (tml)
- lbl += ' [' + col.aggFx + ']';
-
- return lbl;
- }).join(', ');
- }
-
- function _barChart(paragraph) {
- const datum = _chartDatum(paragraph);
-
- if (_.isEmpty(paragraph.charts)) {
- const stacked = paragraph.chartsOptions && paragraph.chartsOptions.barChart
- ? paragraph.chartsOptions.barChart.stacked
- : true;
-
- const options = {
- chart: {
- type: 'multiBarChart',
- height: 400,
- margin: {left: 70},
- duration: 0,
- x: _xX,
- y: _yY,
- xAxis: {
- axisLabel: _xAxisLabel(paragraph),
- tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
- showMaxMin: false
- },
- yAxis: {
- axisLabel: _yAxisLabel(paragraph),
- tickFormat: _yAxisFormat
- },
- color: IgniteChartColors,
- stacked,
- showControls: true,
- legend: {
- vers: 'furious',
- margin: {right: -25}
- }
- }
- };
-
- paragraph.charts = [{options, data: datum}];
-
- _updateCharts(paragraph);
- }
- else
- _updateChartsWithData(paragraph, datum);
- }
-
- function _pieChartDatum(paragraph) {
- const datum = [];
-
- if (paragraph.chartColumnsConfigured() && !paragraph.chartTimeLineEnabled()) {
- paragraph.chartValCols.forEach(function(valCol) {
- let index = paragraph.total;
-
- const values = _.map(paragraph.rows, (row) => {
- const xCol = paragraph.chartKeyCols[0].value;
-
- const v = {
- x: xCol < 0 ? index : row[xCol],
- y: _chartNumber(row, valCol.value, index)
- };
-
- // Workaround for known problem with zero values on Pie chart.
- if (v.y === 0)
- v.y = 0.0001;
-
- index++;
-
- return v;
- });
-
- datum.push({series: paragraph.chartKeyCols[0].label, key: valCol.label, values});
- });
- }
-
- return datum;
- }
-
- function _pieChart(paragraph) {
- let datum = _pieChartDatum(paragraph);
-
- if (datum.length === 0)
- datum = [{values: []}];
-
- paragraph.charts = _.map(datum, function(data) {
- return {
- options: {
- chart: {
- type: 'pieChart',
- height: 400,
- duration: 0,
- x: _xX,
- y: _yY,
- showLabels: true,
- labelThreshold: 0.05,
- labelType: 'percent',
- donut: true,
- donutRatio: 0.35,
- legend: {
- vers: 'furious',
- margin: {
- right: -25
- }
- }
- },
- title: {
- enable: true,
- text: data.key
- }
- },
- data: data.values
- };
- });
-
- _updateCharts(paragraph);
- }
-
- function _lineChart(paragraph) {
- const datum = _chartDatum(paragraph);
-
- if (_.isEmpty(paragraph.charts)) {
- const options = {
- chart: {
- type: 'lineChart',
- height: 400,
- margin: { left: 70 },
- duration: 0,
- x: _xX,
- y: _yY,
- xAxis: {
- axisLabel: _xAxisLabel(paragraph),
- tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
- showMaxMin: false
- },
- yAxis: {
- axisLabel: _yAxisLabel(paragraph),
- tickFormat: _yAxisFormat
- },
- color: IgniteChartColors,
- useInteractiveGuideline: true,
- legend: {
- vers: 'furious',
- margin: {
- right: -25
- }
- }
- }
- };
-
- paragraph.charts = [{options, data: datum}];
-
- _updateCharts(paragraph);
- }
- else
- _updateChartsWithData(paragraph, datum);
- }
-
- function _areaChart(paragraph) {
- const datum = _chartDatum(paragraph);
-
- if (_.isEmpty(paragraph.charts)) {
- const style = paragraph.chartsOptions && paragraph.chartsOptions.areaChart
- ? paragraph.chartsOptions.areaChart.style
- : 'stack';
-
- const options = {
- chart: {
- type: 'stackedAreaChart',
- height: 400,
- margin: {left: 70},
- duration: 0,
- x: _xX,
- y: _yY,
- xAxis: {
- axisLabel: _xAxisLabel(paragraph),
- tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(paragraph),
- showMaxMin: false
- },
- yAxis: {
- axisLabel: _yAxisLabel(paragraph),
- tickFormat: _yAxisFormat
- },
- color: IgniteChartColors,
- style,
- legend: {
- vers: 'furious',
- margin: {right: -25}
- }
- }
- };
-
- paragraph.charts = [{options, data: datum}];
-
- _updateCharts(paragraph);
- }
- else
- _updateChartsWithData(paragraph, datum);
- }
-
- function _chartApplySettings(paragraph, resetCharts) {
- if (resetCharts)
- paragraph.charts = [];
-
- if (paragraph.chart() && paragraph.nonEmpty()) {
- switch (paragraph.result) {
- case 'bar':
- _barChart(paragraph);
- break;
-
- case 'pie':
- _pieChart(paragraph);
- break;
-
- case 'line':
- _lineChart(paragraph);
- break;
-
- case 'area':
- _areaChart(paragraph);
- break;
-
- default:
- }
- }
- }
-
- $scope.chartRemoveKeyColumn = function(paragraph, index) {
- paragraph.chartKeyCols.splice(index, 1);
-
- _chartApplySettings(paragraph, true);
- };
-
- $scope.chartRemoveValColumn = function(paragraph, index) {
- paragraph.chartValCols.splice(index, 1);
-
- _chartApplySettings(paragraph, true);
- };
-
- $scope.chartAcceptKeyColumn = function(paragraph, item) {
- const accepted = _.findIndex(paragraph.chartKeyCols, item) < 0;
-
- if (accepted) {
- paragraph.chartKeyCols = [item];
-
- _chartApplySettings(paragraph, true);
- }
-
- return false;
- };
-
- const _numberClasses = ['java.math.BigDecimal', 'java.lang.Byte', 'java.lang.Double',
- 'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short'];
-
- const _numberType = function(cls) {
- return _.includes(_numberClasses, cls);
- };
-
- $scope.chartAcceptValColumn = function(paragraph, item) {
- const valCols = paragraph.chartValCols;
-
- const accepted = _.findIndex(valCols, item) < 0 && item.value >= 0 && _numberType(item.type);
-
- if (accepted) {
- if (valCols.length === MAX_VAL_COLS - 1)
- valCols.shift();
-
- valCols.push(item);
-
- _chartApplySettings(paragraph, true);
- }
-
- return false;
- };
-
- $scope.scrollParagraphs = [];
-
- $scope.rebuildScrollParagraphs = function() {
- $scope.scrollParagraphs = $scope.notebook.paragraphs.map(function(paragraph) {
- return {
- text: paragraph.name,
- click: 'scrollToParagraph("' + paragraph.id + '")'
- };
- });
- };
-
- $scope.scrollToParagraph = (id) => {
- const idx = _.findIndex($scope.notebook.paragraphs, {id});
-
- if (idx >= 0) {
- if (!_.includes($scope.notebook.expandedParagraphs, idx))
- $scope.notebook.expandedParagraphs = $scope.notebook.expandedParagraphs.concat([idx]);
-
- if ($scope.notebook.paragraphs[idx].ace)
- setTimeout(() => $scope.notebook.paragraphs[idx].ace.focus());
- }
-
- $location.hash(id);
-
- $anchorScroll();
- };
-
- const _hideColumn = (col) => col.fieldName !== '_KEY' && col.fieldName !== '_VAL';
-
- const _allColumn = () => true;
-
- $scope.aceInit = function(paragraph) {
- return function(editor) {
- editor.setAutoScrollEditorIntoView(true);
- editor.$blockScrolling = Infinity;
-
- const renderer = editor.renderer;
-
- renderer.setHighlightGutterLine(false);
- renderer.setShowPrintMargin(false);
- renderer.setOption('fontFamily', 'monospace');
- renderer.setOption('fontSize', '14px');
- renderer.setOption('minLines', '5');
- renderer.setOption('maxLines', '15');
-
- editor.setTheme('ace/theme/chrome');
-
- Object.defineProperty(paragraph, 'ace', { value: editor });
- };
- };
-
- /**
- * Update caches list.
- */
- const _refreshFn = () =>
- agentMgr.topology(true)
- .then((nodes) => {
- $scope.caches = _.sortBy(_.reduce(nodes, (cachesAcc, node) => {
- _.forEach(node.caches, (cache) => {
- let item = _.find(cachesAcc, {name: cache.name});
-
- if (_.isNil(item)) {
- cache.label = maskCacheName(cache.name, true);
- cache.value = cache.name;
-
- cache.nodes = [];
-
- cachesAcc.push(item = cache);
- }
-
- item.nodes.push({
- nid: node.nodeId.toUpperCase(),
- ip: _.head(node.attributes['org.apache.ignite.ips'].split(', ')),
- version: node.attributes['org.apache.ignite.build.ver'],
- gridName: node.attributes['org.apache.ignite.ignite.name'],
- os: `${node.attributes['os.name']} ${node.attributes['os.arch']} ${node.attributes['os.version']}`,
- client: node.attributes['org.apache.ignite.cache.client']
- });
- });
-
- return cachesAcc;
- }, []), (cache) => cache.label.toLowerCase());
-
- // Reset to first cache in case of stopped selected.
- const cacheNames = _.map($scope.caches, (cache) => cache.value);
-
- _.forEach($scope.notebook.paragraphs, (paragraph) => {
- if (!_.includes(cacheNames, paragraph.cacheName))
- paragraph.cacheName = _.head(cacheNames);
- });
-
- // Await for demo caches.
- if (!$ctrl.demoStarted && $root.IgniteDemoMode && _.nonEmpty(cacheNames)) {
- $ctrl.demoStarted = true;
-
- Loading.finish('sqlLoading');
-
- _.forEach($scope.notebook.paragraphs, (paragraph) => $scope.execute(paragraph));
- }
- })
- .catch((err) => Messages.showError(err));
-
- const _startWatch = () =>
- agentMgr.startClusterWatch('Back to Configuration', 'base.configuration.tabs.advanced.clusters')
- .then(() => Loading.start('sqlLoading'))
- .then(_refreshFn)
- .then(() => {
- if (!$root.IgniteDemoMode)
- Loading.finish('sqlLoading');
- })
- .then(() => {
- stopTopology = $interval(_refreshFn, 5000, 0, false);
- });
-
- Notebook.find($state.params.noteId)
- .then((notebook) => {
- $scope.notebook = _.cloneDeep(notebook);
-
- $scope.notebook_name = $scope.notebook.name;
-
- if (!$scope.notebook.expandedParagraphs)
- $scope.notebook.expandedParagraphs = [];
-
- if (!$scope.notebook.paragraphs)
- $scope.notebook.paragraphs = [];
-
- $scope.notebook.paragraphs = _.map($scope.notebook.paragraphs,
- (paragraph) => new Paragraph($animate, $timeout, JavaTypes, paragraph));
-
- if (_.isEmpty($scope.notebook.paragraphs))
- $scope.addQuery();
- else
- $scope.rebuildScrollParagraphs();
- })
- .then(_startWatch)
- .catch(() => {
- $scope.notebookLoadFailed = true;
-
- Loading.finish('sqlLoading');
- });
-
- $scope.renameNotebook = (name) => {
- if (!name)
- return;
-
- if ($scope.notebook.name !== name) {
- const prevName = $scope.notebook.name;
-
- $scope.notebook.name = name;
-
- Notebook.save($scope.notebook)
- .then(() => $scope.notebook.edit = false)
- .catch((err) => {
- $scope.notebook.name = prevName;
-
- Messages.showError(err);
- });
- }
- else
- $scope.notebook.edit = false;
- };
-
- $scope.removeNotebook = (notebook) => Notebook.remove(notebook);
-
- $scope.renameParagraph = function(paragraph, newName) {
- if (!newName)
- return;
-
- if (paragraph.name !== newName) {
- paragraph.name = newName;
-
- $scope.rebuildScrollParagraphs();
-
- Notebook.save($scope.notebook)
- .then(() => paragraph.edit = false)
- .catch(Messages.showError);
- }
- else
- paragraph.edit = false;
- };
-
- $scope.addParagraph = (paragraph, sz) => {
- if ($scope.caches && $scope.caches.length > 0)
- paragraph.cacheName = _.head($scope.caches).value;
-
- $scope.notebook.paragraphs.push(paragraph);
-
- $scope.notebook.expandedParagraphs.push(sz);
-
- $scope.rebuildScrollParagraphs();
-
- $location.hash(paragraph.id);
- };
-
- $scope.addQuery = function() {
- const sz = $scope.notebook.paragraphs.length;
-
- ActivitiesData.post({ action: '/queries/add/query' });
-
- const paragraph = new Paragraph($animate, $timeout, JavaTypes, {
- name: 'Query' + (sz === 0 ? '' : sz),
- query: '',
- pageSize: $scope.pageSizes[1],
- timeLineSpan: $scope.timeLineSpans[0],
- result: 'none',
- rate: {
- value: 1,
- unit: 60000,
- installed: false
- },
- qryType: 'query'
- });
-
- $scope.addParagraph(paragraph, sz);
-
- $timeout(() => {
- $anchorScroll();
-
- paragraph.ace.focus();
- });
- };
-
- $scope.addScan = function() {
- const sz = $scope.notebook.paragraphs.length;
-
- ActivitiesData.post({ action: '/queries/add/scan' });
-
- const paragraph = new Paragraph($animate, $timeout, JavaTypes, {
- name: 'Scan' + (sz === 0 ? '' : sz),
- query: '',
- pageSize: $scope.pageSizes[1],
- timeLineSpan: $scope.timeLineSpans[0],
- result: 'none',
- rate: {
- value: 1,
- unit: 60000,
- installed: false
- },
- qryType: 'scan'
- });
-
- $scope.addParagraph(paragraph, sz);
- };
-
- function _saveChartSettings(paragraph) {
- if (!_.isEmpty(paragraph.charts)) {
- const chart = paragraph.charts[0].api.getScope().chart;
-
- if (!LegacyUtils.isDefined(paragraph.chartsOptions))
- paragraph.chartsOptions = {barChart: {stacked: true}, areaChart: {style: 'stack'}};
-
- switch (paragraph.result) {
- case 'bar':
- paragraph.chartsOptions.barChart.stacked = chart.stacked();
-
- break;
-
- case 'area':
- paragraph.chartsOptions.areaChart.style = chart.style();
-
- break;
-
- default:
- }
- }
- }
-
- $scope.setResult = function(paragraph, new_result) {
- if (paragraph.result === new_result)
- return;
-
- _saveChartSettings(paragraph);
-
- paragraph.result = new_result;
-
- if (paragraph.chart())
- _chartApplySettings(paragraph, true);
- };
-
- $scope.resultEq = function(paragraph, result) {
- return (paragraph.result === result);
- };
-
- $scope.removeParagraph = function(paragraph) {
- Confirm.confirm('Are you sure you want to remove query: "' + paragraph.name + '"?')
- .then(function() {
- $scope.stopRefresh(paragraph);
-
- const paragraph_idx = _.findIndex($scope.notebook.paragraphs, function(item) {
- return paragraph === item;
- });
-
- const panel_idx = _.findIndex($scope.expandedParagraphs, function(item) {
- return paragraph_idx === item;
- });
-
- if (panel_idx >= 0)
- $scope.expandedParagraphs.splice(panel_idx, 1);
-
- $scope.notebook.paragraphs.splice(paragraph_idx, 1);
-
- $scope.rebuildScrollParagraphs();
-
- Notebook.save($scope.notebook)
- .catch(Messages.showError);
- });
- };
-
- $scope.paragraphExpanded = function(paragraph) {
- const paragraph_idx = _.findIndex($scope.notebook.paragraphs, function(item) {
- return paragraph === item;
- });
-
- const panel_idx = _.findIndex($scope.notebook.expandedParagraphs, function(item) {
- return paragraph_idx === item;
- });
-
- return panel_idx >= 0;
- };
-
- const _columnFilter = function(paragraph) {
- return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn;
- };
-
- const _notObjectType = function(cls) {
- return LegacyUtils.isJavaBuiltInClass(cls);
- };
-
- function _retainColumns(allCols, curCols, acceptableType, xAxis, unwantedCols) {
- const retainedCols = [];
-
- const availableCols = xAxis ? allCols : _.filter(allCols, function(col) {
- return col.value >= 0;
- });
-
- if (availableCols.length > 0) {
- curCols.forEach(function(curCol) {
- const col = _.find(availableCols, {label: curCol.label});
-
- if (col && acceptableType(col.type)) {
- col.aggFx = curCol.aggFx;
-
- retainedCols.push(col);
- }
- });
-
- // If nothing was restored, add first acceptable column.
- if (_.isEmpty(retainedCols)) {
- let col;
-
- if (unwantedCols)
- col = _.find(availableCols, (avCol) => !_.find(unwantedCols, {label: avCol.label}) && acceptableType(avCol.type));
-
- if (!col)
- col = _.find(availableCols, (avCol) => acceptableType(avCol.type));
-
- if (col)
- retainedCols.push(col);
- }
- }
-
- return retainedCols;
- }
-
- const _rebuildColumns = function(paragraph) {
- _.forEach(_.groupBy(paragraph.meta, 'fieldName'), function(colsByName, fieldName) {
- const colsByTypes = _.groupBy(colsByName, 'typeName');
-
- const needType = _.keys(colsByTypes).length > 1;
-
- _.forEach(colsByTypes, function(colsByType, typeName) {
- _.forEach(colsByType, function(col, ix) {
- col.fieldName = (needType && !LegacyUtils.isEmptyString(typeName) ? typeName + '.' : '') + fieldName + (ix > 0 ? ix : '');
- });
- });
- });
-
- paragraph.gridOptions.rebuildColumns();
-
- paragraph.chartColumns = _.reduce(paragraph.meta, (acc, col, idx) => {
- if (_notObjectType(col.fieldTypeName)) {
- acc.push({
- label: col.fieldName,
- type: col.fieldTypeName,
- aggFx: $scope.aggregateFxs[0],
- value: idx.toString()
- });
- }
-
- return acc;
- }, []);
-
- if (paragraph.chartColumns.length > 0) {
- paragraph.chartColumns.push(TIME_LINE);
- paragraph.chartColumns.push(ROW_IDX);
- }
-
- // We could accept onl not object columns for X axis.
- paragraph.chartKeyCols = _retainColumns(paragraph.chartColumns, paragraph.chartKeyCols, _notObjectType, true);
-
- // We could accept only numeric columns for Y axis.
- paragraph.chartValCols = _retainColumns(paragraph.chartColumns, paragraph.chartValCols, _numberType, false, paragraph.chartKeyCols);
- };
-
- $scope.toggleSystemColumns = function(paragraph) {
- if (paragraph.disabledSystemColumns)
- return;
-
- paragraph.columnFilter = _columnFilter(paragraph);
-
- paragraph.chartColumns = [];
-
- _rebuildColumns(paragraph);
- };
-
- const _showLoading = (paragraph, enable) => paragraph.loading = enable;
-
- /**
- * @param {Object} paragraph Query
- * @param {Boolean} clearChart Flag is need clear chart model.
- * @param {{columns: Array, rows: Array, responseNodeId: String, queryId: int, hasMore: Boolean}} res Query results.
- * @private
- */
- const _processQueryResult = (paragraph, clearChart, res) => {
- const prevKeyCols = paragraph.chartKeyCols;
- const prevValCols = paragraph.chartValCols;
-
- if (!_.eq(paragraph.meta, res.columns)) {
- paragraph.meta = [];
-
- paragraph.chartColumns = [];
-
- if (!LegacyUtils.isDefined(paragraph.chartKeyCols))
- paragraph.chartKeyCols = [];
-
- if (!LegacyUtils.isDefined(paragraph.chartValCols))
- paragraph.chartValCols = [];
-
- if (res.columns.length) {
- const _key = _.find(res.columns, {fieldName: '_KEY'});
- const _val = _.find(res.columns, {fieldName: '_VAL'});
-
- paragraph.disabledSystemColumns = !(_key && _val) ||
- (res.columns.length === 2 && _key && _val) ||
- (res.columns.length === 1 && (_key || _val));
- }
-
- paragraph.columnFilter = _columnFilter(paragraph);
-
- paragraph.meta = res.columns;
-
- _rebuildColumns(paragraph);
- }
-
- paragraph.page = 1;
-
- paragraph.total = 0;
-
- paragraph.duration = res.duration;
-
- paragraph.queryId = res.hasMore ? res.queryId : null;
-
- paragraph.resNodeId = res.responseNodeId;
-
- paragraph.setError({message: ''});
-
- // Prepare explain results for display in table.
- if (paragraph.queryArgs.query && paragraph.queryArgs.query.startsWith('EXPLAIN') && res.rows) {
- paragraph.rows = [];
-
- res.rows.forEach((row, i) => {
- const line = res.rows.length - 1 === i ? row[0] : row[0] + '\n';
-
- line.replace(/\"/g, '').split('\n').forEach((ln) => paragraph.rows.push([ln]));
- });
- }
- else
- paragraph.rows = res.rows;
-
- paragraph.gridOptions.adjustHeight(paragraph.rows.length);
-
- const chartHistory = paragraph.chartHistory;
-
- // Clear history on query change.
- if (clearChart) {
- chartHistory.length = 0;
-
- _.forEach(paragraph.charts, (chart) => chart.data.length = 0);
- }
-
- // Add results to history.
- chartHistory.push({tm: new Date(), rows: paragraph.rows});
-
- // Keep history size no more than max length.
- while (chartHistory.length > HISTORY_LENGTH)
- chartHistory.shift();
-
- _showLoading(paragraph, false);
-
- if (_.isNil(paragraph.result) || paragraph.result === 'none' || paragraph.scanExplain())
- paragraph.result = 'table';
- else if (paragraph.chart()) {
- let resetCharts = clearChart;
-
- if (!resetCharts) {
- const curKeyCols = paragraph.chartKeyCols;
- const curValCols = paragraph.chartValCols;
-
- resetCharts = !prevKeyCols || !prevValCols ||
- prevKeyCols.length !== curKeyCols.length ||
- prevValCols.length !== curValCols.length;
- }
-
- _chartApplySettings(paragraph, resetCharts);
- }
- };
-
- const _closeOldQuery = (paragraph) => {
- const nid = paragraph.resNodeId;
-
- if (paragraph.queryId && _.find($scope.caches, ({nodes}) => _.find(nodes, {nid: nid.toUpperCase()})))
- return agentMgr.queryClose(nid, paragraph.queryId);
-
- return $q.when();
- };
-
- /**
- * @param {String} name Cache name.
- * @return {Array.<String>} Nids
- */
- const cacheNodes = (name) => {
- return _.find($scope.caches, {name}).nodes;
- };
-
- /**
- * @param {String} name Cache name.
- * @param {Boolean} local Local query.
- * @return {String} Nid
- */
- const _chooseNode = (name, local) => {
- if (_.isEmpty(name))
- return Promise.resolve(null);
-
- const nodes = _.filter(cacheNodes(name), (node) => !node.client);
-
- if (local) {
- return Nodes.selectNode(nodes, name)
- .then((selectedNids) => _.head(selectedNids));
- }
-
- return Promise.resolve(nodes[_.random(0, nodes.length - 1)].nid);
- };
-
- const _executeRefresh = (paragraph) => {
- const args = paragraph.queryArgs;
-
- agentMgr.awaitCluster()
- .then(() => _closeOldQuery(paragraph))
- .then(() => args.localNid || _chooseNode(args.cacheName, false))
- .then((nid) => agentMgr.querySql(nid, args.cacheName, args.query, args.nonCollocatedJoins,
- args.enforceJoinOrder, false, !!args.localNid, args.pageSize, args.lazy))
- .then((res) => _processQueryResult(paragraph, false, res))
- .catch((err) => paragraph.setError(err));
- };
-
- const _tryStartRefresh = function(paragraph) {
- _tryStopRefresh(paragraph);
-
- if (_.get(paragraph, 'rate.installed') && paragraph.queryExecuted()) {
- $scope.chartAcceptKeyColumn(paragraph, TIME_LINE);
-
- _executeRefresh(paragraph);
-
- const delay = paragraph.rate.value * paragraph.rate.unit;
-
- paragraph.rate.stopTime = $interval(_executeRefresh, delay, 0, false, paragraph);
- }
- };
-
- const addLimit = (query, limitSize) =>
- `SELECT * FROM (
- ${query}
- ) LIMIT ${limitSize}`;
-
- $scope.nonCollocatedJoinsAvailable = (paragraph) => {
- const cache = _.find($scope.caches, {name: paragraph.cacheName});
-
- if (cache)
- return !!_.find(cache.nodes, (node) => Version.since(node.version, NON_COLLOCATED_JOINS_SINCE));
-
- return false;
- };
-
- $scope.enforceJoinOrderAvailable = (paragraph) => {
- const cache = _.find($scope.caches, {name: paragraph.cacheName});
-
- if (cache)
- return !!_.find(cache.nodes, (node) => Version.since(node.version, ...ENFORCE_JOIN_SINCE));
-
- return false;
- };
-
- $scope.lazyQueryAvailable = (paragraph) => {
- const cache = _.find($scope.caches, {name: paragraph.cacheName});
-
- if (cache)
- return !!_.find(cache.nodes, (node) => Version.since(node.version, ...LAZY_QUERY_SINCE));
-
- return false;
- };
-
- $scope.ddlAvailable = (paragraph) => {
- const cache = _.find($scope.caches, {name: paragraph.cacheName});
-
- if (cache)
- return !!_.find(cache.nodes, (node) => Version.since(node.version, ...DDL_SINCE));
-
- return false;
- };
-
- $scope.execute = (paragraph, local = false) => {
- const nonCollocatedJoins = !!paragraph.nonCollocatedJoins;
- const enforceJoinOrder = !!paragraph.enforceJoinOrder;
- const lazy = !!paragraph.lazy;
-
- $scope.queryAvailable(paragraph) && _chooseNode(paragraph.cacheName, local)
- .then((nid) => {
- Notebook.save($scope.notebook)
- .catch(Messages.showError);
-
- paragraph.localQueryMode = local;
- paragraph.prevQuery = paragraph.queryArgs ? paragraph.queryArgs.query : paragraph.query;
-
- _showLoading(paragraph, true);
-
- return _closeOldQuery(paragraph)
- .then(() => {
- const args = paragraph.queryArgs = {
- type: 'QUERY',
- cacheName: ($scope.ddlAvailable(paragraph) && !paragraph.useAsDefaultSchema) ? null : paragraph.cacheName,
- query: paragraph.query,
- pageSize: paragraph.pageSize,
- maxPages: paragraph.maxPages,
- nonCollocatedJoins,
- enforceJoinOrder,
- localNid: local ? nid : null,
- lazy
- };
-
- const qry = args.maxPages ? addLimit(args.query, args.pageSize * args.maxPages) : paragraph.query;
-
- ActivitiesData.post({ action: '/queries/execute' });
-
- return agentMgr.querySql(nid, args.cacheName, qry, nonCollocatedJoins, enforceJoinOrder, false, local, args.pageSize, lazy);
- })
- .then((res) => {
- _processQueryResult(paragraph, true, res);
-
- _tryStartRefresh(paragraph);
- })
- .catch((err) => {
- paragraph.setError(err);
-
- _showLoading(paragraph, false);
-
- $scope.stopRefresh(paragraph);
- })
- .then(() => paragraph.ace.focus());
- });
- };
-
- const _cancelRefresh = (paragraph) => {
- if (paragraph.rate && paragraph.rate.stopTime) {
- delete paragraph.queryArgs;
-
- paragraph.rate.installed = false;
-
- $interval.cancel(paragraph.rate.stopTime);
-
- delete paragraph.rate.stopTime;
- }
- };
-
- $scope.explain = (paragraph) => {
- if (!$scope.queryAvailable(paragraph))
- return;
-
- Notebook.save($scope.notebook)
- .catch(Messages.showError);
-
- _cancelRefresh(paragraph);
-
- _showLoading(paragraph, true);
-
- _closeOldQuery(paragraph)
- .then(() => _chooseNode(paragraph.cacheName, false))
- .then((nid) => {
- const args = paragraph.queryArgs = {
- type: 'EXPLAIN',
- cacheName: paragraph.cacheName,
- query: 'EXPLAIN ' + paragraph.query,
- pageSize: paragraph.pageSize
- };
-
- ActivitiesData.post({ action: '/queries/explain' });
-
- return agentMgr.querySql(nid, args.cacheName, args.query, false, !!paragraph.enforceJoinOrder, false, false, args.pageSize, false);
- })
- .then((res) => _processQueryResult(paragraph, true, res))
- .catch((err) => {
- paragraph.setError(err);
-
- _showLoading(paragraph, false);
- })
- .then(() => paragraph.ace.focus());
- };
-
- $scope.scan = (paragraph, local = false) => {
- const cacheName = paragraph.cacheName;
- const caseSensitive = !!paragraph.caseSensitive;
- const filter = paragraph.filter;
- const pageSize = paragraph.pageSize;
-
- paragraph.localQueryMode = local;
-
- $scope.scanAvailable(paragraph) && _chooseNode(cacheName, local)
- .then((nid) => {
- paragraph.scanningInProgress = true;
-
- Notebook.save($scope.notebook)
- .catch(Messages.showError);
-
- _cancelRefresh(paragraph);
-
- _showLoading(paragraph, true);
-
- _closeOldQuery(paragraph)
- .then(() => {
- paragraph.queryArgs = {
- type: 'SCAN',
- cacheName,
- filter,
- regEx: false,
- caseSensitive,
- near: false,
- pageSize,
- localNid: local ? nid : null
- };
-
- ActivitiesData.post({ action: '/queries/scan' });
-
- return agentMgr.queryScan(nid, cacheName, filter, false, caseSensitive, false, local, pageSize);
- })
- .then((res) => _processQueryResult(paragraph, true, res))
- .catch((err) => {
- paragraph.setError(err);
-
- _showLoading(paragraph, false);
- })
- .then(() => paragraph.scanningInProgress = false);
- });
- };
-
- function _updatePieChartsWithData(paragraph, newDatum) {
- $timeout(() => {
- _.forEach(paragraph.charts, function(chart) {
- const chartDatum = chart.data;
-
- chartDatum.length = 0;
-
- _.forEach(newDatum, function(series) {
- if (chart.options.title.text === series.key)
- _.forEach(series.values, (v) => chartDatum.push(v));
- });
- });
-
- _.forEach(paragraph.charts, (chart) => chart.api.update());
- });
- }
-
- $scope.nextPage = (paragraph) => {
- _showLoading(paragraph, true);
-
- paragraph.queryArgs.pageSize = paragraph.pageSize;
-
- agentMgr.queryNextPage(paragraph.resNodeId, paragraph.queryId, paragraph.pageSize)
- .then((res) => {
- paragraph.page++;
-
- paragraph.total += paragraph.rows.length;
-
- paragraph.duration = res.duration;
-
- paragraph.rows = res.rows;
-
- if (paragraph.chart()) {
- if (paragraph.result === 'pie')
- _updatePieChartsWithData(paragraph, _pieChartDatum(paragraph));
- else
- _updateChartsWithData(paragraph, _chartDatum(paragraph));
- }
-
- paragraph.gridOptions.adjustHeight(paragraph.rows.length);
-
- _showLoading(paragraph, false);
-
- if (!res.hasMore)
- delete paragraph.queryId;
- })
- .catch((err) => {
- paragraph.setError(err);
-
- _showLoading(paragraph, false);
- })
- .then(() => paragraph.ace && paragraph.ace.focus());
- };
-
- const _export = (fileName, columnDefs, meta, rows, toClipBoard = false) => {
- let csvContent = '';
-
- const cols = [];
- const excludedCols = [];
-
- _.forEach(meta, (col, idx) => {
- if (columnDefs[idx].visible)
- cols.push(_fullColName(col));
- else
- excludedCols.push(idx);
- });
-
- csvContent += cols.join(';') + '\n';
-
- _.forEach(rows, (row) => {
- cols.length = 0;
-
- if (Array.isArray(row)) {
- _.forEach(row, (elem, idx) => {
- if (_.includes(excludedCols, idx))
- return;
-
- cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem));
- });
- }
- else {
- _.forEach(columnDefs, (col) => {
- if (col.visible) {
- const elem = row[col.fieldName];
-
- cols.push(_.isUndefined(elem) ? '' : JSON.stringify(elem));
- }
- });
- }
-
- csvContent += cols.join(';') + '\n';
- });
-
- if (toClipBoard)
- IgniteCopyToClipboard.copy(csvContent);
- else
- LegacyUtils.download('text/csv', fileName, csvContent);
- };
-
- /**
- * Generate file name with query results.
- *
- * @param paragraph {Object} Query paragraph .
- * @param all {Boolean} All result export flag.
- * @returns {string}
- */
- const exportFileName = (paragraph, all) => {
- const args = paragraph.queryArgs;
-
- if (args.type === 'SCAN')
- return `export-scan-${args.cacheName}-${paragraph.name}${all ? '-all' : ''}.csv`;
-
- return `export-query-${paragraph.name}${all ? '-all' : ''}.csv`;
- };
-
- $scope.exportCsvToClipBoard = (paragraph) => {
- _export(exportFileName(paragraph, false), paragraph.gridOptions.columnDefs, paragraph.meta, paragraph.rows, true);
- };
-
- $scope.exportCsv = function(paragraph) {
- _export(exportFileName(paragraph, false), paragraph.gridOptions.columnDefs, paragraph.meta, paragraph.rows);
-
- // paragraph.gridOptions.api.exporter.csvExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE);
- };
-
- $scope.exportPdf = function(paragraph) {
- paragraph.gridOptions.api.exporter.pdfExport(uiGridExporterConstants.ALL, uiGridExporterConstants.VISIBLE);
- };
-
- $scope.exportCsvAll = (paragraph) => {
- paragraph.csvIsPreparing = true;
-
- const args = paragraph.queryArgs;
-
- return Promise.resolve(args.localNid || _chooseNode(args.cacheName, false))
- .then((nid) => args.type === 'SCAN'
- ? agentMgr.queryScanGetAll(nid, args.cacheName, args.query, !!args.regEx, !!args.caseSensitive, !!args.near, !!args.localNid)
- : agentMgr.querySqlGetAll(nid, args.cacheName, args.query, !!args.nonCollocatedJoins, !!args.enforceJoinOrder, false, !!args.localNid, !!args.lazy))
- .then((res) => _export(exportFileName(paragraph, true), paragraph.gridOptions.columnDefs, res.columns, res.rows))
- .catch(Messages.showError)
- .then(() => {
- paragraph.csvIsPreparing = false;
-
- return paragraph.ace && paragraph.ace.focus();
- });
- };
-
- // $scope.exportPdfAll = function(paragraph) {
- // $http.post('/api/v1/agent/query/getAll', {query: paragraph.query, cacheName: paragraph.cacheName})
- // .then(({data}) {
- // _export(paragraph.name + '-all.csv', data.meta, data.rows);
- // })
- // .catch(Messages.showError);
- // };
-
- $scope.rateAsString = function(paragraph) {
- if (paragraph.rate && paragraph.rate.installed) {
- const idx = _.findIndex($scope.timeUnit, function(unit) {
- return unit.value === paragraph.rate.unit;
- });
-
- if (idx >= 0)
- return ' ' + paragraph.rate.value + $scope.timeUnit[idx].short;
-
- paragraph.rate.installed = false;
- }
-
- return '';
- };
-
- $scope.startRefresh = function(paragraph, value, unit) {
- paragraph.rate.value = value;
- paragraph.rate.unit = unit;
- paragraph.rate.installed = true;
-
- if (paragraph.queryExecuted() && !paragraph.scanExplain())
- _tryStartRefresh(paragraph);
- };
-
- $scope.stopRefresh = function(paragraph) {
- paragraph.rate.installed = false;
-
- _tryStopRefresh(paragraph);
- };
-
- $scope.paragraphTimeSpanVisible = function(paragraph) {
- return paragraph.timeLineSupported() && paragraph.chartTimeLineEnabled();
- };
-
- $scope.paragraphTimeLineSpan = function(paragraph) {
- if (paragraph && paragraph.timeLineSpan)
- return paragraph.timeLineSpan.toString();
-
- return '1';
- };
-
- $scope.applyChartSettings = function(paragraph) {
- _chartApplySettings(paragraph, true);
- };
-
- $scope.queryAvailable = function(paragraph) {
- return paragraph.query && !paragraph.loading;
- };
-
- $scope.queryTooltip = function(paragraph, action) {
- if ($scope.queryAvailable(paragraph))
- return;
-
- if (paragraph.loading)
- return 'Waiting for server response';
-
- return 'Input text to ' + action;
- };
-
- $scope.scanAvailable = function(paragraph) {
- return $scope.caches.length && !(paragraph.loading || paragraph.csvIsPreparing);
- };
-
- $scope.scanTooltip = function(paragraph) {
- if ($scope.scanAvailable(paragraph))
- return;
-
- if (paragraph.loading)
- return 'Waiting for server response';
-
- return 'Select cache to export scan results';
- };
-
- $scope.clickableMetadata = function(node) {
- return node.type.slice(0, 5) !== 'index';
- };
-
- $scope.dblclickMetadata = function(paragraph, node) {
- paragraph.ace.insert(node.name);
-
- setTimeout(() => paragraph.ace.focus(), 1);
- };
-
- $scope.importMetadata = function() {
- Loading.start('loadingCacheMetadata');
-
- $scope.metadata = [];
-
- agentMgr.metadata()
- .then((metadata) => {
- $scope.metadata = _.sortBy(_.filter(metadata, (meta) => {
- const cache = _.find($scope.caches, { name: meta.cacheName });
-
- if (cache) {
- meta.name = (cache.sqlSchema || '"' + meta.cacheName + '"') + '.' + meta.typeName;
- meta.displayName = (cache.sqlSchema || meta.maskedName) + '.' + meta.typeName;
-
- if (cache.sqlSchema)
- meta.children.unshift({type: 'plain', name: 'cacheName: ' + meta.maskedName, maskedName: meta.maskedName});
-
- meta.children.unshift({type: 'plain', name: 'mode: ' + cache.mode, maskedName: meta.maskedName});
- }
-
- return cache;
- }), 'name');
- })
- .catch(Messages.showError)
- .then(() => Loading.finish('loadingCacheMetadata'));
- };
-
- $scope.showResultQuery = function(paragraph) {
- if (!_.isNil(paragraph)) {
- const scope = $scope.$new();
-
- if (paragraph.queryArgs.type === 'SCAN') {
- scope.title = 'SCAN query';
-
- const filter = paragraph.queryArgs.filter;
-
- if (_.isEmpty(filter))
- scope.content = [`SCAN query for cache: <b>${maskCacheName(paragraph.queryArgs.cacheName, true)}</b>`];
- else
- scope.content = [`SCAN query for cache: <b>${maskCacheName(paragraph.queryArgs.cacheName, true)}</b> with filter: <b>${filter}</b>`];
- }
- else if (paragraph.queryArgs.query .startsWith('EXPLAIN ')) {
- scope.title = 'Explain query';
- scope.content = paragraph.queryArgs.query.split(/\r?\n/);
- }
- else {
- scope.title = 'SQL query';
- scope.content = paragraph.queryArgs.query.split(/\r?\n/);
- }
-
- // Attach duration and selected node info
- scope.meta = `Duration: ${$filter('duration')(paragraph.duration)}.`;
- scope.meta += paragraph.localQueryMode ? ` Node ID8: ${_.id8(paragraph.resNodeId)}` : '';
-
- // Show a basic modal from a controller
- $modal({scope, templateUrl: messageTemplateUrl, show: true});
- }
- };
-
- $scope.showStackTrace = function(paragraph) {
- if (!_.isNil(paragraph)) {
- const scope = $scope.$new();
-
- scope.title = 'Error details';
- scope.content = [];
-
- const tab = ' ';
-
- const addToTrace = (item) => {
- if (_.nonNil(item)) {
- const clsName = _.isEmpty(item.className) ? '' : '[' + JavaTypes.shortClassName(item.className) + '] ';
-
- scope.content.push((scope.content.length > 0 ? tab : '') + clsName + (item.message || ''));
-
- addToTrace(item.cause);
-
- _.forEach(item.suppressed, (sup) => addToTrace(sup));
- }
- };
-
- addToTrace(paragraph.error.root);
-
- // Show a basic modal from a controller
- $modal({scope, templateUrl: messageTemplateUrl, show: true});
- }
- };
- }
-];
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/modules/sql/sql.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/sql.module.js b/modules/web-console/frontend/app/modules/sql/sql.module.js
deleted file mode 100644
index da9955c..0000000
--- a/modules/web-console/frontend/app/modules/sql/sql.module.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import angular from 'angular';
-
-import NotebookData from './Notebook.data';
-import Notebook from './Notebook.service';
-import notebook from './notebook.controller';
-import controller from './sql.controller';
-
-import sqlTplUrl from 'app/../views/sql/sql.tpl.pug';
-
-angular.module('ignite-console.sql', [
- 'ui.router'
-])
-.config(['$stateProvider', ($stateProvider) => {
- // set up the states
- $stateProvider
- .state('base.sql', {
- url: '/queries',
- abstract: true,
- template: '<ui-view></ui-view>'
- })
- .state('base.sql.notebook', {
- url: '/notebook/{noteId}',
- templateUrl: sqlTplUrl,
- permission: 'query',
- tfMetaTags: {
- title: 'Query notebook'
- },
- controller,
- controllerAs: '$ctrl'
- })
- .state('base.sql.demo', {
- url: '/demo',
- templateUrl: sqlTplUrl,
- permission: 'query',
- tfMetaTags: {
- title: 'SQL demo'
- },
- controller,
- controllerAs: '$ctrl'
- });
-}])
-.service('IgniteNotebookData', NotebookData)
-.service('IgniteNotebook', Notebook)
-.controller('notebookController', notebook);
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/primitives/switcher/index.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/switcher/index.pug b/modules/web-console/frontend/app/primitives/switcher/index.pug
index 5689094..8b7d009 100644
--- a/modules/web-console/frontend/app/primitives/switcher/index.pug
+++ b/modules/web-console/frontend/app/primitives/switcher/index.pug
@@ -17,4 +17,4 @@
mixin switcher()
label.switcher--ignite
input(type='checkbox')&attributes(attributes)
- div(bs-tooltip=attributes.tip && '' data-title=attributes.tip data-trigger='hover')
+ div(bs-tooltip=attributes.tip && '' data-title=attributes.tip data-trigger='hover' data-placement='bottom')
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/app/primitives/switcher/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/switcher/index.scss b/modules/web-console/frontend/app/primitives/switcher/index.scss
index 3e9cd49..fb2fd1b 100644
--- a/modules/web-console/frontend/app/primitives/switcher/index.scss
+++ b/modules/web-console/frontend/app/primitives/switcher/index.scss
@@ -15,31 +15,39 @@
* limitations under the License.
*/
-@import '../../../public/stylesheets/variables';
+@import 'public/stylesheets/variables';
label.switcher--ignite {
- width: 34px;
- max-width: 34px !important;
- height: 20px;
+ $width: 34px;
+ $height: 20px;
- line-height: 20px;
+ $color-inactive-primary: #c5c5c5;
+ $color-inactive-secondary: #ffffff;
+ $color-active-primary: $ignite-brand-primary;
+ $color-active-secondary: #ff8485;
+
+ width: $width;
+ max-width: $width !important;
+ height: $height;
+
+ line-height: $height;
vertical-align: middle;
cursor: pointer;
- input[type="checkbox"] {
+ input[type='checkbox'] {
position: absolute;
opacity: 0.0;
& + div {
position: relative;
- width: 34px;
+ width: $width;
height: 14px;
margin: 3px 0;
border-radius: 8px;
- background-color: #C5C5C5;
+ background-color: $color-inactive-primary;
transition: background 0.2s ease;
&:before {
@@ -49,12 +57,14 @@ label.switcher--ignite {
top: -3px;
left: 0;
- width: 20px;
- height: 20px;
+ width: $height;
+ height: $height;
- border: solid 1px #C5C5C5;
+ border-width: 1px;
+ border-style: solid;
border-radius: 50%;
- background-color: #FFF;
+ border-color: $color-inactive-primary;
+ background-color: $color-inactive-secondary;
transition: all 0.12s ease;
}
@@ -64,17 +74,46 @@ label.switcher--ignite {
}
}
+ &[is-in-progress='true'] + div:before {
+ border-left-width: 2px;
+ border-left-color: $color-active-primary;
+
+ animation-name: switcher--animation;
+ animation-duration: 1s;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+ }
+
&:checked + div {
- background-color: #FF8485;
+ background-color: $color-active-secondary;
&:before {
content: '';
left: 14px;
- border: 0;
- background-color: #EE2B27;
+ border-color: $color-active-primary;
+ background-color: $color-active-primary;
+ }
+ }
+
+ &[is-in-progress='true']:checked + div {
+ background-color: $color-inactive-primary;
+
+ &:before {
+ border-color: $color-inactive-primary;
+ border-left-color: $color-active-primary;
+ background-color: $color-inactive-secondary;
}
}
}
}
+
+@keyframes switcher--animation {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1367bc98/modules/web-console/frontend/views/includes/header-right.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/includes/header-right.pug b/modules/web-console/frontend/views/includes/header-right.pug
index 8eeb281..56fd102 100644
--- a/modules/web-console/frontend/views/includes/header-right.pug
+++ b/modules/web-console/frontend/views/includes/header-right.pug
@@ -20,8 +20,6 @@
ng-click='startDemo()'
) Start Demo
-ignite-cluster-select.wch-nav-item(ng-if='!IgniteDemoMode')
-
.wch-nav-item(ignite-userbar)
div(
ng-class='{active: $state.includes("base.settings")}'