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/ignite-zk Commit: 1367bc98eb08233f9e47ba45335f9dda1fbb7bbd Parents: cbd69d6 Author: Dmitriy Shabalin <[email protected]> Authored: Wed Dec 6 10:36:42 2017 +0700 Committer: Alexey Kuznetsov <[email protected]> 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); + }); + } +}
