Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 70C70200C6B for ; Tue, 18 Apr 2017 07:39:14 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 6F3BB160BB5; Tue, 18 Apr 2017 05:39:14 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 26310160BB4 for ; Tue, 18 Apr 2017 07:39:12 +0200 (CEST) Received: (qmail 56672 invoked by uid 500); 18 Apr 2017 05:39:11 -0000 Mailing-List: contact commits-help@ignite.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ignite.apache.org Delivered-To: mailing list commits@ignite.apache.org Received: (qmail 54984 invoked by uid 99); 18 Apr 2017 05:39:05 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 18 Apr 2017 05:39:05 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 82AB6F2173; Tue, 18 Apr 2017 05:39:04 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sboikov@apache.org To: commits@ignite.apache.org Date: Tue, 18 Apr 2017 05:39:45 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [43/46] ignite git commit: IGNITE-4995 Multi-cluster support for Web Console. archived-at: Tue, 18 Apr 2017 05:39:14 -0000 http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/app/browser.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/app/browser.js b/modules/web-console/backend/app/browser.js deleted file mode 100644 index e9266a8..0000000 --- a/modules/web-console/backend/app/browser.js +++ /dev/null @@ -1,539 +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. - */ - -'use strict'; - -// Fire me up! - -/** - * Module interaction with browsers. - */ -module.exports = { - implements: 'browser-manager', - inject: ['require(lodash)', 'require(socket.io)', 'agent-manager', 'configure'] -}; - -module.exports.factory = (_, socketio, agentMgr, configure) => { - const _errorToJson = (err) => { - return { - message: err.message || err, - code: err.code || 1 - }; - }; - - return { - attach: (server) => { - const io = socketio(server); - - configure.socketio(io); - - io.sockets.on('connection', (socket) => { - const user = socket.request.user; - - const demo = socket.request._query.IgniteDemoMode === 'true'; - - const accountId = () => user._id; - - // Return available drivers to browser. - socket.on('schemaImport:drivers', (cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.availableDrivers()) - .then((drivers) => cb(null, drivers)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Return schemas from database to browser. - socket.on('schemaImport:schemas', (preset, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => { - const jdbcInfo = {user: preset.user, password: preset.password}; - - return agent.metadataSchemas(preset.jdbcDriverJar, preset.jdbcDriverClass, preset.jdbcUrl, jdbcInfo); - }) - .then((schemas) => cb(null, schemas)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Return tables from database to browser. - socket.on('schemaImport:tables', (preset, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => { - const jdbcInfo = {user: preset.user, password: preset.password}; - - return agent.metadataTables(preset.jdbcDriverJar, preset.jdbcDriverClass, preset.jdbcUrl, jdbcInfo, - preset.schemas, preset.tablesOnly); - }) - .then((tables) => cb(null, tables)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Return topology command result from grid to browser. - socket.on('node:topology', (attr, mtr, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.topology(demo, attr, mtr)) - .then((clusters) => cb(null, clusters)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Close query on node. - socket.on('node:query:close', (nid, queryId, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.queryClose(demo, nid, queryId)) - .then(() => cb()) - .catch((err) => cb(_errorToJson(err))); - }); - - // Execute query on node and return first page to browser. - socket.on('node:query', (nid, cacheName, query, distributedJoins, enforceJoinOrder, local, pageSize, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.fieldsQuery(demo, nid, cacheName, query, distributedJoins, enforceJoinOrder, local, pageSize)) - .then((res) => cb(null, res)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Fetch next page for query and return result to browser. - socket.on('node:query:fetch', (nid, queryId, pageSize, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.queryFetch(demo, nid, queryId, pageSize)) - .then((res) => cb(null, res)) - .catch((err) => cb(_errorToJson(err))); - }); - - const fetchResult = (acc) => { - if (!acc.hasMore) - return acc; - - return agent.queryFetch(demo, acc.responseNodeId, acc.queryId, pageSize) - .then(({result}) => { - acc.rows = acc.rows.concat(result.rows); - acc.hasMore = result.hasMore; - - return fetchResult(acc); - }); - }; - - // Execute query on node and return full result to browser. - socket.on('node:query:getAll', (nid, cacheName, query, distributedJoins, enforceJoinOrder, local, cb) => { - // Set page size for query. - const pageSize = 1024; - - agentMgr.findAgent(accountId()) - .then((agent) => { - const firstPage = agent.fieldsQuery(demo, nid, cacheName, query, distributedJoins, enforceJoinOrder, local, pageSize) - .then(({result}) => { - if (result.error) - return Promise.reject(result.error); - - return result.result; - }); - - return firstPage - .then(fetchResult); - }) - .then((res) => cb(null, res)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Collect cache query metrics and return result to browser. - socket.on('node:query:metrics', (nids, since, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.queryDetailMetrics(demo, nids, since)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Collect cache query metrics and return result to browser. - socket.on('node:query:reset:metrics', (nids, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.queryResetDetailMetrics(demo, nids)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Collect running queries from all nodes in grid. - socket.on('node:query:running', (duration, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.queryCollectRunning(demo, duration)) - .then((data) => { - - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Cancel running query by query id on node. - socket.on('node:query:cancel', (nid, queryId, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.queryCancel(demo, nid, queryId)) - .then((data) => { - - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Execute scan query on node and return first page to browser. - socket.on('node:scan', (nid, cacheName, filter, regEx, caseSensitive, near, local, pageSize, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.queryScan(demo, nid, cacheName, filter, regEx, caseSensitive, near, local, pageSize)) - .then((res) => cb(null, res)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Execute scan on node and return full result to browser. - socket.on('node:scan:getAll', (nid, cacheName, filter, regEx, caseSensitive, near, local, cb) => { - // Set page size for query. - const pageSize = 1024; - - agentMgr.findAgent(accountId()) - .then((agent) => { - const firstPage = agent.queryScan(demo, nid, cacheName, filter, regEx, caseSensitive, near, local, pageSize) - .then(({result}) => { - if (result.error) - return Promise.reject(result.error); - - return result.result; - }); - - return firstPage - .then(fetchResult); - }) - .then((res) => cb(null, res)) - .catch((err) => cb(_errorToJson(err))); - }); - - // Return cache metadata from all nodes in grid. - socket.on('node:cache:metadata', (cacheName, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.metadata(demo, cacheName)) - .then((caches) => { - let types = []; - - const _compact = (className) => { - return className.replace('java.lang.', '').replace('java.util.', '').replace('java.sql.', ''); - }; - - const _typeMapper = (meta, typeName) => { - const maskedName = _.isEmpty(meta.cacheName) ? '' : meta.cacheName; - - let fields = meta.fields[typeName]; - - let columns = []; - - for (const fieldName in fields) { - if (fields.hasOwnProperty(fieldName)) { - const fieldClass = _compact(fields[fieldName]); - - columns.push({ - type: 'field', - name: fieldName, - clazz: fieldClass, - system: fieldName === '_KEY' || fieldName === '_VAL', - cacheName: meta.cacheName, - typeName, - maskedName - }); - } - } - - const indexes = []; - - for (const index of meta.indexes[typeName]) { - fields = []; - - for (const field of index.fields) { - fields.push({ - type: 'index-field', - name: field, - order: index.descendings.indexOf(field) < 0, - unique: index.unique, - cacheName: meta.cacheName, - typeName, - maskedName - }); - } - - if (fields.length > 0) { - indexes.push({ - type: 'index', - name: index.name, - children: fields, - cacheName: meta.cacheName, - typeName, - maskedName - }); - } - } - - columns = _.sortBy(columns, 'name'); - - if (!_.isEmpty(indexes)) { - columns = columns.concat({ - type: 'indexes', - name: 'Indexes', - cacheName: meta.cacheName, - typeName, - maskedName, - children: indexes - }); - } - - return { - type: 'type', - cacheName: meta.cacheName || '', - typeName, - maskedName, - children: columns - }; - }; - - for (const meta of caches) { - const cacheTypes = meta.types.map(_typeMapper.bind(null, meta)); - - if (!_.isEmpty(cacheTypes)) - types = types.concat(cacheTypes); - } - - return cb(null, types); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Fetch next page for query and return result to browser. - socket.on('node:visor:collect', (evtOrderKey, evtThrottleCntrKey, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.collect(demo, evtOrderKey, evtThrottleCntrKey)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Gets node configuration for specified node. - socket.on('node:configuration', (nid, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.collectNodeConfiguration(demo, nid)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Gets cache configurations for specified node and caches deployment IDs. - socket.on('cache:configuration', (nid, caches, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.collectCacheConfigurations(demo, nid, caches)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Reset metrics specified cache on specified node and return result to browser. - socket.on('node:cache:reset:metrics', (nid, cacheName, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.cacheResetMetrics(demo, nid, cacheName)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Clear specified cache on specified node and return result to browser. - socket.on('node:cache:clear', (nid, cacheName, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.cacheClear(demo, nid, cacheName)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Start specified cache and return result to browser. - socket.on('node:cache:start', (nids, near, cacheName, cfg, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.cacheStart(demo, nids, near, cacheName, cfg)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Stop specified cache on specified node and return result to browser. - socket.on('node:cache:stop', (nid, cacheName, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.cacheStop(demo, nid, cacheName)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - - // Ping node and return result to browser. - socket.on('node:ping', (taskNid, nid, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.ping(demo, taskNid, nid)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // GC node and return result to browser. - socket.on('node:gc', (nids, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.gc(demo, nids)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Thread dump for node. - socket.on('node:thread:dump', (nid, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.threadDump(demo, nid)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Collect cache partitions. - socket.on('node:cache:partitions', (nids, cacheName, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.partitions(demo, nids, cacheName)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Stops given node IDs - socket.on('node:stop', (nids, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.stopNodes(demo, nids)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Restarts given node IDs. - socket.on('node:restart', (nids, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.restartNodes(demo, nids)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Collect service information from grid. - socket.on('service:collect', (nid, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.services(demo, nid)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - // Collect service information from grid. - socket.on('service:cancel', (nid, name, cb) => { - agentMgr.findAgent(accountId()) - .then((agent) => agent.serviceCancel(demo, nid, name)) - .then((data) => { - if (data.finished) - return cb(null, data.result); - - cb(_errorToJson(data.error)); - }) - .catch((err) => cb(_errorToJson(err))); - }); - - const count = agentMgr.addAgentListener(user._id, socket); - - socket.emit('agent:count', {count}); - }); - - // Handle browser disconnect event. - io.sockets.on('disconnect', (socket) => - agentMgr.removeAgentListener(socket.client.request.user._id, socket) - ); - } - }; -}; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/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 new file mode 100644 index 0000000..793fd5b --- /dev/null +++ b/modules/web-console/backend/app/browsersHandler.js @@ -0,0 +1,279 @@ +/* + * 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. + */ + +'use strict'; + +// Fire me up! + +/** + * Module interaction with browsers. + */ +module.exports = { + implements: 'browsers-handler', + inject: ['require(lodash)', 'require(socket.io)', 'configure', 'errors'] +}; + +module.exports.factory = (_, socketio, configure, errors) => { + class BrowserSockets { + constructor() { + this.sockets = new Map(); + + this.agentHnd = null; + } + + /** + * @param {Socket} sock + */ + add(sock) { + const token = sock.request.user.token; + + if (this.sockets.has(token)) + this.sockets.get(token).push(sock); + else + this.sockets.set(token, [sock]); + + return this.sockets.get(token); + } + + /** + * @param {Socket} sock + */ + remove(sock) { + const token = sock.request.user.token; + + const sockets = this.sockets.get(token); + + _.pull(sockets, sock); + + return sockets; + } + + get(token) { + if (this.sockets.has(token)) + return this.sockets.get(token); + + return []; + } + + demo(token) { + return _.filter(this.sockets.get(token), (sock) => sock.request._query.IgniteDemoMode === 'true'); + } + } + + return class BrowsersHandler { + /** + * @constructor + */ + constructor() { + /** + * Connected browsers. + * @type {BrowserSockets} + */ + this._browserSockets = new BrowserSockets(); + + /** + * Registered Visor task. + * @type {Map} + */ + this._visorTasks = new Map(); + } + + /** + * @param {Error} err + * @return {{code: number, message: *}} + */ + errorTransformer(err) { + return { + code: err.code || 1, + message: err.message || err + }; + } + + /** + * @param {String} token + * @param {Array.} [socks] + */ + agentStats(token, socks = this._browserSockets.get(token)) { + return this._agentHnd.agents(token) + .then((agentSocks) => { + const stat = _.reduce(agentSocks, (acc, agentSock) => { + acc.count += 1; + acc.hasDemo |= _.get(agentSock, 'demo.enabled'); + + if (agentSock.cluster) { + acc.clusters.add({ + id: agentSock.cluster.id + }); + } + + return acc; + }, {count: 0, hasDemo: false, clusters: new Set()}); + + stat.clusters = Array.from(stat.clusters); + + return stat; + }) + .catch(() => ({count: 0, hasDemo: false, clusters: []})) + .then((stat) => _.forEach(socks, (sock) => sock.emit('agents:stat', stat))); + } + + executeOnAgent(token, demo, event, ...args) { + const cb = _.last(args); + + return this._agentHnd.agent(token, demo) + .then((agentSock) => agentSock.emitEvent(event, ..._.dropRight(args))) + .then((res) => cb(null, res)) + .catch((err) => cb(this.errorTransformer(err))); + } + + agentListeners(sock) { + const demo = sock.request._query.IgniteDemoMode === 'true'; + const token = () => sock.request.user.token; + + // Return available drivers to browser. + sock.on('schemaImport:drivers', (...args) => { + this.executeOnAgent(token(), demo, 'schemaImport:drivers', ...args); + }); + + // Return schemas from database to browser. + sock.on('schemaImport:schemas', (...args) => { + this.executeOnAgent(token(), demo, 'schemaImport:schemas', ...args); + }); + + // Return tables from database to browser. + sock.on('schemaImport:tables', (...args) => { + this.executeOnAgent(token(), demo, 'schemaImport:tables', ...args); + }); + } + + /** + * @param {Promise.} agent + * @param {Boolean} demo + * @param {Object.} params + * @return {Promise.} + */ + executeOnNode(agent, demo, params) { + return agent + .then((agentSock) => agentSock.emitEvent('node:rest', {uri: 'ignite', demo, params, method: 'GET'})) + .then((res) => { + if (res.status === 0) + return JSON.parse(res.data); + + throw new Error(res.error); + }); + } + + registerVisorTask(taskId, taskCls, ...argCls) { + this._visorTasks.set(taskId, { + taskCls, + argCls + }); + } + + nodeListeners(sock) { + // Return command result from grid to browser. + sock.on('node:rest', (clusterId, params, cb) => { + const demo = sock.request._query.IgniteDemoMode === 'true'; + const token = sock.request.user.token; + + const agent = this._agentHnd.agent(token, demo, clusterId); + + this.executeOnNode(agent, demo, params) + .then((data) => cb(null, data)) + .catch((err) => cb(this.errorTransformer(err))); + }); + + const internalVisor = (postfix) => `org.apache.ignite.internal.visor.${postfix}`; + + this.registerVisorTask('querySql', internalVisor('query.VisorQueryTask'), internalVisor('query.VisorQueryArg')); + this.registerVisorTask('queryScan', internalVisor('query.VisorScanQueryTask'), internalVisor('query.VisorScanQueryArg')); + this.registerVisorTask('queryFetch', internalVisor('query.VisorQueryNextPageTask'), internalVisor('query.VisorQueryNextPageTaskArg')); + this.registerVisorTask('queryClose', internalVisor('query.VisorQueryCleanupTask'), + 'java.util.Map', 'java.util.UUID', 'java.util.Set'); + + // Return command result from grid to browser. + sock.on('node:visor', (clusterId, taskId, nids, ...args) => { + const demo = sock.request._query.IgniteDemoMode === 'true'; + const token = sock.request.user.token; + + const cb = _.last(args); + args = _.dropRight(args); + + const desc = this._visorTasks.get(taskId); + + if (_.isNil(desc)) + return cb(this.errorTransformer(new errors.IllegalArgumentException(`Failed to find Visor task for id: ${taskId}`))); + + const params = { + cmd: 'exe', + name: 'org.apache.ignite.internal.visor.compute.VisorGatewayTask', + p1: nids, + p2: desc.taskCls + }; + + _.forEach(_.concat(desc.argCls, args), (param, idx) => { params[`p${idx + 3}`] = param; }); + + const agent = this._agentHnd.agent(token, demo, clusterId); + + this.executeOnNode(agent, demo, params) + .then((data) => { + if (data.finished) + return cb(null, data.result); + + cb(this.errorTransformer(data.error)); + }) + .catch((err) => cb(this.errorTransformer(err))); + }); + } + + /** + * + * @param server + * @param {AgentsHandler} agentHnd + */ + attach(server, agentHnd) { + this._agentHnd = agentHnd; + + if (this.io) + throw 'Browser server already started!'; + + const io = socketio(server); + + configure.socketio(io); + + // Handle browser connect event. + io.sockets.on('connection', (sock) => { + this._browserSockets.add(sock); + + // Handle browser disconnect event. + sock.on('disconnect', () => { + this._browserSockets.remove(sock); + + const demo = sock.request._query.IgniteDemoMode === 'true'; + + // Stop demo if latest demo tab for this token. + demo && agentHnd.tryStopDemo(sock); + }); + + this.agentListeners(sock); + this.nodeListeners(sock); + + this.agentStats(sock.request.user.token, [sock]); + }); + } + }; +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/app/routes.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/app/routes.js b/modules/web-console/backend/app/routes.js index 6b5d052..826407b 100644 --- a/modules/web-console/backend/app/routes.js +++ b/modules/web-console/backend/app/routes.js @@ -22,11 +22,11 @@ module.exports = { implements: 'routes', inject: ['routes/public', 'routes/admin', 'routes/profiles', 'routes/demo', 'routes/clusters', 'routes/domains', - 'routes/caches', 'routes/igfss', 'routes/notebooks', 'routes/agents', 'routes/configurations', 'routes/activities'] + 'routes/caches', 'routes/igfss', 'routes/notebooks', 'routes/downloads', 'routes/configurations', 'routes/activities'] }; module.exports.factory = function(publicRoute, adminRoute, profilesRoute, demoRoute, - clustersRoute, domainsRoute, cachesRoute, igfssRoute, notebooksRoute, agentsRoute, configurationsRoute, activitiesRoute) { + clustersRoute, domainsRoute, cachesRoute, igfssRoute, notebooksRoute, downloadsRoute, configurationsRoute, activitiesRoute) { return { register: (app) => { const _mustAuthenticated = (req, res, next) => { @@ -58,7 +58,7 @@ module.exports.factory = function(publicRoute, adminRoute, profilesRoute, demoRo app.use('/configuration/igfs', igfssRoute); app.use('/notebooks', _mustAuthenticated, notebooksRoute); - app.use('/agent', _mustAuthenticated, agentsRoute); + app.use('/downloads', _mustAuthenticated, downloadsRoute); app.use('/activities', _mustAuthenticated, activitiesRoute); } }; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/index.js b/modules/web-console/backend/index.js index 27d7298..7416f51 100644 --- a/modules/web-console/backend/index.js +++ b/modules/web-console/backend/index.js @@ -32,7 +32,8 @@ try { fs.accessSync(igniteModulesInjector, fs.F_OK); injector = require(igniteModulesInjector); -} catch (ignore) { +} +catch (ignore) { injector = require(path.join(__dirname, './injector')); } @@ -71,25 +72,36 @@ const _onListening = (addr) => { console.log('Start listening on ' + bind); }; -Promise.all([injector('settings'), injector('app'), injector('agent-manager'), injector('browser-manager')]) - .then(([settings, app, agentMgr, browserMgr]) => { - // Start rest server. - const server = settings.server.SSLOptions - ? https.createServer(settings.server.SSLOptions) : http.createServer(); +/** + * @param settings + * @param {ApiServer} apiSrv + * @param {AgentsHandler} agentsHnd + * @param {BrowsersHandler} BrowsersHandler + */ +const init = ([settings, apiSrv, agentsHnd, BrowsersHandler]) => { + // Start rest server. + const srv = settings.server.SSLOptions ? https.createServer(settings.server.SSLOptions) : http.createServer(); + + srv.listen(settings.server.port); - server.listen(settings.server.port); - server.on('error', _onError.bind(null, settings.server.port)); - server.on('listening', _onListening.bind(null, server.address())); + srv.on('error', _onError.bind(null, settings.server.port)); + srv.on('listening', _onListening.bind(null, srv.address())); - app.listen(server); + apiSrv.attach(srv); - agentMgr.attach(server); - browserMgr.attach(server); + const browsersHnd = new BrowsersHandler(); + + agentsHnd.attach(srv, browsersHnd); + browsersHnd.attach(srv, agentsHnd); + + // Used for automated test. + if (process.send) + process.send('running'); +}; - // Used for automated test. - if (process.send) - process.send('running'); - }).catch((err) => { +Promise.all([injector('settings'), injector('api-server'), injector('agents-handler'), injector('browsers-handler')]) + .then(init) + .catch((err) => { console.error(err); process.exit(1); http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/package.json ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/package.json b/modules/web-console/backend/package.json index d50136f..2af7787 100644 --- a/modules/web-console/backend/package.json +++ b/modules/web-console/backend/package.json @@ -29,34 +29,34 @@ "win32" ], "dependencies": { - "body-parser": "^1.15.0", - "connect-mongo": "^1.1.0", + "body-parser": "1.17.1", + "connect-mongo": "1.3.2", "cookie-parser": "~1.4.0", - "express": "^4.14.0", - "express-session": "^1.12.0", - "fire-up": "^1.0.0", - "glob": "^7.0.3", - "jszip": "^3.0.0", - "lodash": "^4.8.2", - "mongoose": "^4.4.11", - "morgan": "^1.7.0", - "nconf": "^0.8.2", - "nodemailer": "^2.3.0", - "passport": "^0.3.2", - "passport-local": "^1.0.0", - "passport-local-mongoose": "^4.0.0", - "passport.socketio": "^3.6.1", - "socket.io": "^1.4.5" + "express": "4.15.2", + "express-session": "1.15.2", + "fire-up": "1.0.0", + "glob": "7.1.1", + "jszip": "3.1.3", + "lodash": "4.17.4", + "mongoose": "4.9.4", + "morgan": "1.8.1", + "nconf": "0.8.4", + "nodemailer": "3.1.4", + "passport": "0.3.2", + "passport-local": "1.0.0", + "passport-local-mongoose": "4.0.0", + "passport.socketio": "3.7.0", + "socket.io": "1.7.3" }, "devDependencies": { - "chai": "^3.5.0", - "cross-env": "^1.0.7", - "eslint": "^2.9.0", - "eslint-friendly-formatter": "^2.0.5", - "jasmine-core": "^2.4.1", - "mocha": "~2.5.3", - "mocha-teamcity-reporter": "^1.0.0", - "mockgoose": "^6.0.6", - "supertest": "^2.0.0" + "chai": "3.5.0", + "cross-env": "4.0.0", + "eslint": "3.19.0", + "eslint-friendly-formatter": "2.0.7", + "jasmine-core": "2.5.2", + "mocha": "3.2.0", + "mocha-teamcity-reporter": "1.1.1", + "mockgoose": "6.0.8", + "supertest": "3.0.0" } } http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/routes/agent.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/routes/agent.js b/modules/web-console/backend/routes/agent.js deleted file mode 100644 index 5ae807b..0000000 --- a/modules/web-console/backend/routes/agent.js +++ /dev/null @@ -1,57 +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. - */ - -'use strict'; - -// Fire me up! - -module.exports = { - implements: 'routes/agents', - inject: ['require(lodash)', 'require(express)', 'services/agents', 'services/activities'] -}; - -/** - * @param _ - * @param express - * @param {AgentsService} agentsService - * @param {ActivitiesService} activitiesService - * @returns {Promise} - */ -module.exports.factory = function(_, express, agentsService, activitiesService) { - return new Promise((resolveFactory) => { - const router = new express.Router(); - - /* Get grid topology. */ - router.get('/download/zip', (req, res) => { - activitiesService.merge(req.user._id, { - group: 'agent', - action: '/agent/download' - }); - - agentsService.getArchive(req.origin(), req.user.token) - .then(({fileName, buffer}) => { - // Set the archive name. - res.attachment(fileName); - - res.send(buffer); - }) - .catch(res.api.error); - }); - - resolveFactory(router); - }); -}; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/routes/demo.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/routes/demo.js b/modules/web-console/backend/routes/demo.js index 3f4166d..b200d83 100644 --- a/modules/web-console/backend/routes/demo.js +++ b/modules/web-console/backend/routes/demo.js @@ -26,10 +26,20 @@ const igfss = require('./demo/igfss.json'); module.exports = { implements: 'routes/demo', - inject: ['require(lodash)', 'require(express)', 'settings', 'mongo', 'services/spaces', 'errors'] + inject: ['require(lodash)', 'require(express)', 'errors', 'settings', 'mongo', 'services/spaces'] }; -module.exports.factory = (_, express, settings, mongo, spacesService, errors) => { +/** + * + * @param _ + * @param express + * @param errors + * @param settings + * @param mongo + * @param spacesService + * @return {Promise} + */ +module.exports.factory = (_, express, errors, settings, mongo, spacesService) => { return new Promise((factoryResolve) => { const router = new express.Router(); http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/routes/downloads.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/routes/downloads.js b/modules/web-console/backend/routes/downloads.js new file mode 100644 index 0000000..88a1923 --- /dev/null +++ b/modules/web-console/backend/routes/downloads.js @@ -0,0 +1,57 @@ +/* + * 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. + */ + +'use strict'; + +// Fire me up! + +module.exports = { + implements: 'routes/downloads', + inject: ['require(lodash)', 'require(express)', 'services/agents', 'services/activities'] +}; + +/** + * @param _ + * @param express + * @param {DownloadsService} downloadsService + * @param {ActivitiesService} activitiesService + * @returns {Promise} + */ +module.exports.factory = function(_, express, downloadsService, activitiesService) { + return new Promise((resolveFactory) => { + const router = new express.Router(); + + /* Get grid topology. */ + router.get('/agent', (req, res) => { + activitiesService.merge(req.user._id, { + group: 'agent', + action: '/agent/download' + }); + + downloadsService.prepareArchive(req.origin(), req.user.token) + .then(({fileName, buffer}) => { + // Set the archive name. + res.attachment(fileName); + + res.send(buffer); + }) + .catch(res.api.error); + }); + + resolveFactory(router); + }); +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/services/agents.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/services/agents.js b/modules/web-console/backend/services/agents.js deleted file mode 100644 index 4931bf8..0000000 --- a/modules/web-console/backend/services/agents.js +++ /dev/null @@ -1,83 +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. - */ - -'use strict'; - -// Fire me up! - -module.exports = { - implements: 'services/agents', - inject: ['require(lodash)', 'require(fs)', 'require(path)', 'require(jszip)', 'settings', 'agent-manager', 'errors'] -}; - -/** - * @param _ - * @param fs - * @param path - * @param JSZip - * @param settings - * @param agentMgr - * @param errors - * @returns {AgentsService} - */ -module.exports.factory = (_, fs, path, JSZip, settings, agentMgr, errors) => { - class AgentsService { - /** - * Get agent archive with user agent configuration. - * - * @returns {*} - readable stream for further piping. (http://stuk.github.io/jszip/documentation/api_jszip/generate_node_stream.html) - */ - static getArchive(host, token) { - const latest = agentMgr.supportedAgents.latest; - - if (_.isEmpty(latest)) - throw new errors.MissingResourceException('Missing agent zip on server. Please ask webmaster to upload agent zip!'); - - const filePath = latest.filePath; - const fileName = latest.fileName; - - const folder = path.basename(latest.fileName, '.zip'); - - // Read a zip file. - return new Promise((resolve, reject) => { - fs.readFile(filePath, (errFs, data) => { - if (errFs) - reject(new errors.ServerErrorException(errFs)); - - JSZip.loadAsync(data) - .then((zip) => { - const prop = []; - - prop.push('tokens=' + token); - prop.push(`server-uri=${host}`); - prop.push('#Uncomment following options if needed:'); - prop.push('#node-uri=http://localhost:8080'); - prop.push('#driver-folder=./jdbc-drivers'); - - zip.file(folder + '/default.properties', prop.join('\n')); - - return zip.generateAsync({type: 'nodebuffer', platform: 'UNIX'}) - .then((buffer) => resolve({filePath, fileName, buffer})); - }) - .catch(reject); - }); - }); - } - } - - return AgentsService; -}; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/services/downloads.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/services/downloads.js b/modules/web-console/backend/services/downloads.js new file mode 100644 index 0000000..3dfc2be --- /dev/null +++ b/modules/web-console/backend/services/downloads.js @@ -0,0 +1,80 @@ +/* + * 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. + */ + +'use strict'; + +// Fire me up! + +module.exports = { + implements: 'services/agents', + inject: ['require(lodash)', 'require(fs)', 'require(path)', 'require(jszip)', 'settings', 'agents-handler', 'errors'] +}; + +/** + * @param _ + * @param fs + * @param path + * @param JSZip + * @param settings + * @param agentsHnd + * @param errors + * @returns {DownloadsService} + */ +module.exports.factory = (_, fs, path, JSZip, settings, agentsHnd, errors) => { + class DownloadsService { + /** + * Get agent archive with user agent configuration. + * + * @returns {*} - readable stream for further piping. (http://stuk.github.io/jszip/documentation/api_jszip/generate_node_stream.html) + */ + prepareArchive(host, token) { + if (_.isEmpty(agentsHnd.currentAgent)) + throw new errors.MissingResourceException('Missing agent zip on server. Please ask webmaster to upload agent zip!'); + + const {filePath, fileName} = agentsHnd.currentAgent; + + const folder = path.basename(fileName, '.zip'); + + // Read a zip file. + return new Promise((resolve, reject) => { + fs.readFile(filePath, (errFs, data) => { + if (errFs) + reject(new errors.ServerErrorException(errFs)); + + JSZip.loadAsync(data) + .then((zip) => { + const prop = []; + + prop.push(`tokens=${token}`); + prop.push(`server-uri=${host}`); + prop.push('#Uncomment following options if needed:'); + prop.push('#node-uri=http://localhost:8080'); + prop.push('#driver-folder=./jdbc-drivers'); + + zip.file(`${folder}/default.properties`, prop.join('\n')); + + return zip.generateAsync({type: 'nodebuffer', platform: 'UNIX'}) + .then((buffer) => resolve({filePath, fileName, buffer})); + }) + .catch(reject); + }); + }); + } + } + + return new DownloadsService(); +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/services/users.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/services/users.js b/modules/web-console/backend/services/users.js index 0aff45f..51b88e9 100644 --- a/modules/web-console/backend/services/users.js +++ b/modules/web-console/backend/services/users.js @@ -21,21 +21,21 @@ module.exports = { implements: 'services/users', - inject: ['require(lodash)', 'mongo', 'settings', 'services/spaces', 'services/mails', 'services/activities', 'agent-manager', 'errors'] + inject: ['require(lodash)', 'errors', 'settings', 'mongo', 'services/spaces', 'services/mails', 'services/activities', 'agents-handler'] }; /** * @param _ * @param mongo + * @param errors * @param settings * @param {SpacesService} spacesService * @param {MailsService} mailsService * @param {ActivitiesService} activitiesService - * @param agentMgr - * @param errors + * @param {AgentsHandler} agentHnd * @returns {UsersService} */ -module.exports.factory = (_, mongo, settings, spacesService, mailsService, activitiesService, agentMgr, errors) => { +module.exports.factory = (_, errors, settings, mongo, spacesService, mailsService, activitiesService, agentHnd) => { const _randomString = () => { const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const possibleLen = possible.length; @@ -132,7 +132,7 @@ module.exports.factory = (_, mongo, settings, spacesService, mailsService, activ }) .then((user) => { if (changed.token && user.token !== changed.token) - agentMgr.close(user._id, user.token); + agentHnd.onTokenReset(user); _.extend(user, changed); http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/test/app/httpAgent.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/test/app/httpAgent.js b/modules/web-console/backend/test/app/httpAgent.js index 1394dc5..76b191e 100644 --- a/modules/web-console/backend/test/app/httpAgent.js +++ b/modules/web-console/backend/test/app/httpAgent.js @@ -21,11 +21,11 @@ module.exports = { implements: 'agentFactory', - inject: ['app', 'require(http)', 'require(supertest)'] + inject: ['api-server', 'require(http)', 'require(supertest)'] }; -module.exports.factory = (app, http, request) => { - const express = app.listen(http.createServer()); +module.exports.factory = (apiSrv, http, request) => { + const express = apiSrv.attach(http.createServer()); let authAgentInstance = null; return { http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/backend/test/routes/clusters.js ---------------------------------------------------------------------- diff --git a/modules/web-console/backend/test/routes/clusters.js b/modules/web-console/backend/test/routes/clusters.js index 5dd7a60..b9f6565 100644 --- a/modules/web-console/backend/test/routes/clusters.js +++ b/modules/web-console/backend/test/routes/clusters.js @@ -53,7 +53,7 @@ suite('routes.clusters', () => { }); test('Remove cluster model', (done) => { - return agentFactory.authAgent(db.mocks.accounts[0]) + agentFactory.authAgent(db.mocks.accounts[0]) .then((agent) => { agent.post('/configuration/clusters/remove') .send({_id: db.mocks.clusters[0]._id}) @@ -68,7 +68,7 @@ suite('routes.clusters', () => { }); test('Remove all clusters', (done) => { - return agentFactory.authAgent(db.mocks.accounts[0]) + agentFactory.authAgent(db.mocks.accounts[0]) .then((agent) => { agent.post('/configuration/clusters/remove/all') .expect(200) http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/app.config.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/app.config.js b/modules/web-console/frontend/app/app.config.js index 3ca5c3b..cf527e6 100644 --- a/modules/web-console/frontend/app/app.config.js +++ b/modules/web-console/frontend/app/app.config.js @@ -42,6 +42,7 @@ igniteConsoleCfg.config(['$animateProvider', ($animateProvider) => { igniteConsoleCfg.config(['$modalProvider', ($modalProvider) => { angular.extend($modalProvider.defaults, { animation: 'am-fade-and-scale', + placement: 'center', html: true }); }]); http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/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 80e32a1..a8460e9 100644 --- a/modules/web-console/frontend/app/app.js +++ b/modules/web-console/frontend/app/app.js @@ -114,6 +114,7 @@ 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'; // Inject external modules. @@ -197,6 +198,7 @@ angular .directive('igniteOnFocusOut', igniteOnFocusOut) .directive('igniteRestoreInputFocus', igniteRestoreInputFocus) .directive('igniteListOfRegisteredUsers', igniteListOfRegisteredUsers) +.directive('igniteClusterSelect', clusterSelect) // Services. .service('IgniteErrorPopover', ErrorPopover) .service('JavaTypes', JavaTypes) @@ -241,10 +243,10 @@ angular abstract: true, template: baseTemplate }) - .state('settings', { + .state('base.settings', { url: '/settings', abstract: true, - template: baseTemplate + template: '' }); $urlRouterProvider.otherwise('/404'); @@ -256,8 +258,8 @@ angular $root.$meta = $meta; $root.gettingStarted = gettingStarted; }]) -.run(['$rootScope', 'IgniteAgentMonitor', ($root, agentMonitor) => { - $root.$on('user', () => agentMonitor.init()); +.run(['$rootScope', 'AgentManager', ($root, agentMgr) => { + $root.$on('user', () => agentMgr.connect()); }]) .run(['$rootScope', ($root) => { $root.$on('$stateChangeStart', () => { @@ -272,7 +274,7 @@ angular .then((user) => { $root.$broadcast('user', user); - $state.go('settings.admin'); + $state.go('base.settings.admin'); }) .then(() => Notebook.load()) .catch(Messages.showError); http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/components/activities-user-dialog/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/activities-user-dialog/index.js b/modules/web-console/frontend/app/components/activities-user-dialog/index.js index 2f8fdef..02c7c1e 100644 --- a/modules/web-console/frontend/app/components/activities-user-dialog/index.js +++ b/modules/web-console/frontend/app/components/activities-user-dialog/index.js @@ -25,7 +25,6 @@ resolve: { user: () => user }, - placement: 'center', controller, controllerAs: 'ctrl' }); http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/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 new file mode 100644 index 0000000..a318172 --- /dev/null +++ b/modules/web-console/frontend/app/components/cluster-select/cluster-select.controller.js @@ -0,0 +1,55 @@ +/* + * 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 ['$scope', 'AgentManager', function($scope, agentMgr) { + const ctrl = this; + + ctrl.counter = 1; + + ctrl.cluster = null; + ctrl.clusters = []; + + $scope.$watchCollection(() => agentMgr.clusters, (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, + name: `Cluster ${cluster.id.substring(0, 8).toUpperCase()}`, + click: () => { + if (cluster.id === ctrl.cluster.id) + return; + + agentMgr.saveToStorage(cluster); + + window.open(window.location.href, '_blank'); + } + }); + }); + + if (_.isNil(ctrl.cluster)) + ctrl.cluster = _.find(ctrl.clusters, {id: agentMgr.cluster.id}); + }); +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/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 new file mode 100644 index 0000000..9c1ab75 --- /dev/null +++ b/modules/web-console/frontend/app/components/cluster-select/cluster-select.pug @@ -0,0 +1,40 @@ +//- + 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. + +ul.nav.navbar-nav(ng-if='!IgniteDemoMode' ng-switch='ctrl.clusters.length' style='padding-right: 5px') + li.disabled(ng-switch-when='0') + a + 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 + + li.disabled(ng-switch-when='1') + a + i.icon-cluster + label.padding-left-dflt {{ctrl.cluster.name}} + + li(ng-switch-default) + a.dropdown-toggle(bs-dropdown='' data-placement='bottom-left' data-trigger='hover focus' data-container='self' data-animation='' ng-click='$event.stopPropagation()' aria-haspopup='true' aria-expanded='expanded') + i.icon-cluster + label.padding-left-dflt {{ctrl.cluster.name}} + 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 {{item.name}} +i.icon-help(bs-tooltip='' data-placement='bottom' data-html=true style='padding-right: 20px; line-height: 25px' + data-title='Multi-Cluster Support
\ + More info') http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/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 new file mode 100644 index 0000000..b73845e --- /dev/null +++ b/modules/web-console/frontend/app/components/cluster-select/index.js @@ -0,0 +1,28 @@ +/* + * 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 controller from './cluster-select.controller'; + +export default [() => { + return { + restrict: 'E', + template, + controller, + controllerAs: 'ctrl' + }; +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.js b/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.js index fc3cb85..4a48b46 100644 --- a/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.js +++ b/modules/web-console/frontend/app/components/input-dialog/input-dialog.service.js @@ -49,7 +49,6 @@ export default class InputDialog { toValidValue }) }, - placement: 'center', controller, controllerAs: 'ctrl' }); http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js index 54bfb03..5bacce4 100644 --- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js +++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js @@ -19,7 +19,7 @@ const ICON_SORT = ' {{ COL_FIELD }}'; -const CLUSTER_HEADER_TEMPLATE = `
${ICON_SORT}
`; +const CLUSTER_HEADER_TEMPLATE = `
${ICON_SORT}
`; const MODEL_HEADER_TEMPLATE = `
${ICON_SORT}
`; const CACHE_HEADER_TEMPLATE = `
${ICON_SORT}
`; const IGFS_HEADER_TEMPLATE = `
${ICON_SORT}
`; http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug b/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug index 8444eb4..5a7aa2e 100644 --- a/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug +++ b/modules/web-console/frontend/app/helpers/jade/form/form-field-text.pug @@ -43,7 +43,7 @@ mixin ignite-form-field-text(label, model, name, disabled, required, placeholder +ignite-form-field__label(label, name, required) .ignite-form-field__control +tooltip(tip, tipOpts) - + if block block @@ -58,6 +58,23 @@ mixin ignite-form-field-url(label, model, name, required, placeholder, tip) .ignite-form-field +ignite-form-field__label(label, name, required) .ignite-form-field__control + +tooltip(tip, tipOpts) + + if block + block + + +form-field-feedback(name, 'required', errLbl + ' could not be empty!') + +form-field-feedback(name, 'url', errLbl + ' should be a valid URL!') + + .input-tip + +ignite-form-field-url-input(name, model, false, required, placeholder)(attributes=attributes) + +mixin ignite-form-field-url(label, model, name, required, placeholder, tip) + -var errLbl = label.substring(0, label.length - 1) + + .ignite-form-field + +ignite-form-field__label(label, name, required) + .ignite-form-field__control if tip i.tipField.icon-help(bs-tooltip='' data-title=tip) http://git-wip-us.apache.org/repos/asf/ignite/blob/323e3870/modules/web-console/frontend/app/helpers/jade/mixins.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/helpers/jade/mixins.pug b/modules/web-console/frontend/app/helpers/jade/mixins.pug index e6990f6..672b8f8 100644 --- a/modules/web-console/frontend/app/helpers/jade/mixins.pug +++ b/modules/web-console/frontend/app/helpers/jade/mixins.pug @@ -209,6 +209,12 @@ mixin url(lbl, model, name, required, placeholder, tip) if block block +//- Mixin for text field. +mixin url(lbl, model, name, required, placeholder, tip) + +ignite-form-field-url(lbl, model, name, required, placeholder, tip) + if block + block + //- Mixin for password field. mixin password(lbl, model, name, required, placeholder, tip) +ignite-form-field-password(lbl, model, name, false, required, placeholder, tip)