http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/mail.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/mail.js b/modules/web-console/src/main/js/serve/mail.js new file mode 100644 index 0000000..2c67276 --- /dev/null +++ b/modules/web-console/src/main/js/serve/mail.js @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +// Fire me up! + +/** + * Module for send email. + */ +module.exports = { + implements: 'mail', + inject: ['require(nodemailer)', 'settings'] +}; + +module.exports.factory = function(nodemailer, settings) { + return { + /** + * Send mail to user. + * + * @param {Account} user + * @param {String} subject + * @param {String} html + * @param {String} sendErr + * @throws {Error} + * @return {Promise} + */ + send: (user, subject, html, sendErr) => { + const transporter = { + service: settings.smtp.service, + auth: { + user: settings.smtp.email, + pass: settings.smtp.password + } + }; + + if (transporter.service === '' || transporter.auth.user === '' || transporter.auth.pass === '') + throw new Error('Failed to send email. SMTP server is not configured. Please ask webmaster to setup SMTP server!'); + + const mailer = nodemailer.createTransport(transporter); + + const sign = settings.smtp.sign ? `<br><br>--------------<br>${settings.smtp.sign}<br>` : ''; + + const mail = { + from: settings.smtp.address(settings.smtp.username, settings.smtp.email), + to: settings.smtp.address(`${user.firstName} ${user.lastName}`, user.email), + subject, + html: html + sign + }; + + return new Promise((resolve, reject) => { + mailer.sendMail(mail, (err) => { + if (err) + return reject(sendErr ? new Error(sendErr) : err); + + resolve(user); + }); + }); + } + }; +};
http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/mongo.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/mongo.js b/modules/web-console/src/main/js/serve/mongo.js new file mode 100644 index 0000000..81b4188 --- /dev/null +++ b/modules/web-console/src/main/js/serve/mongo.js @@ -0,0 +1,620 @@ +/* + * 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 mongo schema. + */ +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) { + const mongoose = require('mongoose'); + + // Use native promises + mongoose.Promise = global.Promise; + + const deepPopulate = deepPopulatePlugin(mongoose); + + // Connect to mongoDB database. + mongoose.connect(settings.mongoUrl, {server: {poolSize: 4}}); + + const Schema = mongoose.Schema; + const ObjectId = mongoose.Schema.Types.ObjectId; + const result = { connection: mongoose.connection }; + + result.ObjectId = ObjectId; + + // Define Account schema. + const AccountSchema = new Schema({ + firstName: String, + lastName: 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: (doc, ret) => { + return { + _id: ret._id, + email: ret.email, + firstName: ret.firstName, + lastName: ret.lastName, + 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'}, + demo: {type: Boolean, default: false}, + usedBy: [{ + permission: {type: String, enum: ['VIEW', 'FULL']}, + account: {type: ObjectId, ref: 'Account'} + }] + })); + + // Define Domain model schema. + const DomainModelSchema = new Schema({ + space: {type: ObjectId, ref: 'Space', index: true}, + 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. + const CacheSchema = new Schema({ + space: {type: ObjectId, ref: 'Space', index: true}, + 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); + + const IgfsSchema = new Schema({ + space: {type: ObjectId, ref: 'Space', index: true}, + 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 + }, + colocateMetadata: Boolean, + relaxedConsistency: Boolean + }); + + // Define IGFS model. + result.Igfs = mongoose.model('Igfs', IgfsSchema); + + // Define Cluster schema. + const ClusterSchema = new Schema({ + space: {type: ObjectId, ref: 'Space', index: true}, + 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', 'ZooKeeper']}, + 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 + }, + ZooKeeper: { + curator: String, + zkConnectionString: String, + retryPolicy: { + kind: {type: String, enum: ['ExponentialBackoff', 'BoundedExponentialBackoff', 'UntilElapsed', + 'NTimes', 'OneTime', 'Forever', 'Custom']}, + ExponentialBackoff: { + baseSleepTimeMs: Number, + maxRetries: Number, + maxSleepMs: Number + }, + BoundedExponentialBackoff: { + baseSleepTimeMs: Number, + maxSleepTimeMs: Number, + maxRetries: Number + }, + UntilElapsed: { + maxElapsedTimeMs: Number, + sleepMsBetweenRetries: Number + }, + NTimes: { + n: Number, + sleepMsBetweenRetries: Number + }, + OneTime: { + sleepMsBetweenRetry: Number + }, + Forever: { + retryIntervalMs: Number + }, + Custom: { + className: String + } + }, + basePath: String, + serviceName: String, + allowDuplicateRegistrations: Boolean + } + }, + atomicConfiguration: { + backups: Number, + cacheMode: {type: String, enum: ['LOCAL', 'REPLICATED', 'PARTITIONED']}, + atomicSequenceReserveSize: Number + }, + binaryConfiguration: { + idMapper: String, + nameMapper: String, + serializer: String, + typeConfigurations: [{ + typeName: String, + idMapper: String, + nameMapper: 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, + sendQueueLimit: 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. + const NotebookSchema = new Schema({ + space: {type: ObjectId, ref: 'Space', index: true}, + 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.handleError = function(res, err) { + // TODO IGNITE-843 Send error to admin + res.status(err.code || 500).send(err.message); + }; + + /** + * Query for user spaces. + * + * @param userId User ID. + * @param {Boolean} demo Is need use demo space. + * @returns {Promise} + */ + result.spaces = function(userId, demo) { + return result.Space.find({owner: userId, demo: !!demo}).lean().exec(); + }; + + /** + * Extract IDs from user spaces. + * + * @param userId User ID. + * @param {Boolean} demo Is need use demo space. + * @returns {Promise} + */ + result.spaceIds = function(userId, demo) { + return result.spaces(userId, demo) + .then((spaces) => spaces.map((space) => space._id)); + }; + + // Registering the routes of all plugin modules + for (const 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/eb5ac0ae/modules/web-console/src/main/js/serve/routes/admin.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/admin.js b/modules/web-console/src/main/js/serve/routes/admin.js new file mode 100644 index 0000000..3c2e728 --- /dev/null +++ b/modules/web-console/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. + */ + +'use strict'; + +// Fire me up! + +module.exports = { + implements: 'admin-routes', + inject: ['require(lodash)', 'require(express)', 'settings', 'mail', 'mongo'] +}; + +module.exports.factory = function(_, express, settings, mail, mongo) { + return new Promise((factoryResolve) => { + const router = new express.Router(); + + /** + * Get list of user accounts. + */ + router.post('/list', (req, res) => { + + Promise.all([ + mongo.Space.aggregate([ + {$match: {demo: false}}, + {$lookup: {from: 'clusters', localField: '_id', foreignField: 'space', as: 'clusters'}}, + {$lookup: {from: 'caches', localField: '_id', foreignField: 'space', as: 'caches'}}, + {$lookup: {from: 'domainmodels', localField: '_id', foreignField: 'space', as: 'domainmodels'}}, + {$lookup: {from: 'igfs', localField: '_id', foreignField: 'space', as: 'igfs'}}, + {$project: { + owner: 1, + clusters: {$size: '$clusters'}, + models: {$size: '$domainmodels'}, + caches: {$size: '$caches'}, + igfs: {$size: '$igfs'} + }} + ]).exec(), + mongo.Account.find({}).sort('firstName lastName').lean().exec() + ]) + .then((values) => { + const counters = _.keyBy(values[0], 'owner'); + const accounts = values[1]; + + return accounts.map((account) => { + account.counters = _.omit(counters[account._id], '_id', 'owner'); + + return account; + }); + }) + .then((users) => res.json(users)) + .catch((err) => mongo.handleError(res, err)); + }); + + // Remove user. + router.post('/remove', (req, res) => { + const userId = req.body.userId; + + mongo.Account.findByIdAndRemove(userId).exec() + .then((user) => { + res.sendStatus(200); + + return mongo.spaceIds(userId) + .then((spaceIds) => Promise.all([ + mongo.Cluster.remove({space: {$in: spaceIds}}).exec(), + mongo.Cache.remove({space: {$in: spaceIds}}).exec(), + mongo.DomainModel.remove({space: {$in: spaceIds}}).exec(), + mongo.Igfs.remove({space: {$in: spaceIds}}).exec(), + mongo.Notebook.remove({space: {$in: spaceIds}}).exec(), + mongo.Space.remove({owner: userId}).exec() + ])) + .then(() => user) + .catch((err) => console.error(`Failed to cleanup spaces [user=${user.username}, err=${err}`)); + }) + .then((user) => + mail.send(user, 'Your account was deleted', + `Hello ${user.firstName} ${user.lastName}!<br><br>` + + `You are receiving this email because your account for <a href="http://${req.headers.host}">${settings.smtp.username}</a> was removed.`, + 'Account was removed, but failed to send email notification to user!') + ) + .catch((err) => mongo.handleError(res, err)); + }); + + // Save user. + router.post('/save', (req, res) => { + const params = req.body; + + mongo.Account.findByIdAndUpdate(params.userId, {admin: params.adminFlag}).exec() + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + // Become user. + router.get('/become', (req, res) => { + mongo.Account.findById(req.query.viewedUserId).exec() + .then((viewedUser) => { + req.session.viewedUser = viewedUser; + + res.sendStatus(200); + }) + .catch(() => res.sendStatus(404)); + }); + + // Revert to your identity. + router.get('/revert/identity', (req, res) => { + req.session.viewedUser = null; + + return res.sendStatus(200); + }); + + factoryResolve(router); + }); +}; + http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/agent.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/agent.js b/modules/web-console/src/main/js/serve/routes/agent.js new file mode 100644 index 0000000..8fd8b75 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/agent.js @@ -0,0 +1,82 @@ +/* + * 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: 'agent-routes', + inject: ['require(lodash)', 'require(express)', 'require(fs)', 'require(jszip)', 'settings', 'agent-manager'] +}; + +/** + * @param _ + * @param express + * @param fs + * @param JSZip + * @param settings + * @param {AgentManager} agentMgr + * @returns {Promise} + */ +module.exports.factory = function(_, express, fs, JSZip, settings, agentMgr) { + return new Promise((resolveFactory) => { + const router = new express.Router(); + + /* Get grid topology. */ + router.get('/download/zip', (req, res) => { + const latest = agentMgr.supportedAgents.latest; + + if (_.isEmpty(latest)) + return res.status(500).send('Missing agent zip on server. Please ask webmaster to upload agent zip!'); + + const agentFld = latest.fileName.substr(0, latest.fileName.length - 4); + const agentZip = latest.fileName; + const agentPathZip = latest.filePath; + + // Read a zip file. + fs.readFile(agentPathZip, (errFs, data) => { + if (errFs) + return res.download(agentPathZip, agentZip); + + const zip = new JSZip(data); + + const prop = []; + + const host = req.hostname.match(/:/g) ? req.hostname.slice(0, req.hostname.indexOf(':')) : req.hostname; + + prop.push('token=' + req.user.token); + prop.push('server-uri=' + (settings.agent.SSLOptions ? 'https' : 'http') + '://' + host + ':' + settings.agent.port); + prop.push('#Uncomment following options if needed:'); + prop.push('#node-uri=http://localhost:8080'); + prop.push('#driver-folder=./jdbc-drivers'); + + zip.file(agentFld + '/default.properties', prop.join('\n')); + + const buffer = zip.generate({type: 'nodebuffer', platform: 'UNIX'}); + + // Set the archive name. + res.attachment(agentZip); + + res.send(buffer); + }); + }); + + resolveFactory(router); + }); +}; + http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/caches.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/caches.js b/modules/web-console/src/main/js/serve/routes/caches.js new file mode 100644 index 0000000..61a0cfb --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/caches.js @@ -0,0 +1,132 @@ +/* + * 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: 'caches-routes', + inject: ['require(lodash)', 'require(express)', 'mongo'] +}; + +module.exports.factory = function(_, express, mongo) { + return new Promise((factoryResolve) => { + const router = new express.Router(); + + /** + * Get spaces and caches accessed for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/list', (req, res) => { + const result = {}; + let spaceIds = []; + + // Get owned space and all accessed space. + mongo.spaces(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaces) => { + result.spaces = spaces; + spaceIds = spaces.map((space) => space._id); + + return mongo.Cluster.find({space: {$in: spaceIds}}).sort('name').lean().exec(); + }) + .then((clusters) => { + result.clusters = clusters; + + return mongo.DomainModel.find({space: {$in: spaceIds}}).sort('name').lean().exec(); + }) + .then((domains) => { + result.domains = domains; + + return mongo.Cache.find({space: {$in: spaceIds}}).sort('name').lean().exec(); + }) + .then((caches) => { + result.caches = caches; + + res.json(result); + }) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Save cache. + */ + router.post('/save', (req, res) => { + const params = req.body; + const clusters = params.clusters; + const domains = params.domains; + + mongo.Cache.findOne({space: params.space, name: params.name}).exec() + .then((existingCache) => { + const cacheId = params._id; + + if (existingCache && cacheId !== existingCache._id.toString()) + return res.status(500).send('Cache with name: "' + existingCache.name + '" already exist.'); + + if (cacheId) { + return mongo.Cache.update({_id: cacheId}, params, {upsert: true}).exec() + .then(() => mongo.Cluster.update({_id: {$in: clusters}}, {$addToSet: {caches: cacheId}}, {multi: true}).exec()) + .then(() => mongo.Cluster.update({_id: {$nin: clusters}}, {$pull: {caches: cacheId}}, {multi: true}).exec()) + .then(() => mongo.DomainModel.update({_id: {$in: domains}}, {$addToSet: {caches: cacheId}}, {multi: true}).exec()) + .then(() => mongo.DomainModel.update({_id: {$nin: domains}}, {$pull: {caches: cacheId}}, {multi: true}).exec()) + .then(() => res.send(cacheId)); + } + + return (new mongo.Cache(params)).save() + .then((cache) => + mongo.Cluster.update({_id: {$in: clusters}}, {$addToSet: {caches: cacheId}}, {multi: true}).exec() + .then(() => mongo.DomainModel.update({_id: {$in: domains}}, {$addToSet: {caches: cacheId}}, {multi: true}).exec()) + .then(() => res.send(cache._id)) + ); + }) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove cache by ._id. + */ + router.post('/remove', (req, res) => { + const params = req.body; + const cacheId = params._id; + + mongo.Cluster.update({caches: {$in: [cacheId]}}, {$pull: {caches: cacheId}}, {multi: true}).exec() + .then(() => mongo.DomainModel.update({caches: {$in: [cacheId]}}, {$pull: {caches: cacheId}}, {multi: true}).exec()) + .then(() => mongo.Cache.remove(params).exec()) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove all caches. + */ + router.post('/remove/all', (req, res) => { + mongo.spaceIds(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaceIds) => + mongo.Cluster.update({space: {$in: spaceIds}}, {caches: []}, {multi: true}).exec() + .then(() => mongo.DomainModel.update({space: {$in: spaceIds}}, {caches: []}, {multi: true}).exec()) + .then(() => mongo.Cache.remove({space: {$in: spaceIds}}).exec()) + ) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + factoryResolve(router); + }); +}; + http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/clusters.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/clusters.js b/modules/web-console/src/main/js/serve/routes/clusters.js new file mode 100644 index 0000000..bfe89d3 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/clusters.js @@ -0,0 +1,146 @@ +/* + * 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: 'clusters-routes', + inject: ['require(lodash)', 'require(express)', 'mongo'] +}; + +module.exports.factory = function(_, express, mongo) { + return new Promise((factoryResolve) => { + const router = new express.Router(); + + /** + * Get spaces and clusters accessed for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/list', (req, res) => { + const result = {}; + let spaceIds = []; + let domains = {}; + + mongo.spaces(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaces) => { + result.spaces = spaces; + spaceIds = spaces.map((space) => space._id); + + return mongo.DomainModel.find({space: {$in: spaceIds}}).lean().exec(); + }) + .then((_domains) => { + domains = _domains.reduce((map, obj) => { + map[obj._id] = obj; + + return map; + }, {}); + + return mongo.Cache.find({space: {$in: spaceIds}}).sort('name').lean().exec(); + }) + .then((caches) => { + _.forEach(caches, (cache) => { + cache.domains = _.map(cache.domains, (domainId) => domains[domainId]); + }); + + result.caches = caches; + + return mongo.Igfs.find({space: {$in: spaceIds}}).sort('name').lean().exec(); + }) + .then((igfss) => { + result.igfss = igfss; + + return mongo.Cluster.find({space: {$in: spaceIds}}).sort('name').deepPopulate(mongo.ClusterDefaultPopulate).lean().exec(); + }) + .then((clusters) => { + result.clusters = clusters; + + res.json(result); + }) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Save cluster. + */ + router.post('/save', (req, res) => { + const params = req.body; + const caches = params.caches; + const igfss = params.igfss; + + mongo.Cluster.findOne({space: params.space, name: params.name}).exec() + .then((existingCluster) => { + const clusterId = params._id; + + if (existingCluster && clusterId !== existingCluster._id.toString()) + throw new Error('Cluster with name: "' + existingCluster.name + '" already exist.'); + + if (clusterId) { + return mongo.Cluster.update({_id: clusterId}, params, {upsert: true}).exec() + .then(() => mongo.Cache.update({_id: {$in: caches}}, {$addToSet: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => mongo.Cache.update({_id: {$nin: caches}}, {$pull: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => mongo.Igfs.update({_id: {$in: igfss}}, {$addToSet: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => mongo.Igfs.update({_id: {$nin: igfss}}, {$pull: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => res.send(clusterId)); + } + + return (new mongo.Cluster(params)).save() + .then((cluster) => + mongo.Cache.update({_id: {$in: caches}}, {$addToSet: {clusters: clusterId}}, {multi: true}).exec() + .then(() => mongo.Cache.update({_id: {$nin: caches}}, {$pull: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => mongo.Igfs.update({_id: {$in: igfss}}, {$addToSet: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => mongo.Igfs.update({_id: {$nin: igfss}}, {$pull: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => res.send(cluster._id)) + ); + }) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove cluster by ._id. + */ + router.post('/remove', (req, res) => { + const params = req.body; + const clusterId = params._id; + + mongo.Cache.update({clusters: {$in: [clusterId]}}, {$pull: {clusters: clusterId}}, {multi: true}).exec() + .then(() => mongo.Igfs.update({clusters: {$in: [clusterId]}}, {$pull: {clusters: clusterId}}, {multi: true}).exec()) + .then(() => mongo.Cluster.remove(params).exec()) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove all clusters. + */ + router.post('/remove/all', (req, res) => { + // Get owned space and all accessed space. + mongo.spaceIds(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaceIds) => mongo.Cache.update({space: {$in: spaceIds}}, {clusters: []}, {multi: true}).exec() + .then(() => mongo.Igfs.update({space: {$in: spaceIds}}, {clusters: []}, {multi: true}).exec()) + .then(() => mongo.Cluster.remove({space: {$in: spaceIds}}).exec()) + ) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + factoryResolve(router); + }); +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/demo.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/demo.js b/modules/web-console/src/main/js/serve/routes/demo.js new file mode 100644 index 0000000..dd47eb9 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/demo.js @@ -0,0 +1,135 @@ +/* + * 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: 'demo-routes', + inject: [ + 'require(lodash)', + 'require(express)', + 'settings', + 'mongo', + 'require(./demo/domains.json)', + 'require(./demo/caches.json)', + 'require(./demo/igfss.json)', + 'require(./demo/clusters.json)' + ] +}; + +module.exports.factory = (_, express, settings, mongo, domains, caches, igfss, clusters) => { + return new Promise((factoryResolve) => { + const router = new express.Router(); + + /** + * Reset demo configuration. + */ + router.post('/reset', (req, res) => { + mongo.spaces(req.user._id, true) + .then((spaces) => { + if (spaces.length) { + const spaceIds = spaces.map((space) => space._id); + + return Promise.all([ + mongo.Cluster.remove({space: {$in: spaceIds}}).exec(), + mongo.Cache.remove({space: {$in: spaceIds}}).exec(), + mongo.DomainModel.remove({space: {$in: spaceIds}}).exec(), + mongo.Igfs.remove({space: {$in: spaceIds}}).exec() + ]).then(() => spaces[0]); + } + + return new mongo.Space({name: 'Demo space', owner: req.user._id, demo: true}).save(); + }) + .then((space) => { + return Promise.all(_.map(clusters, (cluster) => { + const clusterDoc = new mongo.Cluster(cluster); + + clusterDoc.space = space._id; + + return clusterDoc.save(); + })); + }) + .then((clusterDocs) => { + return _.map(clusterDocs, (cluster) => { + const addCacheToCluster = (cacheDoc) => cluster.caches.push(cacheDoc._id); + const addIgfsToCluster = (igfsDoc) => cluster.igfss.push(igfsDoc._id); + + if (cluster.name.endsWith('-caches')) { + const cachePromises = _.map(caches, (cacheData) => { + const cache = new mongo.Cache(cacheData); + + cache.space = cluster.space; + cache.clusters.push(cluster._id); + + return cache.save() + .then((cacheDoc) => { + const domainData = _.find(domains, (item) => + item.databaseTable === cacheDoc.name.slice(0, -5).toUpperCase()); + + if (domainData) { + const domain = new mongo.DomainModel(domainData); + + domain.space = cacheDoc.space; + domain.caches.push(cacheDoc._id); + + return domain.save() + .then((domainDoc) => { + cacheDoc.domains.push(domainDoc._id); + + return cacheDoc.save(); + }); + } + + return cacheDoc; + }); + }); + + return Promise.all(cachePromises) + .then((cacheDocs) => { + _.forEach(cacheDocs, addCacheToCluster); + + return cluster.save(); + }); + } + + if (cluster.name.endsWith('-igfs')) { + return Promise.all(_.map(igfss, (igfs) => { + const igfsDoc = new mongo.Igfs(igfs); + + igfsDoc.space = cluster.space; + igfsDoc.clusters.push(cluster._id); + + return igfsDoc.save(); + })) + .then((igfsDocs) => { + _.forEach(igfsDocs, addIgfsToCluster); + + return cluster.save(); + }); + } + }); + }) + .then(() => res.sendStatus(200)) + .catch((err) => res.status(500).send(err.message)); + }); + + factoryResolve(router); + }); +}; + http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/demo/caches.json ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/demo/caches.json b/modules/web-console/src/main/js/serve/routes/demo/caches.json new file mode 100644 index 0000000..f7a8690 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/demo/caches.json @@ -0,0 +1,87 @@ +[ + { + "name": "CarCache", + "cacheMode": "PARTITIONED", + "atomicityMode": "ATOMIC", + "readThrough": true, + "writeThrough": true, + "sqlFunctionClasses": [], + "cacheStoreFactory": { + "kind": "CacheJdbcPojoStoreFactory", + "CacheJdbcPojoStoreFactory": { + "dataSourceBean": "dsH2", + "dialect": "H2" + } + }, + "domains": [], + "clusters": [] + }, + { + "name": "ParkingCache", + "cacheMode": "PARTITIONED", + "atomicityMode": "ATOMIC", + "readThrough": true, + "writeThrough": true, + "sqlFunctionClasses": [], + "cacheStoreFactory": { + "kind": "CacheJdbcPojoStoreFactory", + "CacheJdbcPojoStoreFactory": { + "dataSourceBean": "dsH2", + "dialect": "H2" + } + }, + "domains": [], + "clusters": [] + }, + { + "name": "CountryCache", + "cacheMode": "PARTITIONED", + "atomicityMode": "ATOMIC", + "readThrough": true, + "writeThrough": true, + "sqlFunctionClasses": [], + "cacheStoreFactory": { + "kind": "CacheJdbcPojoStoreFactory", + "CacheJdbcPojoStoreFactory": { + "dataSourceBean": "dsH2", + "dialect": "H2" + } + }, + "domains": [], + "clusters": [] + }, + { + "name": "DepartmentCache", + "cacheMode": "PARTITIONED", + "atomicityMode": "ATOMIC", + "readThrough": true, + "writeThrough": true, + "sqlFunctionClasses": [], + "cacheStoreFactory": { + "kind": "CacheJdbcPojoStoreFactory", + "CacheJdbcPojoStoreFactory": { + "dataSourceBean": "dsH2", + "dialect": "H2" + } + }, + "domains": [], + "clusters": [] + }, + { + "name": "EmployeeCache", + "cacheMode": "PARTITIONED", + "atomicityMode": "ATOMIC", + "readThrough": true, + "writeThrough": true, + "sqlFunctionClasses": [], + "cacheStoreFactory": { + "kind": "CacheJdbcPojoStoreFactory", + "CacheJdbcPojoStoreFactory": { + "dataSourceBean": "dsH2", + "dialect": "H2" + } + }, + "domains": [], + "clusters": [] + } +] http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/demo/clusters.json ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/demo/clusters.json b/modules/web-console/src/main/js/serve/routes/demo/clusters.json new file mode 100644 index 0000000..014b519 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/demo/clusters.json @@ -0,0 +1,50 @@ +[ + { + "name": "cluster-igfs", + "connector": { + "noDelay": true + }, + "communication": { + "tcpNoDelay": true + }, + "igfss": [], + "caches": [], + "binaryConfiguration": { + "compactFooter": true, + "typeConfigurations": [] + }, + "discovery": { + "kind": "Multicast", + "Multicast": { + "addresses": ["127.0.0.1:47500..47510"] + }, + "Vm": { + "addresses": ["127.0.0.1:47500..47510"] + } + } + }, + { + "name": "cluster-caches", + "connector": { + "noDelay": true + }, + "communication": { + "tcpNoDelay": true + }, + "igfss": [], + "caches": [], + "binaryConfiguration": { + "compactFooter": true, + "typeConfigurations": [] + }, + "discovery": { + "kind": "Multicast", + "Multicast": { + "addresses": ["127.0.0.1:47500..47510"] + }, + "Vm": { + "addresses": ["127.0.0.1:47500..47510"] + } + } + } +] http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/demo/domains.json ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/demo/domains.json b/modules/web-console/src/main/js/serve/routes/demo/domains.json new file mode 100644 index 0000000..980d8d1 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/demo/domains.json @@ -0,0 +1,307 @@ +[ + { + "keyType": "Integer", + "valueType": "model.Parking", + "queryMetadata": "Configuration", + "databaseSchema": "CARS", + "databaseTable": "PARKING", + "indexes": [], + "aliases": [], + "fields": [ + { + "name": "name", + "className": "String" + }, + { + "name": "capacity", + "className": "Integer" + } + ], + "valueFields": [ + { + "databaseFieldName": "NAME", + "databaseFieldType": "VARCHAR", + "javaFieldName": "name", + "javaFieldType": "String" + }, + { + "databaseFieldName": "CAPACITY", + "databaseFieldType": "INTEGER", + "javaFieldName": "capacity", + "javaFieldType": "int" + } + ], + "keyFields": [ + { + "databaseFieldName": "ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "id", + "javaFieldType": "int" + } + ], + "caches": [] + }, + { + "keyType": "Integer", + "valueType": "model.Department", + "queryMetadata": "Configuration", + "databaseSchema": "PUBLIC", + "databaseTable": "DEPARTMENT", + "indexes": [], + "aliases": [], + "fields": [ + { + "name": "countryId", + "className": "Integer" + }, + { + "name": "name", + "className": "String" + } + ], + "valueFields": [ + { + "databaseFieldName": "COUNTRY_ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "countryId", + "javaFieldType": "int" + }, + { + "databaseFieldName": "NAME", + "databaseFieldType": "VARCHAR", + "javaFieldName": "name", + "javaFieldType": "String" + } + ], + "keyFields": [ + { + "databaseFieldName": "ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "id", + "javaFieldType": "int" + } + ], + "caches": [] + }, + { + "keyType": "Integer", + "valueType": "model.Employee", + "queryMetadata": "Configuration", + "databaseSchema": "PUBLIC", + "databaseTable": "EMPLOYEE", + "indexes": [ + { + "name": "EMP_NAMES", + "indexType": "SORTED", + "fields": [ + { + "name": "firstName", + "direction": true + }, + { + "name": "lastName", + "direction": true + } + ] + }, + { + "name": "EMP_SALARY", + "indexType": "SORTED", + "fields": [ + { + "name": "salary", + "direction": true + } + ] + } + ], + "aliases": [], + "fields": [ + { + "name": "departmentId", + "className": "Integer" + }, + { + "name": "managerId", + "className": "Integer" + }, + { + "name": "firstName", + "className": "String" + }, + { + "name": "lastName", + "className": "String" + }, + { + "name": "email", + "className": "String" + }, + { + "name": "phoneNumber", + "className": "String" + }, + { + "name": "hireDate", + "className": "Date" + }, + { + "name": "job", + "className": "String" + }, + { + "name": "salary", + "className": "Double" + } + ], + "valueFields": [ + { + "databaseFieldName": "DEPARTMENT_ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "departmentId", + "javaFieldType": "int" + }, + { + "databaseFieldName": "MANAGER_ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "managerId", + "javaFieldType": "Integer" + }, + { + "databaseFieldName": "FIRST_NAME", + "databaseFieldType": "VARCHAR", + "javaFieldName": "firstName", + "javaFieldType": "String" + }, + { + "databaseFieldName": "LAST_NAME", + "databaseFieldType": "VARCHAR", + "javaFieldName": "lastName", + "javaFieldType": "String" + }, + { + "databaseFieldName": "EMAIL", + "databaseFieldType": "VARCHAR", + "javaFieldName": "email", + "javaFieldType": "String" + }, + { + "databaseFieldName": "PHONE_NUMBER", + "databaseFieldType": "VARCHAR", + "javaFieldName": "phoneNumber", + "javaFieldType": "String" + }, + { + "databaseFieldName": "HIRE_DATE", + "databaseFieldType": "DATE", + "javaFieldName": "hireDate", + "javaFieldType": "Date" + }, + { + "databaseFieldName": "JOB", + "databaseFieldType": "VARCHAR", + "javaFieldName": "job", + "javaFieldType": "String" + }, + { + "databaseFieldName": "SALARY", + "databaseFieldType": "DOUBLE", + "javaFieldName": "salary", + "javaFieldType": "Double" + } + ], + "keyFields": [ + { + "databaseFieldName": "ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "id", + "javaFieldType": "int" + } + ], + "caches": [] + }, + { + "keyType": "Integer", + "valueType": "model.Country", + "queryMetadata": "Configuration", + "databaseSchema": "PUBLIC", + "databaseTable": "COUNTRY", + "indexes": [], + "aliases": [], + "fields": [ + { + "name": "name", + "className": "String" + }, + { + "name": "population", + "className": "Integer" + } + ], + "valueFields": [ + { + "databaseFieldName": "NAME", + "databaseFieldType": "VARCHAR", + "javaFieldName": "name", + "javaFieldType": "String" + }, + { + "databaseFieldName": "POPULATION", + "databaseFieldType": "INTEGER", + "javaFieldName": "population", + "javaFieldType": "int" + } + ], + "keyFields": [ + { + "databaseFieldName": "ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "id", + "javaFieldType": "int" + } + ], + "caches": [] + }, + { + "keyType": "Integer", + "valueType": "model.Car", + "queryMetadata": "Configuration", + "databaseSchema": "CARS", + "databaseTable": "CAR", + "indexes": [], + "aliases": [], + "fields": [ + { + "name": "parkingId", + "className": "Integer" + }, + { + "name": "name", + "className": "String" + } + ], + "valueFields": [ + { + "databaseFieldName": "PARKING_ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "parkingId", + "javaFieldType": "int" + }, + { + "databaseFieldName": "NAME", + "databaseFieldType": "VARCHAR", + "javaFieldName": "name", + "javaFieldType": "String" + } + ], + "keyFields": [ + { + "databaseFieldName": "ID", + "databaseFieldType": "INTEGER", + "javaFieldName": "id", + "javaFieldType": "int" + } + ], + "caches": [] + } +] http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/demo/igfss.json ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/demo/igfss.json b/modules/web-console/src/main/js/serve/routes/demo/igfss.json new file mode 100644 index 0000000..cd128a6 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/demo/igfss.json @@ -0,0 +1,10 @@ +[ + { + "ipcEndpointEnabled": true, + "fragmentizerEnabled": true, + "name": "igfs", + "dataCacheName": "igfs-data", + "metaCacheName": "igfs-meta", + "clusters": [] + } +] http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/domains.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/domains.js b/modules/web-console/src/main/js/serve/routes/domains.js new file mode 100644 index 0000000..9dbf418 --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/domains.js @@ -0,0 +1,195 @@ +/* + * 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: 'domains-routes', + inject: ['require(lodash)', 'require(express)', 'mongo'] +}; + +module.exports.factory = (_, express, mongo) => { + return new Promise((factoryResolve) => { + const router = new express.Router(); + + /** + * Get spaces and domain models accessed for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/list', (req, res) => { + const result = {}; + let spacesIds = []; + + mongo.spaces(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaces) => { + result.spaces = spaces; + spacesIds = spaces.map((space) => space._id); + + return mongo.Cluster.find({space: {$in: spacesIds}}, '_id name').sort('name').lean().exec(); + }) + .then((clusters) => { + result.clusters = clusters; + + return mongo.Cache.find({space: {$in: spacesIds}}).sort('name').lean().exec(); + }) + .then((caches) => { + result.caches = caches; + + return mongo.DomainModel.find({space: {$in: spacesIds}}).sort('valueType').lean().exec(); + }) + .then((domains) => { + result.domains = domains; + + res.json(result); + }) + .catch((err) => mongo.handleError(res, err)); + }); + + function _updateCacheStore(cacheStoreChanges) { + const promises = []; + + _.forEach(cacheStoreChanges, (change) => + promises.push(mongo.Cache.update({_id: {$eq: change.cacheId}}, change.change, {}).exec()) + ); + + return Promise.all(promises); + } + + const _saveDomainModel = (domain, savedDomains) => { + const caches = domain.caches; + const cacheStoreChanges = domain.cacheStoreChanges; + const domainId = domain._id; + + return mongo.DomainModel.findOne({space: domain.space, valueType: domain.valueType}).exec() + .then((_domain) => { + if (_domain && domainId !== _domain._id.toString()) + throw new Error('Domain model with value type: "' + _domain.valueType + '" already exist.'); + + if (domainId) { + return mongo.DomainModel.update({_id: domain._id}, domain, {upsert: true}).exec() + .then(() => mongo.Cache.update({_id: {$in: caches}}, {$addToSet: {domains: domainId}}, {multi: true}).exec()) + .then(() => mongo.Cache.update({_id: {$nin: caches}}, {$pull: {domains: domainId}}, {multi: true}).exec()) + .then(() => { + savedDomains.push(domain); + + return _updateCacheStore(cacheStoreChanges); + }); + } + + return (new mongo.DomainModel(domain)).save() + .then((savedDomain) => { + savedDomains.push(savedDomain); + + return mongo.Cache.update({_id: {$in: caches}}, {$addToSet: {domains: savedDomain._id}}, {multi: true}).exec(); + }) + .then(() => _updateCacheStore(cacheStoreChanges)); + }); + }; + + const _save = (domains, res) => { + if (domains && domains.length > 0) { + const savedDomains = []; + const generatedCaches = []; + const promises = []; + + _.forEach(domains, (domain) => { + if (domain.newCache) { + promises.push( + mongo.Cache.findOne({space: domain.space, name: domain.newCache.name}).exec() + .then((cache) => { + if (cache) + return Promise.resolve(cache); + + // If cache not found, then create it and associate with domain model. + const newCache = domain.newCache; + newCache.space = domain.space; + + return (new mongo.Cache(newCache)).save() + .then((_cache) => { + generatedCaches.push(_cache); + + return mongo.Cluster.update({_id: {$in: _cache.clusters}}, {$addToSet: {caches: _cache._id}}, {multi: true}).exec() + .then(() => Promise.resolve(_cache)); + }); + }) + .then((cache) => { + domain.caches = [cache._id]; + + return _saveDomainModel(domain, savedDomains); + }) + .catch((err) => mongo.handleError(res, err)) + ); + } + else + promises.push(_saveDomainModel(domain, savedDomains)); + }); + + Promise.all(promises) + .then(() => res.send({savedDomains, generatedCaches})) + .catch((err) => mongo.handleError(res, err)); + } + else + res.status(500).send('Nothing to save!'); + }; + + /** + * Save domain model. + */ + router.post('/save', (req, res) => { + _save([req.body], res); + }); + + /** + * Batch save domain models. + */ + router.post('/save/batch', (req, res) => { + _save(req.body, res); + }); + + /** + * Remove domain model by ._id. + */ + router.post('/remove', (req, res) => { + const params = req.body; + const domainId = params._id; + + mongo.DomainModel.findOne(params).exec() + .then((domain) => mongo.Cache.update({_id: {$in: domain.caches}}, {$pull: {domains: domainId}}, {multi: true}).exec()) + .then(() => mongo.DomainModel.remove(params).exec()) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove all domain models. + */ + router.post('/remove/all', (req, res) => { + mongo.spaceIds(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaceIds) => mongo.Cache.update({space: {$in: spaceIds}}, {domains: []}, {multi: true}).exec() + .then(() => mongo.DomainModel.remove({space: {$in: spaceIds}}).exec())) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + factoryResolve(router); + }); +}; + http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/igfs.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/igfs.js b/modules/web-console/src/main/js/serve/routes/igfs.js new file mode 100644 index 0000000..6e5e60c --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/igfs.js @@ -0,0 +1,122 @@ +/* + * 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: 'igfs-routes', + inject: ['require(lodash)', 'require(express)', 'mongo'] +}; + +module.exports.factory = function(_, express, mongo) { + return new Promise((factoryResolve) => { + const router = new express.Router(); + + /** + * Get spaces and IGFSs accessed for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/list', (req, res) => { + const result = {}; + let spaceIds = []; + + // Get owned space and all accessed space. + mongo.spaces(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaces) => { + result.spaces = spaces; + spaceIds = spaces.map((space) => space._id); + + return mongo.Cluster.find({space: {$in: spaceIds}}, '_id name').sort('name').lean().exec(); + }) + .then((clusters) => { + result.clusters = clusters; + + return mongo.Igfs.find({space: {$in: spaceIds}}).sort('name').lean().exec(); + }) + .then((igfss) => { + result.igfss = igfss; + + res.json(result); + }) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Save IGFS. + */ + router.post('/save', (req, res) => { + const params = req.body; + const clusters = params.clusters; + + mongo.Igfs.findOne({space: params.space, name: params.name}).exec() + .then((_igfs) => { + const igfsId = params._id; + + if (_igfs && igfsId !== _igfs._id.toString()) + return res.status(500).send('IGFS with name: "' + params.name + '" already exist.'); + + if (params._id) { + return mongo.Igfs.update({_id: igfsId}, params, {upsert: true}).exec() + .then(() => mongo.Cluster.update({_id: {$in: clusters}}, {$addToSet: {igfss: igfsId}}, {multi: true}).exec()) + .then(() => mongo.Cluster.update({_id: {$nin: clusters}}, {$pull: {igfss: igfsId}}, {multi: true}).exec()) + .then(() => res.send(igfsId)); + } + + return (new mongo.Igfs(params)).save() + .then((igfs) => + mongo.Cluster.update({_id: {$in: clusters}}, {$addToSet: {igfss: igfsId}}, {multi: true}).exec() + .then(() => res.send(igfs._id)) + ); + }) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove IGFS by ._id. + */ + router.post('/remove', (req, res) => { + const params = req.body; + const igfsId = params._id; + + mongo.Cluster.update({igfss: {$in: [igfsId]}}, {$pull: {igfss: igfsId}}, {multi: true}).exec() + .then(() => mongo.Igfs.remove(params).exec()) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove all IGFSs. + */ + router.post('/remove/all', (req, res) => { + // Get owned space and all accessed space. + mongo.spaceIds(req.currentUserId(), req.header('IgniteDemoMode')) + .then((spaceIds) => + mongo.Cluster.update({space: {$in: spaceIds}}, {igfss: []}, {multi: true}).exec() + .then(() => mongo.Igfs.remove({space: {$in: spaceIds}}).exec()) + ) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + factoryResolve(router); + }); +}; + http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/notebooks.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/notebooks.js b/modules/web-console/src/main/js/serve/routes/notebooks.js new file mode 100644 index 0000000..37665bf --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/notebooks.js @@ -0,0 +1,121 @@ +/* + * 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: 'notebooks-routes', + inject: ['require(express)', 'mongo'] +}; + +module.exports.factory = function(express, mongo) { + return new Promise((factoryResolve) => { + const router = new express.Router(); + + /** + * Get notebooks names accessed for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/list', (req, res) => { + mongo.spaces(req.currentUserId()) + .then((spaces) => mongo.Notebook.find({space: {$in: spaces.map((value) => value._id)}}).select('_id name').sort('name').lean().exec()) + .then((notebooks) => res.json(notebooks)) + .catch((err) => mongo.handleError(res, err)); + + }); + + /** + * Get notebook accessed for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/get', (req, res) => { + mongo.spaces(req.currentUserId()) + .then((spaces) => mongo.Notebook.findOne({space: {$in: spaces.map((value) => value._id)}, _id: req.body.noteId}).lean().exec()) + .then((notebook) => res.json(notebook)) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Save notebook accessed for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/save', (req, res) => { + const note = req.body; + + mongo.Notebook.findOne({space: note.space, name: note.name}).exec() + .then((notebook) => { + const noteId = note._id; + + if (notebook && noteId !== notebook._id.toString()) + throw new Error('Notebook with name: "' + notebook.name + '" already exist.'); + + if (noteId) { + return mongo.Notebook.update({_id: noteId}, note, {upsert: true}).exec() + .then(() => res.send(noteId)) + .catch((err) => mongo.handleError(res, err)); + } + + return (new mongo.Notebook(req.body)).save(); + }) + .then((notebook) => res.send(notebook._id)) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Remove notebook by ._id. + * + * @param req Request. + * @param res Response. + */ + router.post('/remove', (req, res) => { + mongo.Notebook.remove(req.body).exec() + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + /** + * Create new notebook for user account. + * + * @param req Request. + * @param res Response. + */ + router.post('/new', (req, res) => { + mongo.spaceIds(req.currentUserId()) + .then((spaceIds) => + mongo.Notebook.findOne({space: spaceIds[0], name: req.body.name}) + .then((notebook) => { + if (notebook) + throw new Error('Notebook with name: "' + notebook.name + '" already exist.'); + + return spaceIds; + })) + .then((spaceIds) => (new mongo.Notebook({space: spaceIds[0], name: req.body.name})).save()) + .then((notebook) => res.send(notebook._id)) + .catch((err) => mongo.handleError(res, err)); + }); + + factoryResolve(router); + }); +}; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/serve/routes/profile.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/serve/routes/profile.js b/modules/web-console/src/main/js/serve/routes/profile.js new file mode 100644 index 0000000..5e4278f --- /dev/null +++ b/modules/web-console/src/main/js/serve/routes/profile.js @@ -0,0 +1,95 @@ +/* + * 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: 'profile-routes', + inject: ['require(lodash)', 'require(express)', 'mongo', 'agent-manager'] +}; + +/** + * + * @param _ Lodash module + * @param express Express module + * @param mongo + * @param {AgentManager} agentMgr + * @returns {Promise} + */ +module.exports.factory = function(_, express, mongo, agentMgr) { + return new Promise((resolveFactory) => { + const router = new express.Router(); + + /** + * Save user profile. + */ + router.post('/save', (req, res) => { + const params = req.body; + + if (params.password && _.isEmpty(params.password)) + return res.status(500).send('Wrong value for new password!'); + + mongo.Account.findById(params._id).exec() + .then((user) => { + if (!params.password) + return Promise.resolve(user); + + return new Promise((resolve, reject) => { + user.setPassword(params.password, (err, _user) => { + if (err) + return reject(err); + + delete params.password; + + resolve(_user); + }); + }); + }) + .then((user) => { + if (!params.email || user.email === params.email) + return Promise.resolve(user); + + return new Promise((resolve, reject) => { + mongo.Account.findOne({email: params.email}, (err, _user) => { + // TODO send error to admin + if (err) + reject(new Error('Failed to check email!')); + + if (_user && _user._id !== user._id) + reject(new Error('User with this email already registered!')); + + resolve(user); + }); + }); + }) + .then((user) => { + if (params.token && user.token !== params.token) + agentMgr.close(user._id); + + _.extend(user, params); + + return user.save(); + }) + .then(() => res.sendStatus(200)) + .catch((err) => mongo.handleError(res, err)); + }); + + resolveFactory(router); + }); +};
