http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/routes/igfs.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/igfs.js b/modules/control-center-web/src/main/js/routes/igfs.js deleted file mode 100644 index 2fad048..0000000 --- a/modules/control-center-web/src/main/js/routes/igfs.js +++ /dev/null @@ -1,143 +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 _ = require('lodash'); -var router = require('express').Router(); -var db = require('../db'); - -/** - * Get spaces and IGFSs accessed for user account. - * - * @param req Request. - * @param res Response. - */ -router.post('/list', function (req, res) { - var user_id = req.currentUserId(); - - // Get owned space and all accessed space. - db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { - if (db.processed(err, res)) { - var space_ids = spaces.map(function (value) { - return value._id; - }); - - // Get all clusters for spaces. - db.Cluster.find({space: {$in: space_ids}}, '_id name').sort('name').exec(function (err, clusters) { - if (db.processed(err, res)) { - // Get all IGFSs for spaces. - db.Igfs.find({space: {$in: space_ids}}).sort('name').exec(function (err, igfss) { - if (db.processed(err, res)) { - _.forEach(igfss, function (igfs) { - // Remove deleted clusters. - igfs.clusters = _.filter(igfs.clusters, function (clusterId) { - return _.findIndex(clusters, function (cluster) { - return cluster._id.equals(clusterId); - }) >= 0; - }); - }); - - res.json({ - spaces: spaces, - clusters: clusters.map(function (cluster) { - return {value: cluster._id, label: cluster.name}; - }), - igfss: igfss - }); - } - }); } - }); - } - }); -}); - -/** - * Save IGFS. - */ -router.post('/save', function (req, res) { - var params = req.body; - var igfsId = params._id; - var clusters = params.clusters; - - if (params._id) { - db.Igfs.update({_id: igfsId}, params, {upsert: true}, function (err) { - if (db.processed(err, res)) - db.Cluster.update({_id: {$in: clusters}}, {$addToSet: {igfss: igfsId}}, {multi: true}, function (err) { - if (db.processed(err, res)) - db.Cluster.update({_id: {$nin: clusters}}, {$pull: {igfss: igfsId}}, {multi: true}, function (err) { - if (db.processed(err, res)) - res.send(params._id); - }); - }); - }) - } - else - db.Igfs.findOne({space: params.space, name: params.name}, function (err, igfs) { - if (db.processed(err, res)) { - if (igfs) - return res.status(500).send('IGFS with name: "' + igfs.name + '" already exist.'); - - (new db.Igfs(params)).save(function (err, igfs) { - if (db.processed(err, res)) { - igfsId = igfs._id; - - db.Cluster.update({_id: {$in: clusters}}, {$addToSet: {igfss: igfsId}}, {multi: true}, function (err) { - if (db.processed(err, res)) - res.send(igfsId); - }); - } - }); - } - }); -}); - -/** - * Remove IGFS by ._id. - */ -router.post('/remove', function (req, res) { - db.Igfs.remove(req.body, function (err) { - if (db.processed(err, res)) - res.sendStatus(200); - }) -}); - -/** - * Remove all IGFSs. - */ -router.post('/remove/all', function (req, res) { - var user_id = req.currentUserId(); - - // Get owned space and all accessed space. - db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { - if (db.processed(err, res)) { - var space_ids = spaces.map(function (value) { - return value._id; - }); - - db.Igfs.remove({space: {$in: space_ids}}, function (err) { - if (err) - return res.status(500).send(err.message); - - db.Cluster.update({space: {$in: space_ids}}, {igfss: []}, {multi: true}, function (err) { - if (db.processed(err, res)) - res.sendStatus(200); - }); - }) - } - }); -}); - -module.exports = router;
http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/routes/notebooks.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/notebooks.js b/modules/control-center-web/src/main/js/routes/notebooks.js deleted file mode 100644 index 93defd4..0000000 --- a/modules/control-center-web/src/main/js/routes/notebooks.js +++ /dev/null @@ -1,151 +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 router = require('express').Router(); - -var db = require('../db'); -var utils = require('./../helpers/common-utils'); - -/** - * Get notebooks names accessed for user account. - * - * @param req Request. - * @param res Response. - */ -router.post('/list', function (req, res) { - var user_id = req.currentUserId(); - - // Get owned space and all accessed space. - db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { - if (err) - return res.status(500).send(err.message); - - var space_ids = spaces.map(function (value) { - return value._id; - }); - - // Get all metadata for spaces. - db.Notebook.find({space: {$in: space_ids}}).select('_id name').sort('name').exec(function (err, notebooks) { - if (err) - return res.status(500).send(err.message); - - res.json(notebooks); - }); - }); -}); - -/** - * Get notebook accessed for user account. - * - * @param req Request. - * @param res Response. - */ -router.post('/get', function (req, res) { - var user_id = req.currentUserId(); - - // Get owned space and all accessed space. - db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { - if (err) - return res.status(500).send(err.message); - - var space_ids = spaces.map(function (value) { - return value._id; - }); - - // Get all metadata for spaces. - db.Notebook.findOne({space: {$in: space_ids}, _id: req.body.noteId}).exec(function (err, notebook) { - if (err) - return res.status(500).send(err.message); - - res.json(notebook); - }); - }); -}); - -/** - * Save notebook accessed for user account. - * - * @param req Request. - * @param res Response. - */ -router.post('/save', function (req, res) { - var note = req.body; - var noteId = note._id; - - if (noteId) - db.Notebook.update({_id: noteId}, note, {upsert: true}, function (err) { - if (err) - return res.status(500).send(err.message); - - res.send(noteId); - }); - else - db.Notebook.findOne({space: note.space, name: note.name}, function (err, note) { - if (err) - return res.status(500).send(err.message); - - if (note) - return res.status(500).send('Notebook with name: "' + note.name + '" already exist.'); - - (new db.Notebook(req.body)).save(function (err, note) { - if (err) - return res.status(500).send(err.message); - - res.send(note._id); - }); - }); -}); - -/** - * Remove notebook by ._id. - * - * @param req Request. - * @param res Response. - */ -router.post('/remove', function (req, res) { - db.Notebook.remove(req.body, function (err) { - if (err) - return res.status(500).send(err.message); - - res.sendStatus(200); - }); -}); - -/** - * Create new notebook for user account. - * - * @param req Request. - * @param res Response. - */ -router.post('/new', function (req, res) { - var user_id = req.currentUserId(); - - // Get owned space and all accessed space. - db.Space.findOne({owner: user_id}, function (err, space) { - if (err) - return res.status(500).send(err.message); - - (new db.Notebook({space: space.id, name: req.body.name})).save(function (err, note) { - if (err) - return res.status(500).send(err.message); - - return res.send(note._id); - }); - }); -}); - -module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/routes/profile.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/profile.js b/modules/control-center-web/src/main/js/routes/profile.js deleted file mode 100644 index ae262cc..0000000 --- a/modules/control-center-web/src/main/js/routes/profile.js +++ /dev/null @@ -1,85 +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 _ = require('lodash'); - -var router = require('express').Router(); -var db = require('../db'); - -function _updateUser(res, params) { - db.Account.update({_id: params._id}, params, {upsert: true}, function (err, user) { - // TODO IGNITE-843 Send error to admin. - if (err) - return res.status(500).send('Failed to update profile!'); - - if (params.email) - user.email = params.email; - - res.sendStatus(200); - }); -} - -function _checkEmail(res, user, params) { - if (params.email && user.email != params.email) { - db.Account.findOne({email: params.email}, function(err, userForEmail) { - // TODO send error to admin - if (err) - return res.status(500).send('Failed to check e-mail!'); - - if (userForEmail && userForEmail._id != user._id) - return res.status(500).send('User with this e-mail already registered!'); - - _updateUser(res, params); - }); - } - else - _updateUser(res, params); -} - -/** - * Save user profile. - */ -router.post('/save', function (req, res) { - var params = req.body; - - db.Account.findById(params._id, function (err, user) { - // TODO IGNITE-843 Send error to admin - if (err) - return res.status(500).send('Failed to find user!'); - - if (params.password) { - if (_.isEmpty(params.password)) - return res.status(500).send('Wrong value for new password!'); - - user.setPassword(params.password, function (err, user) { - if (err) - return res.status(500).send(err.message); - - user.save(function(err) { - if (err) - return res.status(500).send("Failed to change password!"); - - _checkEmail(res, user, params); - }); - }); - } - else - _checkEmail(res, user, params); - }); -}); - -module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/routes/public.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/public.js b/modules/control-center-web/src/main/js/routes/public.js deleted file mode 100644 index 44786a8..0000000 --- a/modules/control-center-web/src/main/js/routes/public.js +++ /dev/null @@ -1,231 +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 router = require('express').Router(); -var passport = require('passport'); -var nodemailer = require('nodemailer'); - -var db = require('../db'); -var config = require('../helpers/configuration-loader.js'); -var $commonUtils = require('./../helpers/common-utils'); - -// GET user. -router.post('/user', function (req, res) { - var becomeUsed = req.session.viewedUser && req.user.admin; - - var user = req.user; - - if (becomeUsed) { - user = req.session.viewedUser; - - user.becomeUsed = true; - } - - res.json(user); -}); - -/** - * Register new account. - */ -router.post('/register', function (req, res) { - db.Account.count(function (err, cnt) { - if (err) - return res.status(401).send(err.message); - - req.body.admin = cnt == 0; - - var account = new db.Account(req.body); - - account.token = $commonUtils.randomString(20); - - db.Account.register(account, req.body.password, function (err, account) { - if (err) - return res.status(401).send(err.message); - - if (!account) - return res.status(500).send('Failed to create account.'); - - new db.Space({name: 'Personal space', owner: account._id}).save(); - - req.logIn(account, {}, function (err) { - if (err) - return res.status(401).send(err.message); - - return res.sendStatus(200); - }); - }); - }); -}); - -/** - * Login in exist account. - */ -router.post('/login', function (req, res, next) { - passport.authenticate('local', function (err, user) { - if (err) - return res.status(401).send(err.message); - - if (!user) - return res.status(401).send('Invalid email or password'); - - req.logIn(user, {}, function (err) { - if (err) - return res.status(401).send(err.message); - - return res.sendStatus(200); - }); - })(req, res, next); -}); - -/** - * Logout. - */ -router.post('/logout', function (req, res) { - req.logout(); - - res.sendStatus(200); -}); - -/** - * Send e-mail to user with reset token. - */ -router.post('/password/forgot', function(req, res) { - var transporter = { - service: config.get('smtp:service'), - auth: { - user:config.get('smtp:email'), - pass: config.get('smtp:password') - } - }; - - if (transporter.service == '' || transporter.auth.user == '' || transporter.auth.pass == '') - return res.status(401).send('Can\'t send e-mail with instructions to reset password. Please ask webmaster to setup SMTP server!'); - - var token = $commonUtils.randomString(20); - - db.Account.findOne({ email: req.body.email }, function(err, user) { - if (!user) - return res.status(401).send('No account with that email address exists!'); - - if (err) - // TODO IGNITE-843 Send email to admin - return res.status(401).send('Failed to reset password!'); - - user.resetPasswordToken = token; - - user.save(function(err) { - if (err) - // TODO IGNITE-843 Send email to admin - return res.status(401).send('Failed to reset password!'); - - var mailer = nodemailer.createTransport(transporter); - - var mailOptions = { - from: config.address(config.get('smtp:username'), config.get('smtp:email')), - to: config.address(user.username, user.email), - subject: 'Password Reset', - text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + - 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + - 'http://' + req.headers.host + '/password/reset?token=' + token + '\n\n' + - 'If you did not request this, please ignore this email and your password will remain unchanged.\n\n' + - '--------------\n' + - 'Apache Ignite Web Console\n' - }; - - mailer.sendMail(mailOptions, function(err){ - if (err) - return res.status(401).send('Failed to send e-mail with reset link! ' + err); - - return res.status(200).send('An e-mail has been sent with further instructions.'); - }); - }); - }); -}); - -/** - * Change password with given token. - */ -router.post('/password/reset', function(req, res) { - db.Account.findOne({ resetPasswordToken: req.body.token }, function(err, user) { - if (!user) - return res.status(500).send('Invalid token for password reset!'); - - if (err) - // TODO IGNITE-843 Send email to admin - return res.status(500).send('Failed to reset password!'); - - user.setPassword(req.body.password, function (err, updatedUser) { - if (err) - return res.status(500).send(err.message); - - updatedUser.resetPasswordToken = undefined; - - updatedUser.save(function (err) { - if (err) - return res.status(500).send(err.message); - - var transporter = { - service: config.get('smtp:service'), - auth: { - user: config.get('smtp:email'), - pass: config.get('smtp:password') - } - }; - - var mailer = nodemailer.createTransport(transporter); - - var mailOptions = { - from: config.address(config.get('smtp:username'), config.get('smtp:email')), - to: config.address(user.username, user.email), - subject: 'Your password has been changed', - text: 'Hello,\n\n' + - 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n\n' + - 'Now you can login: http://' + req.headers.host + '\n\n' + - '--------------\n' + - 'Apache Ignite Web Console\n' - }; - - mailer.sendMail(mailOptions, function (err) { - if (err) - return res.status(503).send('Password was changed, but failed to send confirmation e-mail!<br />' + err); - - return res.status(200).send(user.email); - }); - }); - }); - }); -}); - -/* GET reset password page. */ -router.post('/validate/token', function (req, res) { - var token = req.body.token; - - var data = {token: token}; - - db.Account.findOne({resetPasswordToken: token}, function (err, user) { - if (!user) - data.error = 'Invalid token for password reset!'; - else if (err) - data.error = err; - else - data.email = user.email; - - res.json(data); - }); -}); - -module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve.js b/modules/control-center-web/src/main/js/serve.js index 9fff7a4..b5897a2 100644 --- a/modules/control-center-web/src/main/js/serve.js +++ b/modules/control-center-web/src/main/js/serve.js @@ -15,108 +15,94 @@ * limitations under the License. */ +const http = require('http'), + https = require('https'), + path = require('path'); + /** - * Module dependencies. + * Event listener for HTTP server "error" event. */ -var http = require('http'); -var https = require('https'); -var config = require('./helpers/configuration-loader.js'); -var app = require('./app'); -var agentManager = require('./agents/agent-manager'); +const _onError = (port, error) => { + if (error.syscall !== 'listen') + throw error; + + var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; -var fs = require('fs'); + // Handle specific listen errors with friendly messages. + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); -var debug = require('debug')('ignite-web-console:server'); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + + break; + default: + throw error; + } +}; /** - * Get port from environment and store in Express. + * Event listener for HTTP server "listening" event. */ -var port = config.normalizePort(config.get('server:port') || process.env.PORT || 80); +const _onListening = (addr) => { + var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; -// Create HTTP server. -var server = http.createServer(app); + console.log('Start listening on ' + bind); +}; -app.set('port', port); +const igniteModules = (process.env.IGNITE_MODULES && path.relative(__dirname, process.env.IGNITE_MODULES)) || './ignite_modules'; -/** - * Listen on provided port, on all network interfaces. - */ -server.listen(port); -server.on('error', onError); -server.on('listening', onListening); - -if (config.get('server:ssl')) { - httpsServer = https.createServer({ - key: fs.readFileSync(config.get('server:key')), - cert: fs.readFileSync(config.get('server:cert')), - passphrase: config.get('server:keyPassphrase') - }, app); - - var httpsPort = config.normalizePort(config.get('server:https-port') || 443); - - /** - * Listen on provided port, on all network interfaces. - */ - httpsServer.listen(httpsPort); - httpsServer.on('error', onError); - httpsServer.on('listening', onListening); -} +const fireUp = require('fire-up').newInjector({ + basePath: __dirname, + modules: [ + './serve/**/*.js', + `${igniteModules}/**/*.js` + ] +}); -/** - * Start agent server. - */ -var agentServer; +Promise.all([fireUp('settings'), fireUp('app'), fireUp('agent')]) + .then((values) => { + const settings = values[0], app = values[1], agent = values[2]; -if (config.get('agent-server:ssl')) { - agentServer = https.createServer({ - key: fs.readFileSync(config.get('agent-server:key')), - cert: fs.readFileSync(config.get('agent-server:cert')), - passphrase: config.get('agent-server:keyPassphrase') - }); -} -else { - agentServer = http.createServer(); -} + // Create HTTP server. + const server = http.createServer(app); -agentServer.listen(config.get('agent-server:port')); + app.set('port', settings.server.port); -agentManager.createManager(agentServer); + server.listen(settings.server.port); + server.on('error', _onError.bind(null, settings.server.port)); + server.on('listening', _onListening.bind(null, server.address())); -/** - * Event listener for HTTP server "error" event. - */ -function onError(error) { - if (error.syscall !== 'listen') { - throw error; - } - - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port; - - // Handle specific listen errors with friendly messages. - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; - case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; - default: - throw error; - } -} + // Create HTTPS server if needed. + if (settings.serverSSLOptions) { + const httpsServer = https.createServer(settings.server.SSLOptions, app); -/** - * Event listener for HTTP server "listening" event. - */ -function onListening() { - var addr = server.address(); - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - - console.log('Start listening on ' + bind); -} + const httpsPort = settings.server.SSLOptions.port; + + httpsServer.listen(httpsPort); + httpsServer.on('error', _onError.bind(null, httpsPort)); + httpsServer.on('listening', _onListening.bind(null, httpsServer.address())); + } + + // Start agent server. + const agentServer = settings.agent.SSLOptions + ? https.createServer(settings.agent.SSLOptions) : http.createServer(); + + agentServer.listen(settings.agent.port); + agentServer.on('error', _onError.bind(null, settings.agent.port)); + agentServer.on('listening', _onListening.bind(null, agentServer.address())); + + agent.listen(agentServer); + + // Used for automated test. + if (process.send) + process.send('running'); + }).catch((err) => { + console.error(err); + + process.exit(1); + }); http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/agent.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/agent.js b/modules/control-center-web/src/main/js/serve/agent.js new file mode 100644 index 0000000..d1ffe1a --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/agent.js @@ -0,0 +1,380 @@ +/* + * 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. + */ + +// Fire me up! + +module.exports = { + implements: 'agent', + inject: ['require(fs)', 'require(ws)', 'require(apache-ignite)', 'mongo'] +}; + +module.exports.factory = function (fs, ws, apacheIgnite, mongo) { + /** + * @constructor + */ + function AgentManager() { + this._clients = {}; + } + + /** + * + */ + AgentManager.prototype.listen = function (srv) { + if (this._server) + throw 'Agent server already started!'; + + this._server = srv; + + this._wss = new ws.Server({server: this._server}); + + var self = this; + + this._wss.on('connection', function (ws) { + new Client(ws, self); + }); + }; + + /** + * @param userId + * @param {Client} client + */ + AgentManager.prototype._removeClient = function (userId, client) { + var connections = this._clients[userId]; + + if (connections) { + var idx; + + while ((idx = connections.indexOf(client)) !== -1) + connections.splice(idx, 1); + + if (connections.length == 0) + delete this._clients[userId]; + } + }; + + /** + * @param userId + * @param {Client} client + */ + AgentManager.prototype._addClient = function (userId, client) { + var existingConnections = this._clients[userId]; + + if (!existingConnections) { + existingConnections = []; + + this._clients[userId] = existingConnections; + } + + existingConnections.push(client); + }; + + /** + * @param userId + * @returns {Client} + */ + AgentManager.prototype.findClient = function (userId) { + const clientsList = this._clients[userId]; + + if (!clientsList || clientsList.length == 0) + return null; + + return clientsList[0]; + }; + + /** + * Creates an instance of server for Ignite + * + * @constructor + * @this {AgentServer} + * @param {Client} client Connected client + * @param {Boolean} demo Use demo node for request + */ + function AgentServer(client, demo) { + this._client = client; + this._demo = !!demo; + } + + /** + * Run http request + * + * @this {AgentServer} + * @param {Command} cmd Command + * @param {callback} callback on finish + */ + AgentServer.prototype.runCommand = function (cmd, callback) { + var params = {cmd: cmd.name()}; + + for (var key in cmd._params) + params[key] = cmd._params[key]; + + var body = undefined; + + var headers = undefined; + + var method = 'GET'; + + if (cmd._isPost()) { + body = cmd.postData(); + + method = 'POST'; + + headers = {'JSONObject': 'application/json'}; + } + + this._client.executeRest("ignite", params, this._demo, method, headers, body, callback); + }; + + /** + * @constructor + * @param {AgentManager} manager + * @param {WebSocket} ws + */ + function Client(ws, manager) { + var self = this; + + this._manager = manager; + this._ws = ws; + + ws.on('close', function () { + if (self._user) { + self._manager._removeClient(self._user._id, self); + } + }); + + ws.on('message', function (msgStr) { + var msg = JSON.parse(msgStr); + + self['_rmt' + msg.type](msg); + }); + + this._reqCounter = 0; + + this._cbMap = {}; + } + + Client.prototype._runCommand = function (method, args) { + var self = this; + + return new Promise(function (resolve, reject) { + self._invokeRmtMethod(method, args, function (error, res) { + if (error != null) + return reject(error); + + resolve(res); + }); + }); + }; + + /** + * @param {String} uri + * @param {Object} params + * @param {Boolean} demo + * @param {String} [method] + * @param {Object} [headers] + * @param {String} [body] + * @param {callback} [callback] Callback. Take 3 arguments: {Number} successStatus, {String} error, {String} response. + */ + Client.prototype.executeRest = function (uri, params, demo, method, headers, body, callback) { + if (typeof(params) != 'object') + throw '"params" argument must be an object'; + + if (typeof(callback) != 'function') + throw 'callback must be a function'; + + if (body && typeof(body) != 'string') + throw 'body must be a string'; + + if (headers && typeof(headers) != 'object') + throw 'headers must be an object'; + + if (!method) + method = 'GET'; + else + method = method.toUpperCase(); + + if (method != 'GET' && method != 'POST') + throw 'Unknown HTTP method: ' + method; + + const cb = function (error, restResult) { + if (error) + return callback(error); + + const restError = restResult.error; + + if (restError) + return callback(restError); + + const restCode = restResult.restCode; + + if (restCode !== 200) { + if (restCode === 401) + return callback.call({code: restCode, message: "Failed to authenticate on node."}); + + return callback.call({ + code: restCode, + message: restError || "Failed connect to node and execute REST command." + }); + } + + try { + var nodeResponse = JSON.parse(restResult.response); + + if (nodeResponse.successStatus === 0) + return callback(null, nodeResponse.response); + + switch (nodeResponse.successStatus) { + case 1: + return callback({code: 500, message: nodeResponse.error}); + case 2: + return callback({code: 401, message: nodeResponse.error}); + case 3: + return callback({code: 403, message: nodeResponse.error}); + } + + callback(nodeResponse.error); + } + catch (e) { + callback(e); + } + }; + + this._invokeRmtMethod('executeRest', [uri, params, demo, method, headers, body], cb); + }; + + /** + * @param {string} error + */ + Client.prototype.authResult = function (error) { + return this._runCommand('authResult', [].slice.call(arguments)); + }; + + /** + * @param {String} driverPath + * @param {String} driverClass + * @param {String} url + * @param {Object} info + * @returns {Promise} Promise on list of tables (see org.apache.ignite.schema.parser.DbTable java class) + */ + Client.prototype.metadataSchemas = function (driverPath, driverClass, url, info) { + return this._runCommand('schemas', [].slice.call(arguments)); + }; + + /** + * @param {String} driverPath + * @param {String} driverClass + * @param {String} url + * @param {Object} info + * @param {Array} schemas + * @param {Boolean} tablesOnly + * @returns {Promise} Promise on list of tables (see org.apache.ignite.schema.parser.DbTable java class) + */ + Client.prototype.metadataTables = function (driverPath, driverClass, url, info, schemas, tablesOnly) { + return this._runCommand('metadata', [].slice.call(arguments)); + }; + + /** + * @returns {Promise} Promise on list of jars from driver folder. + */ + Client.prototype.availableDrivers = function () { + return this._runCommand('availableDrivers', [].slice.call(arguments)); + }; + + /** + * Run http request + * + * @this {AgentServer} + * @param {String} method Command name. + * @param {Array} args Command params. + * @param {Function} callback on finish + */ + Client.prototype._invokeRmtMethod = function (method, args, callback) { + if (this._ws.readyState != 1) { + if (callback) + callback('org.apache.ignite.agent.AgentException: Connection is closed'); + + return; + } + + var msg = { + method: method, + args: args + }; + + if (callback) { + var reqId = this._reqCounter++; + + this._cbMap[reqId] = callback; + + msg.reqId = reqId; + } + + this._ws.send(JSON.stringify(msg)) + }; + + Client.prototype._rmtAuthMessage = function (msg) { + var self = this; + + fs.stat('public/agent/ignite-web-agent-1.5.0.final.zip', function (err, stats) { + var relDate = 0; + + if (!err) + relDate = stats.birthtime.getTime(); + + if ((msg.relDate || 0) < relDate) + self.authResult('You are using an older version of the agent. Please reload agent archive'); + + mongo.Account.findOne({token: msg.token}, function (err, account) { + if (err) { + self.authResult('Failed to authorize user'); + // TODO IGNITE-1379 send error to web master. + } + else if (!account) + self.authResult('Invalid token, user not found'); + else { + self.authResult(null); + + self._user = account; + + self._manager._addClient(account._id, self); + + self._cluster = new apacheIgnite.Ignite(new AgentServer(self)); + + self._demo = new apacheIgnite.Ignite(new AgentServer(self, true)); + } + }); + }); + }; + + Client.prototype._rmtCallRes = function (msg) { + var callback = this._cbMap[msg.reqId]; + + if (!callback) return; + + delete this._cbMap[msg.reqId]; + + callback(msg.error, msg.response); + }; + + /** + * @returns {Ignite} + */ + Client.prototype.ignite = function (demo) { + return demo ? this._demo : this._cluster; + }; + + return new AgentManager(); +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/app.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/app.js b/modules/control-center-web/src/main/js/serve/app.js new file mode 100644 index 0000000..cc93ed9 --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/app.js @@ -0,0 +1,33 @@ +/* + * 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. + */ + +// Fire me up! + +module.exports = { + implements: 'app', + inject: ['require(express)', 'configure', 'routes'] +}; + +module.exports.factory = function(express, configure, routes) { + const app = new express(); + + configure(app); + + routes.register(app); + + return app; +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/config/default.json ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/config/default.json b/modules/control-center-web/src/main/js/serve/config/default.json new file mode 100644 index 0000000..574d42a --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/config/default.json @@ -0,0 +1,26 @@ +{ + "server": { + "port": 3000, + "https-port": 8443, + "ssl": false, + "key": "serve/keys/test.key", + "cert": "serve/keys/test.crt", + "keyPassphrase": "password" + }, + "mongoDB": { + "url": "mongodb://localhost/web-control-center" + }, + "agent-server": { + "port": 3001, + "ssl": true, + "key": "serve/keys/test.key", + "cert": "serve/keys/test.crt", + "keyPassphrase": "password" + }, + "smtp": { + "service": "", + "username": "", + "email": "", + "password": "" + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/configure.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/configure.js b/modules/control-center-web/src/main/js/serve/configure.js new file mode 100644 index 0000000..c16b516 --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/configure.js @@ -0,0 +1,67 @@ +/* + * 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. + */ + +// Fire me up! + +module.exports = { + implements: 'configure', + inject: ['require(morgan)', 'require(cookie-parser)', 'require(body-parser)', 'require(express-force-ssl)', + 'require(express-session)', 'require(connect-mongo)', 'require(passport)', 'settings', 'mongo'] +}; + +module.exports.factory = function (logger, cookieParser, bodyParser, forceSSL, session, connectMongo, passport, + settings, mongo) { + return (app) => { + app.use(logger('dev', { + skip: function (req, res) { + return res.statusCode < 400; + } + })); + + app.use(cookieParser(settings.sessionSecret)); + + app.use(bodyParser.json({limit: '50mb'})); + app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); + + var mongoStore = connectMongo(session); + + app.use(session({ + secret: settings.sessionSecret, + resave: false, + saveUninitialized: true, + cookie: { + expires: new Date(Date.now() + settings.cookieTTL), + maxAge: settings.cookieTTL + } + , store: new mongoStore({mongooseConnection: mongo.connection}) + })); + + app.use(passport.initialize()); + app.use(passport.session()); + + passport.serializeUser(mongo.Account.serializeUser()); + passport.deserializeUser(mongo.Account.deserializeUser()); + + passport.use(mongo.Account.createStrategy()); + + if (settings.SSLOptions) { + app.set('forceSSLOptions', settings.SSLOptions); + + app.use(forceSSL); + } + }; +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/keys/test.crt ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/keys/test.crt b/modules/control-center-web/src/main/js/serve/keys/test.crt new file mode 100644 index 0000000..50c6d5c --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/keys/test.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB6zCCAVQCCQDcAphbU6UcLjANBgkqhkiG9w0BAQsFADA6MRIwEAYDVQQDDAls +b2NhbGhvc3QxJDAiBgkqhkiG9w0BCQEWFXNldmRva2ltb3ZAYXBhY2hlLm9yZzAe +Fw0xNTA3MTQxMzAyNTNaFw0xODA2MjMxMzAyNTNaMDoxEjAQBgNVBAMMCWxvY2Fs +aG9zdDEkMCIGCSqGSIb3DQEJARYVc2V2ZG9raW1vdkBhcGFjaGUub3JnMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP/zpJrdHqCj6lPpeFF6LQtzKef6UiyBBo +rbuOtCCgW8KMJJciluBWk2126qLt9smBN4jBpSNU3pq0r9gBMUTd/LSe7aY4D5ED +Pjp7XsypNVKeHaHbFi7KhfHy0LYxsWiNPmmHJv4dtYOp+pGK25rkXNfyJxxjgxN6 +wo34+MnZIQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFk9XEjcdyihws+fVmdGGUFo +bVxI9YGH6agiNbU3WNF4B4VRzcPPW8z2mEo7eF9kgYmq/YzH4T8tgi/qkL/u8eZV +Wmi9bg6RThLN6/hj3wVoOFKykbDQ05FFdhIJXN5UOjPmxYM97EKqg6J0W2HAb8SG ++UekPnmAo/2HTKsLykH8 +-----END CERTIFICATE----- http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/keys/test.key ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/keys/test.key b/modules/control-center-web/src/main/js/serve/keys/test.key new file mode 100644 index 0000000..1b395c0 --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/keys/test.key @@ -0,0 +1,18 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,6798185330CE2EE2 + +sOwkmD8rvjx11l09V26dJhLhl+SyPIhyeZ3TqHXrYCATKoXlzidT+uPu1jVYtrwr +nBLA6TrIDYRrBNlEsqGZ0cSvWTIczzVW1xZKHEJo5q2vUT/W8u/Q1QQtS3P3GeKF +dEzx496rpZqwwVw59GNbuIwyYoVvQf3iEXzfhplGmLPELYIplDFOLgNuXQyXSGx6 +rwKsCxXMLsDyrA6DCz0Odf08p2HvWk/s5Ne3DFcQlqRNtIrBVGD2O0/Fp8ZZ2I4E +Yn2OIIWJff3HanOjLOWKdN8YAn5UleNmlEUdIHeS5qaQ68mabOxLkSef9qglV+sd +FHTtUq0cG6t6nhxZBziexha6v1yl/xABAHHhNPOfak+HthWxRD4N9f1yFYAeTmkn +4kwBWoSUe12XRf2pGNqhEUKN/KhDmWk85wI55i/Cu2XmNoiBFlS9BXrRYU8uVCJw +KlxjKTDWl1opCyvxTDxJnMkt44ZT445LRePKVueGIIKSUIXNQypOE+C1I0CL0N2W +Ts3m9nthquvLeMx92k7b8yW69BER5uac3SIlGCOJObQXsHgyk8wYiyd/zLKfjctG +PXieaW81UKjp+GqWpvWPz3VqnKwoyUWeVOOTviurli6kYOrHuySTMqMb6hxJctw9 +grAQTT0UPiAKWcM7InLzZnRjco+v9QLLEokjVngXPba16K/CItFY16xuGlaFLW7Y +XTc67AkL8b76HBZelMjmCsqjvSoULhuMFwTOvUMm/mSM8rMoi9asrJRLQHRMWCST +/6RENPLzPlOMnNLBujpBbn8V3/aYzEZsHMI+6S3d27WYlTJIqpabSA== +-----END RSA PRIVATE KEY----- http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/mongo.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/mongo.js b/modules/control-center-web/src/main/js/serve/mongo.js new file mode 100644 index 0000000..edfb5f8 --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/mongo.js @@ -0,0 +1,551 @@ +/* + * 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. + */ + +// Fire me up! + +module.exports = { + implements: 'mongo', + inject: ['require(mongoose-deep-populate)', 'require(passport-local-mongoose)', 'settings', 'ignite_modules/mongo:*'] +}; + +module.exports.factory = function (deepPopulatePlugin, passportMongo, settings, pluginMongo) { + var mongoose = require('mongoose'); + + const deepPopulate = deepPopulatePlugin(mongoose); + + // Connect to mongoDB database. + mongoose.connect(settings.mongoUrl, {server: {poolSize: 4}}); + + const Schema = mongoose.Schema, ObjectId = mongoose.Schema.Types.ObjectId, + result = { connection: mongoose.connection }; + + // Define Account schema. + var AccountSchema = new Schema({ + username: String, + email: String, + company: String, + country: String, + lastLogin: Date, + admin: Boolean, + token: String, + resetPasswordToken: String + }); + + // Install passport plugin. + AccountSchema.plugin(passportMongo, { + usernameField: 'email', limitAttempts: true, lastLoginField: 'lastLogin', + usernameLowerCase: true + }); + + // Configure transformation to JSON. + AccountSchema.set('toJSON', { + transform: function (doc, ret) { + return { + _id: ret._id, + email: ret.email, + username: ret.username, + company: ret.company, + country: ret.country, + admin: ret.admin, + token: ret.token, + lastLogin: ret.lastLogin + }; + } + }); + + // Define Account model. + result.Account = mongoose.model('Account', AccountSchema); + + // Define Space model. + result.Space = mongoose.model('Space', new Schema({ + name: String, + owner: {type: ObjectId, ref: 'Account'}, + usedBy: [{ + permission: {type: String, enum: ['VIEW', 'FULL']}, + account: {type: ObjectId, ref: 'Account'} + }] + })); + + // Define Domain model schema. + var DomainModelSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + caches: [{type: ObjectId, ref: 'Cache'}], + queryMetadata: {type: String, enum: ['Annotations', 'Configuration']}, + kind: {type: String, enum: ['query', 'store', 'both']}, + databaseSchema: String, + databaseTable: String, + keyType: String, + valueType: String, + keyFields: [{ + databaseFieldName: String, + databaseFieldType: String, + javaFieldName: String, + javaFieldType: String + }], + valueFields: [{ + databaseFieldName: String, + databaseFieldType: String, + javaFieldName: String, + javaFieldType: String + }], + fields: [{name: String, className: String}], + aliases: [{field: String, alias: String}], + indexes: [{ + name: String, + indexType: {type: String, enum: ['SORTED', 'FULLTEXT', 'GEOSPATIAL']}, + fields: [{name: String, direction: Boolean}] + }], + demo: Boolean + }); + + // Define model of Domain models. + result.DomainModel = mongoose.model('DomainModel', DomainModelSchema); + + // Define Cache schema. + var CacheSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + name: String, + clusters: [{type: ObjectId, ref: 'Cluster'}], + domains: [{type: ObjectId, ref: 'DomainModel'}], + cacheMode: {type: String, enum: ['PARTITIONED', 'REPLICATED', 'LOCAL']}, + atomicityMode: {type: String, enum: ['ATOMIC', 'TRANSACTIONAL']}, + + backups: Number, + memoryMode: {type: String, enum: ['ONHEAP_TIERED', 'OFFHEAP_TIERED', 'OFFHEAP_VALUES']}, + offHeapMaxMemory: Number, + startSize: Number, + swapEnabled: Boolean, + + evictionPolicy: { + kind: {type: String, enum: ['LRU', 'FIFO', 'Sorted']}, + LRU: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + FIFO: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + SORTED: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + } + }, + + rebalanceMode: {type: String, enum: ['SYNC', 'ASYNC', 'NONE']}, + rebalanceBatchSize: Number, + rebalanceBatchesPrefetchCount: Number, + rebalanceOrder: Number, + rebalanceDelay: Number, + rebalanceTimeout: Number, + rebalanceThrottle: Number, + + cacheStoreFactory: { + kind: { + type: String, + enum: ['CacheJdbcPojoStoreFactory', 'CacheJdbcBlobStoreFactory', 'CacheHibernateBlobStoreFactory'] + }, + CacheJdbcPojoStoreFactory: { + dataSourceBean: String, + dialect: { + type: String, + enum: ['Generic', 'Oracle', 'DB2', 'SQLServer', 'MySQL', 'PostgreSQL', 'H2'] + } + }, + CacheJdbcBlobStoreFactory: { + connectVia: {type: String, enum: ['URL', 'DataSource']}, + connectionUrl: String, + user: String, + dataSourceBean: String, + dialect: { + type: String, + enum: ['Generic', 'Oracle', 'DB2', 'SQLServer', 'MySQL', 'PostgreSQL', 'H2'] + }, + initSchema: Boolean, + createTableQuery: String, + loadQuery: String, + insertQuery: String, + updateQuery: String, + deleteQuery: String + }, + CacheHibernateBlobStoreFactory: { + hibernateProperties: [String] + } + }, + storeKeepBinary: Boolean, + loadPreviousValue: Boolean, + readThrough: Boolean, + writeThrough: Boolean, + + writeBehindEnabled: Boolean, + writeBehindBatchSize: Number, + writeBehindFlushSize: Number, + writeBehindFlushFrequency: Number, + writeBehindFlushThreadCount: Number, + + invalidate: Boolean, + defaultLockTimeout: Number, + atomicWriteOrderMode: {type: String, enum: ['CLOCK', 'PRIMARY']}, + writeSynchronizationMode: {type: String, enum: ['FULL_SYNC', 'FULL_ASYNC', 'PRIMARY_SYNC']}, + + sqlEscapeAll: Boolean, + sqlSchema: String, + sqlOnheapRowCacheSize: Number, + longQueryWarningTimeout: Number, + sqlFunctionClasses: [String], + snapshotableIndex: Boolean, + statisticsEnabled: Boolean, + managementEnabled: Boolean, + readFromBackup: Boolean, + copyOnRead: Boolean, + maxConcurrentAsyncOperations: Number, + nearCacheEnabled: Boolean, + nearConfiguration: { + nearStartSize: Number, + nearEvictionPolicy: { + kind: {type: String, enum: ['LRU', 'FIFO', 'Sorted']}, + LRU: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + FIFO: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + SORTED: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + } + } + }, + demo: Boolean + }); + + // Install deep populate plugin. + CacheSchema.plugin(deepPopulate, { + whitelist: ['domains'] + }); + + // Define Cache model. + result.Cache = mongoose.model('Cache', CacheSchema); + + var IgfsSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + name: String, + clusters: [{type: ObjectId, ref: 'Cluster'}], + affinnityGroupSize: Number, + blockSize: Number, + streamBufferSize: Number, + dataCacheName: String, + metaCacheName: String, + defaultMode: {type: String, enum: ['PRIMARY', 'PROXY', 'DUAL_SYNC', 'DUAL_ASYNC']}, + dualModeMaxPendingPutsSize: Number, + dualModePutExecutorService: String, + dualModePutExecutorServiceShutdown: Boolean, + fragmentizerConcurrentFiles: Number, + fragmentizerEnabled: Boolean, + fragmentizerThrottlingBlockLength: Number, + fragmentizerThrottlingDelay: Number, + ipcEndpointConfiguration: { + type: {type: String, enum: ['SHMEM', 'TCP']}, + host: String, + port: Number, + memorySize: Number, + tokenDirectoryPath: String + }, + ipcEndpointEnabled: Boolean, + maxSpaceSize: Number, + maximumTaskRangeLength: Number, + managementPort: Number, + pathModes: [{path: String, mode: {type: String, enum: ['PRIMARY', 'PROXY', 'DUAL_SYNC', 'DUAL_ASYNC']}}], + perNodeBatchSize: Number, + perNodeParallelBatchCount: Number, + prefetchBlocks: Number, + sequentialReadsBeforePrefetch: Number, + trashPurgeTimeout: Number, + secondaryFileSystemEnabled: Boolean, + secondaryFileSystem: { + uri: String, + cfgPath: String, + userName: String + } + }); + + // Define IGFS model. + result.Igfs = mongoose.model('Igfs', IgfsSchema); + + // Define Cluster schema. + var ClusterSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + name: String, + localHost: String, + discovery: { + localAddress: String, + localPort: Number, + localPortRange: Number, + addressResolver: String, + socketTimeout: Number, + ackTimeout: Number, + maxAckTimeout: Number, + networkTimeout: Number, + joinTimeout: Number, + threadPriority: Number, + heartbeatFrequency: Number, + maxMissedHeartbeats: Number, + maxMissedClientHeartbeats: Number, + topHistorySize: Number, + listener: String, + dataExchange: String, + metricsProvider: String, + reconnectCount: Number, + statisticsPrintFrequency: Number, + ipFinderCleanFrequency: Number, + authenticator: String, + forceServerMode: Boolean, + clientReconnectDisabled: Boolean, + kind: {type: String, enum: ['Vm', 'Multicast', 'S3', 'Cloud', 'GoogleStorage', 'Jdbc', 'SharedFs']}, + Vm: { + addresses: [String] + }, + Multicast: { + multicastGroup: String, + multicastPort: Number, + responseWaitTime: Number, + addressRequestAttempts: Number, + localAddress: String, + addresses: [String] + }, + S3: { + bucketName: String + }, + Cloud: { + credential: String, + credentialPath: String, + identity: String, + provider: String, + regions: [String], + zones: [String] + }, + GoogleStorage: { + projectName: String, + bucketName: String, + serviceAccountP12FilePath: String, + serviceAccountId: String, + addrReqAttempts: String + }, + Jdbc: { + initSchema: Boolean + }, + SharedFs: { + path: String + } + }, + atomicConfiguration: { + backups: Number, + cacheMode: {type: String, enum: ['LOCAL', 'REPLICATED', 'PARTITIONED']}, + atomicSequenceReserveSize: Number + }, + binaryConfiguration: { + idMapper: String, + serializer: String, + typeConfigurations: [{typeName: String, idMapper: String, serializer: String, enum: Boolean}], + compactFooter: Boolean + }, + caches: [{type: ObjectId, ref: 'Cache'}], + clockSyncSamples: Number, + clockSyncFrequency: Number, + deploymentMode: {type: String, enum: ['PRIVATE', 'ISOLATED', 'SHARED', 'CONTINUOUS']}, + discoveryStartupDelay: Number, + igfsThreadPoolSize: Number, + igfss: [{type: ObjectId, ref: 'Igfs'}], + includeEventTypes: [String], + managementThreadPoolSize: Number, + marshaller: { + kind: {type: String, enum: ['OptimizedMarshaller', 'JdkMarshaller']}, + OptimizedMarshaller: { + poolSize: Number, + requireSerializable: Boolean + } + }, + marshalLocalJobs: Boolean, + marshallerCacheKeepAliveTime: Number, + marshallerCacheThreadPoolSize: Number, + metricsExpireTime: Number, + metricsHistorySize: Number, + metricsLogFrequency: Number, + metricsUpdateFrequency: Number, + networkTimeout: Number, + networkSendRetryDelay: Number, + networkSendRetryCount: Number, + communication: { + listener: String, + localAddress: String, + localPort: Number, + localPortRange: Number, + sharedMemoryPort: Number, + directBuffer: Boolean, + directSendBuffer: Boolean, + idleConnectionTimeout: Number, + connectTimeout: Number, + maxConnectTimeout: Number, + reconnectCount: Number, + socketSendBuffer: Number, + socketReceiveBuffer: Number, + messageQueueLimit: Number, + slowClientQueueLimit: Number, + tcpNoDelay: Boolean, + ackSendThreshold: Number, + unacknowledgedMessagesBufferSize: Number, + socketWriteTimeout: Number, + selectorsCount: Number, + addressResolver: String + }, + connector: { + enabled: Boolean, + jettyPath: String, + host: String, + port: Number, + portRange: Number, + idleTimeout: Number, + idleQueryCursorTimeout: Number, + idleQueryCursorCheckFrequency: Number, + receiveBufferSize: Number, + sendBufferSize: Number, + directBuffer: Boolean, + noDelay: Boolean, + selectorCount: Number, + threadPoolSize: Number, + messageInterceptor: String, + secretKey: String, + sslEnabled: Boolean, + sslClientAuth: Boolean, + sslFactory: String + }, + peerClassLoadingEnabled: Boolean, + peerClassLoadingLocalClassPathExclude: [String], + peerClassLoadingMissedResourcesCacheSize: Number, + peerClassLoadingThreadPoolSize: Number, + publicThreadPoolSize: Number, + swapSpaceSpi: { + kind: {type: String, enum: ['FileSwapSpaceSpi']}, + FileSwapSpaceSpi: { + baseDirectory: String, + readStripesNumber: Number, + maximumSparsity: Number, + maxWriteQueueSize: Number, + writeBufferSize: Number + } + }, + systemThreadPoolSize: Number, + timeServerPortBase: Number, + timeServerPortRange: Number, + transactionConfiguration: { + defaultTxConcurrency: {type: String, enum: ['OPTIMISTIC', 'PESSIMISTIC']}, + defaultTxIsolation: {type: String, enum: ['READ_COMMITTED', 'REPEATABLE_READ', 'SERIALIZABLE']}, + defaultTxTimeout: Number, + pessimisticTxLogLinger: Number, + pessimisticTxLogSize: Number, + txSerializableEnabled: Boolean, + txManagerFactory: String + }, + sslEnabled: Boolean, + sslContextFactory: { + keyAlgorithm: String, + keyStoreFilePath: String, + keyStoreType: String, + protocol: String, + trustStoreFilePath: String, + trustStoreType: String, + trustManagers: [String] + }, + rebalanceThreadPoolSize: Number + }); + + // Install deep populate plugin. + ClusterSchema.plugin(deepPopulate, { + whitelist: [ + 'caches', + 'caches.domains', + 'igfss' + ] + }); + + // Define Cluster model. + result.Cluster = mongoose.model('Cluster', ClusterSchema); + + result.ClusterDefaultPopulate = ''; + + // Define Notebook schema. + var NotebookSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + name: String, + expandedParagraphs: [Number], + paragraphs: [{ + name: String, + query: String, + editor: Boolean, + result: {type: String, enum: ['none', 'table', 'bar', 'pie', 'line', 'area']}, + pageSize: Number, + timeLineSpan: String, + hideSystemColumns: Boolean, + cacheName: String, + chartsOptions: {barChart: {stacked: Boolean}, areaChart: {style: String}}, + rate: { + value: Number, + unit: Number + } + }] + }); + + // Define Notebook model. + result.Notebook = mongoose.model('Notebook', NotebookSchema); + + result.upsert = function (model, data, cb) { + if (data._id) { + var id = data._id; + + delete data._id; + + model.findOneAndUpdate({_id: id}, data, cb); + } + else + new model(data).save(cb); + }; + + result.processed = function(err, res) { + if (err) { + res.status(500).send(err); + + return false; + } + + return true; + }; + + // Registering the routes of all plugin modules + for (var name in pluginMongo) + if (pluginMongo.hasOwnProperty(name)) + pluginMongo[name].register(mongoose, deepPopulate, result); + + return result; +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/routes/admin.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/routes/admin.js b/modules/control-center-web/src/main/js/serve/routes/admin.js new file mode 100644 index 0000000..a79d584 --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/routes/admin.js @@ -0,0 +1,126 @@ +/* + * 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. + */ + +// Fire me up! + +module.exports = { + implements: 'admin-routes', + inject: ['require(lodash)', 'require(express)', 'require(nodemailer)', 'mongo'] +}; + +module.exports.factory = function (_, express, nodemailer, mongo) { + return new Promise((resolve) => { + const router = express.Router(); + + /** + * Get list of user accounts. + */ + router.post('/list', function (req, res) { + mongo.Account.find({}).sort('username').exec(function (err, users) { + if (err) + return res.status(500).send(err.message); + + res.json(users); + }); + }); + + // Remove user. + router.post('/remove', function (req, res) { + var userId = req.body.userId; + + mongo.Account.findByIdAndRemove(userId, function (err, user) { + if (err) + return res.status(500).send(err.message); + + mongo.Space.find({owner: userId}, function (err, spaces) { + _.forEach(spaces, function (space) { + mongo.Cluster.remove({space: space._id}).exec(); + mongo.Cache.remove({space: space._id}).exec(); + mongo.DomainModel.remove({space: space._id}).exec(); + mongo.Notebook.remove({space: space._id}).exec(); + mongo.Space.remove({owner: space._id}).exec(); + }); + }); + + var transporter = { + service: settings.smtp.service, + auth: { + user: settings.smtp.email, + pass: settings.smtp.password + } + }; + + if (transporter.service != '' || transporter.auth.user != '' || transporter.auth.pass != '') { + var mailer = nodemailer.createTransport(transporter); + + var mailOptions = { + from: settings.smtp.address(settings.smtp.username, settings.smtp.email), + to: settings.smtp.address(user.username, user.email), + subject: 'Your account was deleted', + text: 'You are receiving this e-mail because admin remove your account.\n\n' + + '--------------\n' + + 'Apache Ignite Web Console http://' + req.headers.host + '\n' + }; + + mailer.sendMail(mailOptions, function (err) { + if (err) + return res.status(503).send('Account was removed, but failed to send e-mail notification to user!<br />' + err); + + res.sendStatus(200); + }); + } + else + res.sendStatus(200); + }); + }); + + // Save user. + router.post('/save', function (req, res) { + var userId = req.body.userId; + var adminFlag = req.body.adminFlag; + + mongo.Account.findByIdAndUpdate(userId, {admin: adminFlag}, function (err) { + if (err) + return res.status(500).send(err.message); + + res.sendStatus(200); + }); + }); + + // Become user. + router.get('/become', function (req, res) { + mongo.Account.findById(req.query.viewedUserId).exec(function (err, viewedUser) { + if (err) + return res.sendStatus(404); + + req.session.viewedUser = viewedUser; + + return res.sendStatus(200); + }) + }); + + // Become user. + router.get('/revert/identity', function (req, res) { + req.session.viewedUser = null; + + return res.sendStatus(200); + }); + + resolve(router); + }); +}; + http://git-wip-us.apache.org/repos/asf/ignite/blob/721a1165/modules/control-center-web/src/main/js/serve/routes/agent.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/serve/routes/agent.js b/modules/control-center-web/src/main/js/serve/routes/agent.js new file mode 100644 index 0000000..e5abf0f --- /dev/null +++ b/modules/control-center-web/src/main/js/serve/routes/agent.js @@ -0,0 +1,331 @@ +/* + * 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. + */ + +// Fire me up! + +module.exports = { + implements: 'agent-routes', + inject: ['require(lodash)', 'require(express)', 'require(apache-ignite)', 'require(fs)', 'require(jszip)', 'settings', 'agent'] +}; + +/** + * @param _ + * @param express + * @param apacheIgnite + * @param fs + * @param JSZip + * @param settings + * @param {AgentManager} agent + * @returns {Promise} + */ +module.exports.factory = function (_, express, apacheIgnite, fs, JSZip, settings, agent) { + return new Promise((resolve) => { + const router = express.Router(); + + const SqlFieldsQuery = apacheIgnite.SqlFieldsQuery, ScanQuery = apacheIgnite.ScanQuery; + + const _client = (userId) => { + return new Promise(function (resolve, reject) { + var client = agent.findClient(userId); + + if (client) + return resolve(client); + + reject({code: 503, message: 'Connection to Ignite Web Agent is not established'}); + }); + }; + + const _compact = (className) => { + return className.replace('java.lang.', '').replace('java.util.', '').replace('java.sql.', ''); + }; + + const _handleException = (res) => { + return function (error) { + if (_.isObject(error)) + return res.status(error.code).send(error.message); + + return res.status(500).send(error); + } + }; + + /* Get grid topology. */ + router.get('/download/zip', function (req, res) { + var agentFld = settings.agentFile; + var agentZip = agentFld + '.zip'; + var agentPathZip = 'public/agent/' + agentFld + '.zip'; + + fs.stat(agentPathZip, function (err, stats) { + if (err) + return res.download(agentPathZip, agentZip); + + // Read a zip file. + fs.readFile(agentPathZip, function (err, data) { + if (err) + return res.download(agentPathZip, agentZip); + + var zip = new JSZip(data); + + var prop = []; + + var host = req.hostname.match(/:/g) ? req.hostname.slice(0, req.hostname.indexOf(':')) : req.hostname; + + prop.push('token=' + req.user.token); + prop.push('server-uri=wss://' + host + ':' + settings.agentPort); + prop.push('#Uncomment following options if needed:'); + prop.push('#node-uri=http://localhost:8080'); + prop.push('#driver-folder=./jdbc-drivers'); + prop.push(''); + prop.push("#Note: Don't change this auto generated line"); + prop.push('rel-date=' + stats.birthtime.getTime()); + + zip.file(agentFld + '/default.properties', prop.join('\n')); + + var buffer = zip.generate({type: 'nodebuffer', platform: 'UNIX'}); + + // Set the archive name. + res.attachment(agentZip); + + res.send(buffer); + }); + }); + }); + + /* Get grid topology. */ + router.post('/topology', function (req, res) { + _client(req.currentUserId()) + .then((client) => client.ignite(req.body.demo).cluster(req.body.attr, req.body.mtr)) + .then((clusters) => res.json(clusters)) + .catch(_handleException(res)); + }); + + /* Execute query. */ + router.post('/query', function (req, res) { + _client(req.currentUserId()) + .then((client) => { + // Create sql query. + var qry = new SqlFieldsQuery(req.body.query); + + // Set page size for query. + qry.setPageSize(req.body.pageSize); + + return client.ignite(req.body.demo).cache(req.body.cacheName).query(qry).nextPage() + }) + .then((cursor) => res.json({ + meta: cursor.fieldsMetadata(), + rows: cursor.page(), + queryId: cursor.queryId() + })) + .catch(_handleException(res)); + }); + + /* Execute query getAll. */ + router.post('/query/getAll', function (req, res) { + _client(req.currentUserId()) + .then((client) => { + // Create sql query. + const qry = req.body.query ? new SqlFieldsQuery(req.body.query) : new ScanQuery(); + + // Set page size for query. + qry.setPageSize(1024); + + // Get query cursor. + const cursor = client.ignite(req.body.demo).cache(req.body.cacheName).query(qry); + + return new Promise(function (resolve) { + cursor.getAll().then(rows => resolve({meta: cursor.fieldsMetadata(), rows})) + }); + }) + .then(response => res.json(response)) + .catch(_handleException(res)); + }); + + /* Execute query. */ + router.post('/scan', function (req, res) { + _client(req.currentUserId()) + .then((client) => { + // Create sql query. + var qry = new ScanQuery(); + + // Set page size for query. + qry.setPageSize(req.body.pageSize); + + // Get query cursor. + return client.ignite(req.body.demo).cache(req.body.cacheName).query(qry).nextPage() + }) + .then((cursor) => res.json({ + meta: cursor.fieldsMetadata(), + rows: cursor.page(), + queryId: cursor.queryId() + })) + .catch(_handleException(res)); + }); + + /* Get next query page. */ + router.post('/query/fetch', function (req, res) { + _client(req.currentUserId()) + .then((client) => { + var cache = client.ignite(req.body.demo).cache(req.body.cacheName); + + var cmd = cache._createCommand('qryfetch') + .addParam('qryId', req.body.queryId) + .addParam('pageSize', req.body.pageSize); + + return cache.__createPromise(cmd); + }) + .then((page) => res.json({rows: page['items'], last: page === null || page['last']})) + .catch(_handleException(res)); + }); + + /* Close query cursor by id. */ + router.post('/query/close', function (req, res) { + _client(req.currentUserId()) + .then((client) => { + var cache = client.ignite(req.body.demo).cache(req.body.cacheName); + + return cache.__createPromise(cache._createCommand('qrycls').addParam('qryId', req.body.queryId)) + }) + .then(() => res.sendStatus(200)) + .catch(_handleException(res)); + }); + + /* Get metadata for cache. */ + router.post('/cache/metadata', function (req, res) { + _client(req.currentUserId()) + .then((client) => client.ignite(req.body.demo).cache(req.body.cacheName).metadata()) + .then((caches) => { + var types = []; + + for (var meta of caches) { + var cacheTypes = meta.types.map(function (typeName) { + var fields = meta.fields[typeName]; + + var columns = []; + + for (var fieldName in fields) { + var fieldClass = _compact(fields[fieldName]); + + columns.push({ + type: 'field', + name: fieldName, + clazz: fieldClass, + system: fieldName == "_KEY" || fieldName == "_VAL", + cacheName: meta.cacheName, + typeName: typeName + }); + } + + var indexes = []; + + for (var index of meta.indexes[typeName]) { + fields = []; + + for (var field of index.fields) { + fields.push({ + type: 'index-field', + name: field, + order: index.descendings.indexOf(field) < 0, + unique: index.unique, + cacheName: meta.cacheName, + typeName: typeName + }); + } + + if (fields.length > 0) + indexes.push({ + type: 'index', + name: index.name, + children: fields, + cacheName: meta.cacheName, + typeName: typeName + }); + } + + columns = _.sortBy(columns, 'name'); + + if (!_.isEmpty(indexes)) + columns = columns.concat({ + type: 'indexes', + name: 'Indexes', + cacheName: meta.cacheName, + typeName: typeName, + children: indexes + }); + + return { + type: 'type', + cacheName: meta.cacheName || "", + typeName: typeName, + children: columns + }; + }); + + if (!_.isEmpty(cacheTypes)) + types = types.concat(cacheTypes); + } + + res.json(types); + }) + .catch(_handleException(res)); + }); + + /* Ping client. */ + router.post('/ping', function (req, res) { + _client(req.currentUserId()) + .then(() => res.sendStatus(200)) + .catch(_handleException(res)); + }); + + /* Get JDBC drivers list. */ + router.post('/drivers', function (req, res) { + _client(req.currentUserId()) + .then((client) => client.availableDrivers()) + .then((arr) => res.json(arr)) + .catch(_handleException(res)); + }); + + /** Get database schemas. */ + router.post('/schemas', function (req, res) { + _client(req.currentUserId()) + .then((client) => { + var args = req.body; + + args.jdbcInfo = {user: args.user, password: args.password}; + + return client.metadataSchemas(args.jdbcDriverJar, args.jdbcDriverClass, args.jdbcUrl, args.jdbcInfo) + }) + .then((arr) => res.json(arr)) + .catch(_handleException(res)); + }); + + /** Get database tables. */ + router.post('/tables', function (req, res) { + _client(req.currentUserId()) + .then((client) => { + var args = req.body; + + args.jdbcInfo = {user: args.user, password: args.password}; + + return client.metadataTables(args.jdbcDriverJar, args.jdbcDriverClass, args.jdbcUrl, args.jdbcInfo, args.schemas, args.tablesOnly) + }) + .then((arr) => res.json(arr)) + .catch(_handleException(res)); + }); + + resolve(router); + }); +}; +
