http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/services/AgentMonitor.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/AgentMonitor.service.js b/modules/web-console/src/main/js/app/services/AgentMonitor.service.js new file mode 100644 index 0000000..88995d5 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/AgentMonitor.service.js @@ -0,0 +1,337 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import io from 'socket.io-client'; // eslint-disable-line no-unused-vars + +class IgniteAgentMonitor { + constructor(socketFactory, $root, $q, $state, $modal, $common) { + this._scope = $root.$new(); + + $root.$on('$stateChangeStart', () => { + this.stopWatch(); + }); + + // Pre-fetch modal dialogs. + this._downloadAgentModal = $modal({ + scope: this._scope, + templateUrl: '/templates/agent-download.html', + show: false, + backdrop: 'static' + }); + + const _modalHide = this._downloadAgentModal.hide; + + /** + * Special dialog hide function. + */ + this._downloadAgentModal.hide = () => { + $common.hideAlert(); + + _modalHide(); + }; + + /** + * Close dialog and go by specified link. + */ + this._scope.back = () => { + this.stopWatch(); + + if (this._scope.backState) + this._scope.$$postDigest(() => $state.go(this._scope.backState)); + }; + + this._scope.downloadAgent = () => { + const lnk = document.createElement('a'); + + lnk.setAttribute('href', '/api/v1/agent/download/zip'); + lnk.setAttribute('target', '_self'); + lnk.setAttribute('download', null); + lnk.style.display = 'none'; + + document.body.appendChild(lnk); + + lnk.click(); + + document.body.removeChild(lnk); + }; + + this._scope.hasAgents = null; + this._scope.showModal = false; + + this._evtOrderKey = $common.randomString(20); + this._evtThrottleCntrKey = $common.randomString(20); + + /** + * @type {Socket} + */ + this._socket = null; + + this._socketFactory = socketFactory; + + this._$q = $q; + + this._$common = $common; + } + + /** + * @private + */ + checkModal() { + if (this._scope.showModal && !this._scope.hasAgents) + this._downloadAgentModal.$promise.then(this._downloadAgentModal.show); + else if ((this._scope.hasAgents || !this._scope.showModal) && this._downloadAgentModal.$isShown) + this._downloadAgentModal.hide(); + } + + /** + * @returns {Promise} + */ + awaitAgent() { + if (this._scope.hasAgents) + return this._$q.when(); + + if (this._scope.hasAgents !== null) + this.checkModal(); + + const latch = this._$q.defer(); + + const offConnected = this._scope.$on('agent:connected', (event, success) => { + offConnected(); + + if (success) + return latch.resolve(); + + latch.reject(); + }); + + return latch.promise; + } + + init() { + this._socket = this._socketFactory(); + + this._socket.on('connect_error', () => { + this._scope.hasAgents = false; + }); + + this._socket.on('agent:count', ({count}) => { + this._scope.hasAgents = count > 0; + + this.checkModal(); + + if (this._scope.hasAgents) + this._scope.$broadcast('agent:connected', true); + }); + + this._socket.on('disconnect', () => { + this._scope.hasAgents = false; + + this.checkModal(); + }); + } + + /** + * @param {Object} back + * @returns {Promise} + */ + startWatch(back) { + this._scope.backState = back.state; + this._scope.backText = back.text; + + this._scope.agentGoal = back.goal; + + this._scope.showModal = true; + + return this.awaitAgent(); + } + + /** + * + * @param {String} event + * @param {Object} [args] + * @returns {Promise} + * @private + */ + _emit(event, ...args) { + if (!this._socket) + return this._$q.reject('Failed to connect to agent'); + + const latch = this._$q.defer(); + + const onDisconnect = () => { + this._socket.removeListener('disconnect', onDisconnect); + + latch.reject('Connection to server was closed'); + }; + + this._socket.on('disconnect', onDisconnect); + + args.push((err, res) => { + this._socket.removeListener('disconnect', onDisconnect); + + if (err) + latch.reject(err); + + latch.resolve(res); + }); + + this._socket.emit(event, ...args); + + return latch.promise; + } + + drivers() { + return this._emit('schemaImport:drivers'); + } + + /** + * + * @param {Object} preset + * @returns {Promise} + */ + schemas(preset) { + return this._emit('schemaImport:schemas', preset); + } + + /** + * + * @param {Object} preset + * @returns {Promise} + */ + tables(preset) { + return this._emit('schemaImport:tables', preset); + } + + /** + * @param {String} errMsg + */ + showNodeError(errMsg) { + this._downloadAgentModal.show(); + + this._$common.showError(errMsg); + } + + /** + * + * @param {String} event + * @param {Object} [args] + * @returns {Promise} + * @private + */ + _rest(event, ...args) { + return this._downloadAgentModal.$promise + .then(() => this._emit(event, ...args)); + } + + /** + * @param {Boolean} [attr] + * @param {Boolean} [mtr] + * @returns {Promise} + */ + topology(attr, mtr) { + return this._rest('node:topology', !!attr, !!mtr); + } + + /** + * @param {int} [queryId] + * @returns {Promise} + */ + queryClose(queryId) { + return this._rest('node:query:close', queryId); + } + + /** + * @param {String} cacheName Cache name. + * @param {int} pageSize + * @param {String} [query] Query if null then scan query. + * @returns {Promise} + */ + query(cacheName, pageSize, query) { + return this._rest('node:query', _.isEmpty(cacheName) ? null : cacheName, pageSize, query); + } + + /** + * @param {String} cacheName Cache name. + * @param {String} [query] Query if null then scan query. + * @returns {Promise} + */ + queryGetAll(cacheName, query) { + return this._rest('node:query:getAll', _.isEmpty(cacheName) ? null : cacheName, query); + } + + /** + * @param {String} [cacheName] Cache name. + * @returns {Promise} + */ + metadata(cacheName) { + return this._rest('node:cache:metadata', _.isEmpty(cacheName) ? null : cacheName); + } + + /** + * @param {int} queryId + * @param {int} pageSize + * @returns {Promise} + */ + next(queryId, pageSize) { + return this._rest('node:query:fetch', queryId, pageSize); + } + + collect() { + return this._rest('node:visor:collect', this._evtOrderKey, this._evtThrottleCntrKey); + } + + /** + * Clear specified cache on specified node. + * @param {String} nid Node id. + * @param {String} cacheName Cache name. + * @returns {Promise} + */ + cacheClear(nid, cacheName) { + return this._rest('node:cache:clear', nid, cacheName); + } + + /** + * Stop specified cache on specified node. + * @param {String} nid Node id. + * @param {String} cacheName Cache name. + * @returns {Promise} + */ + cacheStop(nid, cacheName) { + return this._rest('node:cache:stop', nid, cacheName); + } + + /** + * Ping node. + * @param {String} nid Node id. + * @returns {Promise} + */ + ping(nid) { + return this._rest('node:ping', nid); + } + + stopWatch() { + this._scope.showModal = false; + + this.checkModal(); + + this._scope.$broadcast('agent:connected', false); + } +} + +IgniteAgentMonitor.$inject = ['igniteSocketFactory', '$rootScope', '$q', '$state', '$modal', '$common']; + +export default ['IgniteAgentMonitor', IgniteAgentMonitor];
http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/services/ChartColors.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/ChartColors.service.js b/modules/web-console/src/main/js/app/services/ChartColors.service.js new file mode 100644 index 0000000..ec3f365 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/ChartColors.service.js @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import COLORS from 'app/data/colors.json!'; + +export default ['IgniteChartColors', function() { + return COLORS; +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/services/Countries.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/Countries.service.js b/modules/web-console/src/main/js/app/services/Countries.service.js new file mode 100644 index 0000000..87d11fd --- /dev/null +++ b/modules/web-console/src/main/js/app/services/Countries.service.js @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import COUNTRIES from 'app/data/countries.json!'; + +export default ['IgniteCountries', function() { + const indexByName = _.keyBy(COUNTRIES, 'name'); + const UNDEFINED_COUNTRY = {name: '', code: ''}; + + const getByName = (name) => (indexByName[name] || UNDEFINED_COUNTRY); + const getAll = () => (COUNTRIES); + + return { + getByName, + getAll + }; +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/services/InetAddress.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/InetAddress.service.js b/modules/web-console/src/main/js/app/services/InetAddress.service.js new file mode 100644 index 0000000..abdd8a3 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/InetAddress.service.js @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default ['IgniteInetAddress', function() { + return { + /** + * @param {String} ip IP address to check. + * @returns {boolean} 'true' if given ip address is valid. + */ + validIp(ip) { + const regexp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; + + return regexp.test(ip); + }, + /** + * @param {String} hostNameOrIp host name or ip address to check. + * @returns {boolean} 'true' if given is host name or ip. + */ + validHost(hostNameOrIp) { + const regexp = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; + + return regexp.test(hostNameOrIp) || this.validIp(hostNameOrIp); + }, + /** + * @param {int} port Port value to check. + * @returns boolean 'true' if given port is valid tcp/udp port range. + */ + validPort(port) { + return _.isInteger(port) && port > 0 && port <= 65535; + }, + /** + * @param {int} port Port value to check. + * @returns {boolean} 'true' if given port in non system port range(user+dynamic). + */ + validNonSystemPort(port) { + return _.isInteger(port) && port >= 1024 && port <= 65535; + } + }; +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/services/JavaTypes.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/JavaTypes.service.js b/modules/web-console/src/main/js/app/services/JavaTypes.service.js new file mode 100644 index 0000000..a755e13 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/JavaTypes.service.js @@ -0,0 +1,84 @@ +/* + * 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. + */ + +// Java built-in class names. +import JAVA_CLASSES from 'app/data/java-classes.json!'; + +// Java build-in primitive. +import JAVA_PRIMITIVES from 'app/data/java-primitives.json!'; + +import JAVA_KEYWORDS from 'app/data/java-keywords.json!'; + +export default ['JavaTypes', function() { + return { + /** + * @param {String} clsName Class name to check. + * @returns boolean 'true' if given class name non a Java built-in type. + */ + nonBuiltInClass(clsName) { + return _.isNil(_.find(JAVA_CLASSES, (clazz) => clsName === clazz.short || clsName === clazz.full)); + }, + /** + * @param clsName Class name to check. + * @returns Full class name for java build-in types or source class otherwise. + */ + fullClassName(clsName) { + const type = _.find(JAVA_CLASSES, (clazz) => clsName === clazz.short); + + return type ? type.full : clsName; + }, + /** + * @param {String} value text to check. + * @returns boolean 'true' if given text is valid Java identifier. + */ + validIdentifier(value) { + const regexp = /^(([a-zA-Z_$][a-zA-Z0-9_$]*)\.)*([a-zA-Z_$][a-zA-Z0-9_$]*)$/igm; + + return value === '' || regexp.test(value); + }, + /** + * @param {String} value text to check. + * @returns boolean 'true' if given text is valid Java package. + */ + validPackage(value) { + const regexp = /^(([a-zA-Z_$][a-zA-Z0-9_$]*)\.)*([a-zA-Z_$][a-zA-Z0-9_$]*(\.?\*)?)$/igm; + + return value === '' || regexp.test(value); + }, + /** + * @param {String} value text to check. + * @returns boolean 'true' if given text is a Java type with package. + */ + packageSpecified(value) { + return value.split('.').length >= 2; + }, + /** + * @param {String} value text to check. + * @returns boolean 'true' if given text non Java keyword. + */ + isKeywords(value) { + return _.includes(JAVA_KEYWORDS, value); + }, + /** + * @param {String} clsName Class name to check. + * @returns {boolean} 'true' if givent class name is java primitive. + */ + isJavaPrimitive(clsName) { + return _.includes(JAVA_PRIMITIVES, clsName); + } + }; +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/services/cleanup.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/cleanup.service.js b/modules/web-console/src/main/js/app/services/cleanup.service.js new file mode 100644 index 0000000..380beda --- /dev/null +++ b/modules/web-console/src/main/js/app/services/cleanup.service.js @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default ['$cleanup', () => { + const cleanup = (original, dist) => { + if (_.isUndefined(original)) + return dist; + + if (_.isObject(original)) { + _.forOwn(original, (value, key) => { + if (/\$\$hashKey/.test(key)) + return; + + const attr = cleanup(value); + + if (!_.isNil(attr)) { + dist = dist || {}; + dist[key] = attr; + } + }); + } else if ((_.isString(original) && original.length) || _.isNumber(original) || _.isBoolean(original)) + dist = original; + else if (_.isArray(original) && original.length) + dist = _.map(original, (value) => cleanup(value, {})); + + return dist; + }; + + return cleanup; +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/app/services/confirm.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/confirm.service.js b/modules/web-console/src/main/js/app/services/confirm.service.js new file mode 100644 index 0000000..bb07cfd --- /dev/null +++ b/modules/web-console/src/main/js/app/services/confirm.service.js @@ -0,0 +1,70 @@ +/* + * 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. + */ + +// Confirm popup service. +export default ['$confirm', ['$modal', '$rootScope', '$q', '$animate', ($modal, $root, $q, $animate) => { + const scope = $root.$new(); + + const modal = $modal({templateUrl: '/templates/confirm.html', scope, placement: 'center', show: false}); + + let deferred; + + const _hide = (animate) => { + $animate.enabled(modal.$element, animate); + + modal.hide(); + }; + + scope.confirmYes = () => { + _hide(scope.animate); + + deferred.resolve(true); + }; + + scope.confirmNo = () => { + _hide(scope.animate); + + deferred.resolve(false); + }; + + scope.confirmCancel = () => { + _hide(true); + + deferred.reject('cancelled'); + }; + + /** + * + * @param {String } content + * @param {Boolean} [yesNo] + * @param {Boolean} [animate] + * @returns {Promise} + */ + modal.confirm = (content, yesNo, animate) => { + scope.animate = !!animate; + scope.content = content || 'Confirm?'; + scope.yesNo = !!yesNo; + + deferred = $q.defer(); + + modal.$promise.then(modal.show); + + return deferred.promise; + }; + + return modal; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/build/system.config.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/build/system.config.js b/modules/web-console/src/main/js/build/system.config.js new file mode 100644 index 0000000..edd9e9e --- /dev/null +++ b/modules/web-console/src/main/js/build/system.config.js @@ -0,0 +1,415 @@ +System.config({ + defaultJSExtensions: true, + transpiler: "babel", + babelOptions: { + "optional": [ + "runtime", + "optimisation.modules.system" + ] + }, + paths: { + "github:*": "build/jspm_packages/github/*", + "npm:*": "build/jspm_packages/npm/*" + }, + separateCSS: true, + + map: { + "ace": "github:ajaxorg/[email protected]", + "angular": "github:angular/[email protected]", + "angular-animate": "github:angular/[email protected]", + "angular-drag-and-drop-lists": "github:marceljuenemann/[email protected]", + "angular-gridster": "github:ManifestWebDesign/[email protected]", + "angular-motion": "github:mgcrea/[email protected]", + "angular-nvd3": "github:krispo/[email protected]", + "angular-retina": "github:jrief/[email protected]", + "angular-sanitize": "github:angular/[email protected]", + "angular-smart-table": "github:lorenzofox3/[email protected]", + "angular-socket-io": "github:btford/[email protected]", + "angular-strap": "github:mgcrea/[email protected]", + "angular-tree-control": "github:wix/[email protected]", + "angular-ui-grid": "github:angular-ui/[email protected]", + "angular-ui-router": "github:angular-ui/[email protected]", + "angular-ui-router-metatags": "github:tinusn/[email protected]", + "babel": "npm:[email protected]", + "babel-runtime": "npm:[email protected]", + "blob": "github:eligrey/Blob.js@master", + "bootstrap-carousel": "github:twbs/[email protected]", + "clean-css": "npm:[email protected]", + "core-js": "npm:[email protected]", + "css": "github:systemjs/[email protected]", + "file-saver": "github:eligrey/FileSaver.js@master", + "font-awesome": "npm:[email protected]", + "jade": "github:johnsoftek/[email protected]", + "jquery": "github:components/[email protected]", + "json": "github:systemjs/[email protected]", + "jszip": "github:Stuk/[email protected]", + "lodash": "github:lodash/[email protected]", + "pdfmake": "github:bpampuch/[email protected]", + "query-command-supported": "github:zenorocha/[email protected]", + "socket.io-client": "github:socketio/[email protected]", + "text": "github:systemjs/[email protected]", + "github:angular-ui/[email protected]": { + "pdfmake": "github:bpampuch/[email protected]" + }, + "github:angular-ui/[email protected]": { + "angular": "github:angular/[email protected]" + }, + "github:angular/[email protected]": { + "angular": "github:angular/[email protected]" + }, + "github:angular/[email protected]": { + "angular": "github:angular/[email protected]" + }, + "github:angular/[email protected]": { + "jquery": "github:components/[email protected]" + }, + "github:btford/[email protected]": { + "socket.io-client": "github:socketio/[email protected]" + }, + "github:eligrey/FileSaver.js@master": { + "blob": "github:eligrey/Blob.js@master" + }, + "github:johnsoftek/[email protected]": { + "jade-compiler": "npm:[email protected]", + "text": "github:systemjs/[email protected]" + }, + "github:jspm/[email protected]": { + "assert": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "buffer": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "events": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "Base64": "npm:[email protected]", + "events": "github:jspm/[email protected]", + "inherits": "npm:[email protected]", + "stream": "github:jspm/[email protected]", + "url": "github:jspm/[email protected]", + "util": "github:jspm/[email protected]" + }, + "github:jspm/[email protected]": { + "https-browserify": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "os-browserify": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "path-browserify": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "process": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "stream-browserify": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "tty-browserify": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "url": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "util": "npm:[email protected]" + }, + "github:jspm/[email protected]": { + "vm-browserify": "npm:[email protected]" + }, + "github:krispo/[email protected]": { + "d3": "npm:[email protected]", + "nvd3": "npm:[email protected]" + }, + "github:mgcrea/[email protected]": { + "angular": "github:angular/[email protected]", + "css": "github:systemjs/[email protected]" + }, + "github:mgcrea/[email protected]": { + "angular": "github:angular/[email protected]", + "angular-animate": "github:angular/[email protected]", + "angular-motion": "github:mgcrea/[email protected]", + "angular-sanitize": "github:angular/[email protected]" + }, + "github:twbs/[email protected]": { + "jquery": "npm:[email protected]" + }, + "npm:[email protected]": { + "acorn": "npm:[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "stream": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "stream": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "kind-of": "npm:[email protected]", + "longest": "npm:[email protected]", + "repeat-string": "npm:[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]", + "module": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "util": "npm:[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "base64-js": "npm:[email protected]", + "child_process": "github:jspm/[email protected]", + "fs": "github:jspm/[email protected]", + "ieee754": "npm:[email protected]", + "isarray": "npm:[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "align-text": "npm:[email protected]", + "lazy-cache": "npm:[email protected]" + }, + "npm:[email protected]": { + "buffer": "github:jspm/[email protected]", + "commander": "npm:[email protected]", + "fs": "github:jspm/[email protected]", + "http": "github:jspm/[email protected]", + "https": "github:jspm/[email protected]", + "os": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "source-map": "npm:[email protected]", + "url": "github:jspm/[email protected]", + "util": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "center-align": "npm:[email protected]", + "right-align": "npm:[email protected]", + "wordwrap": "npm:[email protected]" + }, + "npm:[email protected]": { + "child_process": "github:jspm/[email protected]", + "events": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "child_process": "github:jspm/[email protected]", + "events": "github:jspm/[email protected]", + "fs": "github:jspm/[email protected]", + "graceful-readlink": "npm:[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "acorn": "npm:[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "systemjs-json": "github:systemjs/[email protected]" + }, + "npm:[email protected]": { + "buffer": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "assert": "github:jspm/[email protected]", + "css-parse": "npm:[email protected]", + "css-stringify": "npm:[email protected]", + "fs": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "css": "github:systemjs/[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "http": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "util": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "buffer": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "buffer": "github:jspm/[email protected]", + "character-parser": "npm:[email protected]", + "clean-css": "npm:[email protected]", + "commander": "npm:[email protected]", + "constantinople": "npm:[email protected]", + "jstransformer": "npm:[email protected]", + "mkdirp": "npm:[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "systemjs-json": "github:systemjs/[email protected]", + "transformers": "npm:[email protected]", + "uglify-js": "npm:[email protected]", + "void-elements": "npm:[email protected]", + "with": "npm:[email protected]" + }, + "npm:[email protected]": { + "assert": "github:jspm/[email protected]", + "fs": "github:jspm/[email protected]", + "is-promise": "npm:[email protected]", + "promise": "npm:[email protected]" + }, + "npm:[email protected]": { + "buffer": "github:jspm/[email protected]", + "is-buffer": "npm:[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]", + "minimist": "npm:[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "d3": "npm:[email protected]" + }, + "npm:[email protected]": { + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "wordwrap": "npm:[email protected]" + }, + "npm:[email protected]": { + "os": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "assert": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "is-promise": "npm:[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "asap": "npm:[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "buffer": "github:jspm/[email protected]", + "core-util-is": "npm:[email protected]", + "events": "github:jspm/[email protected]", + "inherits": "npm:[email protected]", + "isarray": "npm:[email protected]", + "process": "github:jspm/[email protected]", + "stream-browserify": "npm:[email protected]", + "string_decoder": "npm:[email protected]" + }, + "npm:[email protected]": { + "align-text": "npm:[email protected]" + }, + "npm:[email protected]": { + "amdefine": "npm:[email protected]", + "fs": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "amdefine": "npm:[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "events": "github:jspm/[email protected]", + "inherits": "npm:[email protected]", + "readable-stream": "npm:[email protected]" + }, + "npm:[email protected]": { + "buffer": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "css": "npm:[email protected]", + "fs": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "promise": "npm:[email protected]", + "uglify-js": "npm:[email protected]", + "vm": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]", + "optimist": "npm:[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "source-map": "npm:[email protected]", + "util": "github:jspm/[email protected]", + "vm": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "async": "npm:[email protected]", + "fs": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "source-map": "npm:[email protected]", + "uglify-to-browserify": "npm:[email protected]", + "yargs": "npm:[email protected]" + }, + "npm:[email protected]": { + "fs": "github:jspm/[email protected]", + "stream": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "assert": "github:jspm/[email protected]", + "punycode": "npm:[email protected]", + "querystring": "npm:[email protected]", + "util": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "inherits": "npm:[email protected]", + "process": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "indexof": "npm:[email protected]" + }, + "npm:[email protected]": { + "http": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "process": "github:jspm/[email protected]", + "tty": "github:jspm/[email protected]" + }, + "npm:[email protected]": { + "acorn": "npm:[email protected]", + "acorn-globals": "npm:[email protected]" + }, + "npm:[email protected]": { + "assert": "github:jspm/[email protected]", + "camelcase": "npm:[email protected]", + "cliui": "npm:[email protected]", + "decamelize": "npm:[email protected]", + "fs": "github:jspm/[email protected]", + "path": "github:jspm/[email protected]", + "process": "github:jspm/[email protected]", + "window-size": "npm:[email protected]" + } + } +}); http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/controllers/admin-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/controllers/admin-controller.js b/modules/web-console/src/main/js/controllers/admin-controller.js new file mode 100644 index 0000000..5abee04 --- /dev/null +++ b/modules/web-console/src/main/js/controllers/admin-controller.js @@ -0,0 +1,92 @@ +/* + * 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. + */ + +// Controller for Admin screen. +import consoleModule from 'controllers/common-module'; + +consoleModule.controller('adminController', [ + '$rootScope', '$scope', '$http', '$q', '$common', '$confirm', '$state', 'User', 'IgniteCountries', + ($rootScope, $scope, $http, $q, $common, $confirm, $state, User, Countries) => { + $scope.users = null; + + const _reloadUsers = () => { + $http.post('/api/v1/admin/list') + .then(({data}) => { + $scope.users = data; + + _.forEach($scope.users, (user) => { + user.userName = user.firstName + ' ' + user.lastName; + user.countryCode = Countries.getByName(user.country).code; + user.label = user.userName + ' ' + user.email + ' ' + + (user.company || '') + ' ' + (user.country || ''); + }) + }) + .catch((err) => $common.showError(err)); + }; + + _reloadUsers(); + + $scope.becomeUser = function (user) { + $http.get('/api/v1/admin/become', { params: {viewedUserId: user._id}}) + .then(User.read) + .then((user) => { + $rootScope.$broadcast('user', user); + + $state.go('base.configuration.clusters') + }) + .catch((errMsg) => $common.showError($common.errorMessage(errMsg))); + }; + + $scope.removeUser = (user) => { + $confirm.confirm('Are you sure you want to remove user: "' + user.userName + '"?') + .then(() => { + $http.post('/api/v1/admin/remove', {userId: user._id}) + .success(() => { + const i = _.findIndex($scope.users, (u) => u._id === user._id); + + if (i >= 0) + $scope.users.splice(i, 1); + + $common.showInfo('User has been removed: "' + user.userName + '"'); + }) + .error((errMsg, status) => { + if (status == 503) + $common.showInfo(errMsg); + else + $common.showError('Failed to remove user: "' + $common.errorMessage(errMsg) + '"'); + }); + }); + }; + + $scope.toggleAdmin = (user) => { + if (user.adminChanging) + return; + + user.adminChanging = true; + + $http.post('/api/v1/admin/save', {userId: user._id, adminFlag: !user.admin}) + .success(() => { + user.admin = !user.admin; + + $common.showInfo('Admin right was successfully toggled for user: "' + user.userName + '"'); + }).error((err) => { + $common.showError('Failed to toggle admin right for user: "' + $common.errorMessage(err) + '"'); + }) + .finally(() => user.adminChanging = false); + }; + }] +); http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/controllers/caches-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/controllers/caches-controller.js b/modules/web-console/src/main/js/controllers/caches-controller.js new file mode 100644 index 0000000..8f4bedd --- /dev/null +++ b/modules/web-console/src/main/js/controllers/caches-controller.js @@ -0,0 +1,493 @@ +/* + * 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. + */ + +// Controller for Caches screen. +import consoleModule from 'controllers/common-module'; + +consoleModule.controller('cachesController', [ + '$scope', '$http', '$state', '$filter', '$timeout', '$common', '$confirm', '$clone', '$loading', '$cleanup', '$unsavedChangesGuard', + function ($scope, $http, $state, $filter, $timeout, $common, $confirm, $clone, $loading, $cleanup, $unsavedChangesGuard) { + $unsavedChangesGuard.install($scope); + + var emptyCache = {empty: true}; + + var __original_value; + + var blank = { + evictionPolicy: {}, + cacheStoreFactory: {}, + nearConfiguration: {} + }; + + // We need to initialize backupItem with empty object in order to properly used from angular directives. + $scope.backupItem = emptyCache; + + $scope.ui = $common.formUI(); + $scope.ui.activePanels = [0]; + $scope.ui.topPanels = [0, 1, 2, 3]; + + $scope.hidePopover = $common.hidePopover; + $scope.saveBtnTipText = $common.saveBtnTipText; + $scope.widthIsSufficient = $common.widthIsSufficient; + + var showPopoverMessage = $common.showPopoverMessage; + + $scope.contentVisible = function () { + var item = $scope.backupItem; + + return !item.empty && (!item._id || _.find($scope.displayedRows, {_id: item._id})); + }; + + $scope.toggleExpanded = function () { + $scope.ui.expanded = !$scope.ui.expanded; + + $common.hidePopover(); + }; + + $scope.caches = []; + $scope.domains = []; + + function _cacheLbl(cache) { + return cache.name + ', ' + cache.cacheMode + ', ' + cache.atomicityMode; + } + + function selectFirstItem() { + if ($scope.caches.length > 0) + $scope.selectItem($scope.caches[0]); + } + + function cacheDomains(item) { + return _.reduce($scope.domains, function (memo, domain) { + if (item && _.includes(item.domains, domain.value)) { + memo.push(domain.meta); + } + + return memo; + }, []); + } + + $loading.start('loadingCachesScreen'); + + // When landing on the page, get caches and show them. + $http.post('/api/v1/configuration/caches/list') + .success(function (data) { + var validFilter = $filter('domainsValidation'); + + $scope.spaces = data.spaces; + + _.forEach(data.caches, function (cache) { + cache.label = _cacheLbl(cache); + }); + + $scope.caches = data.caches; + + $scope.clusters = _.map(data.clusters, function (cluster) { + return { + value: cluster._id, + label: cluster.name, + caches: cluster.caches + }; + }); + + $scope.domains = _.sortBy(_.map(validFilter(data.domains, true, false), function (domain) { + return { + value: domain._id, + label: domain.valueType, + kind: domain.kind, + meta: domain + }; + }), 'label'); + + if ($state.params.id) + $scope.createItem($state.params.id); + else { + var lastSelectedCache = angular.fromJson(sessionStorage.lastSelectedCache); + + if (lastSelectedCache) { + var idx = _.findIndex($scope.caches, function (cache) { + return cache._id === lastSelectedCache; + }); + + if (idx >= 0) + $scope.selectItem($scope.caches[idx]); + else { + sessionStorage.removeItem('lastSelectedCache'); + + selectFirstItem(); + } + } + else + selectFirstItem(); + } + + $scope.$watch('ui.inputForm.$valid', function(valid) { + if (valid && __original_value === JSON.stringify($cleanup($scope.backupItem))) { + $scope.ui.inputForm.$dirty = false; + } + }); + + $scope.$watch('backupItem', function (val) { + var form = $scope.ui.inputForm; + + if (form.$pristine || (form.$valid && __original_value === JSON.stringify($cleanup(val)))) + form.$setPristine(); + else + form.$setDirty(); + }, true); + }) + .catch(function (errMsg) { + $common.showError(errMsg); + }) + .finally(function () { + $scope.ui.ready = true; + $scope.ui.inputForm.$setPristine(); + $loading.finish('loadingCachesScreen'); + }); + + $scope.selectItem = function (item, backup) { + function selectItem() { + $scope.selectedItem = item; + + try { + if (item && item._id) + sessionStorage.lastSelectedCache = angular.toJson(item._id); + else + sessionStorage.removeItem('lastSelectedCache'); + } + catch (ignored) { + // No-op. + } + + if (backup) + $scope.backupItem = backup; + else if (item) + $scope.backupItem = angular.copy(item); + else + $scope.backupItem = emptyCache; + + $scope.backupItem = angular.merge({}, blank, $scope.backupItem); + + __original_value = JSON.stringify($cleanup($scope.backupItem)); + + if ($common.getQueryVariable('new')) + $state.go('base.configuration.caches'); + } + + $common.confirmUnsavedChanges($scope.backupItem && $scope.ui.inputForm.$dirty, selectItem); + }; + + function prepareNewItem(id) { + return { + space: $scope.spaces[0]._id, + cacheMode: 'PARTITIONED', + atomicityMode: 'ATOMIC', + readFromBackup: true, + copyOnRead: true, + clusters: id && _.find($scope.clusters, {value: id}) + ? [id] : _.map($scope.clusters, function (cluster) { return cluster.value; }), + domains: id && _.find($scope.domains, { value: id }) ? [id] : [], + cacheStoreFactory: {CacheJdbcBlobStoreFactory: {connectVia: 'DataSource'}} + }; + } + + // Add new cache. + $scope.createItem = function (id) { + $timeout(function () { + $common.ensureActivePanel($scope.ui, 'general', 'cacheName'); + }); + + $scope.selectItem(undefined, prepareNewItem(id)); + }; + + function checkDataSources() { + var clusters = _.filter($scope.clusters, function (cluster) { + return _.includes($scope.backupItem.clusters, cluster.value); + }); + + var checkRes = { checked: true }; + + var failCluster = _.find(clusters, function (cluster) { + var caches = _.filter($scope.caches, function (cache) { + return cache._id !== $scope.backupItem._id && _.find(cluster.caches, function (clusterCache) { + return clusterCache === cache._id; + }); + }); + + caches.push($scope.backupItem); + + checkRes = $common.checkCachesDataSources(caches, $scope.backupItem); + + return !checkRes.checked; + }); + + if (!checkRes.checked) { + return showPopoverMessage($scope.ui, 'store', checkRes.firstCache.cacheStoreFactory.kind === 'CacheJdbcPojoStoreFactory' ? 'pojoDialect' : 'blobDialect', + 'Found cache "' + checkRes.secondCache.name + '" in cluster "' + failCluster.label + '" ' + + 'with the same data source bean name "' + checkRes.firstCache.cacheStoreFactory[checkRes.firstCache.cacheStoreFactory.kind].dataSourceBean + + '" and different database: "' + $common.cacheStoreJdbcDialectsLabel(checkRes.firstDB) + '" in current cache and "' + + $common.cacheStoreJdbcDialectsLabel(checkRes.secondDB) + '" in "' + checkRes.secondCache.name + '"', 10000); + } + + return true; + } + + // Check cache logical consistency. + function validate(item) { + $common.hidePopover(); + + if ($common.isEmptyString(item.name)) + return showPopoverMessage($scope.ui, 'general', 'cacheName', 'Cache name should not be empty!'); + + if (item.memoryMode === 'ONHEAP_TIERED' && item.offHeapMaxMemory > 0 && !$common.isDefined(item.evictionPolicy.kind)) + return showPopoverMessage($scope.ui, 'memory', 'evictionPolicyKind', 'Eviction policy should not be configured!'); + + var form = $scope.ui.inputForm; + var errors = form.$error; + var errKeys = Object.keys(errors); + + if (errKeys && errKeys.length > 0) { + var firstErrorKey = errKeys[0]; + + var firstError = errors[firstErrorKey][0]; + var actualError = firstError.$error[firstErrorKey][0]; + + var errNameFull = actualError.$name; + var errNameShort = errNameFull; + + if (errNameShort.endsWith('TextInput')) + errNameShort = errNameShort.substring(0, errNameShort.length - 9); + + var extractErrorMessage = function (errName) { + try { + return errors[firstErrorKey][0].$errorMessages[errName][firstErrorKey]; + } + catch(ignored) { + try { + msg = form[firstError.$name].$errorMessages[errName][firstErrorKey]; + } + catch(ignited) { + return false; + } + } + }; + + var msg = extractErrorMessage(errNameFull) || extractErrorMessage(errNameShort) || 'Invalid value!'; + + return showPopoverMessage($scope.ui, firstError.$name, errNameFull, msg); + } + + if (item.memoryMode === 'OFFHEAP_VALUES' && !_.isEmpty(item.domains)) + return showPopoverMessage($scope.ui, 'memory', 'memoryMode', + 'Query indexing could not be enabled while values are stored off-heap!'); + + if (item.memoryMode === 'OFFHEAP_TIERED' && !$common.isDefined(item.offHeapMaxMemory)) + return showPopoverMessage($scope.ui, 'memory', 'offHeapMaxMemory', + 'Off-heap max memory should be specified!'); + + var cacheStoreFactorySelected = item.cacheStoreFactory && item.cacheStoreFactory.kind; + + if (cacheStoreFactorySelected) { + var storeFactory = item.cacheStoreFactory[item.cacheStoreFactory.kind]; + + if (item.cacheStoreFactory.kind === 'CacheJdbcPojoStoreFactory') { + if ($common.isEmptyString(storeFactory.dataSourceBean)) + return showPopoverMessage($scope.ui, 'store', 'dataSourceBean', + 'Data source bean name should not be empty!'); + + if (!$common.isValidJavaIdentifier('Data source bean', storeFactory.dataSourceBean, 'dataSourceBean', $scope.ui, 'store')) + return false; + + if (!storeFactory.dialect) + return showPopoverMessage($scope.ui, 'store', 'pojoDialect', + 'Dialect should not be empty!'); + + if (!checkDataSources()) + return false; + } + + if (item.cacheStoreFactory.kind === 'CacheJdbcBlobStoreFactory') { + if (storeFactory.connectVia === 'URL') { + if ($common.isEmptyString(storeFactory.connectionUrl)) + return showPopoverMessage($scope.ui, 'store', 'connectionUrl', + 'Connection URL should not be empty!'); + + if ($common.isEmptyString(storeFactory.user)) + return showPopoverMessage($scope.ui, 'store', 'user', + 'User should not be empty!'); + } + else { + if ($common.isEmptyString(storeFactory.dataSourceBean)) + return showPopoverMessage($scope.ui, 'store', 'dataSourceBean', + 'Data source bean name should not be empty!'); + + if (!$common.isValidJavaIdentifier('Data source bean', storeFactory.dataSourceBean, 'dataSourceBean', $scope.ui, 'store')) + return false; + + if (!storeFactory.dialect) + return showPopoverMessage($scope.ui, 'store', 'blobDialect', + 'Database should not be empty!'); + + if (!checkDataSources()) + return false; + } + } + } + + if ((item.readThrough || item.writeThrough) && !cacheStoreFactorySelected) + return showPopoverMessage($scope.ui, 'store', 'cacheStoreFactory', + (item.readThrough ? 'Read' : 'Write') + ' through are enabled but store is not configured!'); + + if (item.writeBehindEnabled && !cacheStoreFactorySelected) + return showPopoverMessage($scope.ui, 'store', 'cacheStoreFactory', + 'Write behind enabled but store is not configured!'); + + if (cacheStoreFactorySelected) { + if (!item.readThrough && !item.writeThrough) + return showPopoverMessage($scope.ui, 'store', 'readThroughTooltip', + 'Store is configured but read/write through are not enabled!'); + } + + if (item.writeBehindFlushSize === 0 && item.writeBehindFlushFrequency === 0) + return showPopoverMessage($scope.ui, 'store', 'writeBehindFlushSize', + 'Both "Flush frequency" and "Flush size" are not allowed as 0!'); + + if (item.cacheMode !== 'LOCAL' && item.rebalanceMode !== 'NONE' && item.rebalanceBatchSize === 0) + return showPopoverMessage($scope.ui, 'rebalance', 'rebalanceBatchSize', + 'Batch size should be more than 0 if rebalance mode is "SYNC" or "ASYNC" !', 10000); + + return true; + } + + // Save cache in database. + function save(item) { + $http.post('/api/v1/configuration/caches/save', item) + .success(function (_id) { + item.label = _cacheLbl(item); + + $scope.ui.inputForm.$setPristine(); + + var idx = _.findIndex($scope.caches, function (cache) { + return cache._id === _id; + }); + + if (idx >= 0) + angular.merge($scope.caches[idx], item); + else { + item._id = _id; + $scope.caches.push(item); + } + + $scope.selectItem(item); + + $common.showInfo('Cache "' + item.name + '" saved.'); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + } + + // Save cache. + $scope.saveItem = function () { + var item = $scope.backupItem; + + angular.extend(item, $common.autoCacheStoreConfiguration(item, cacheDomains(item))); + + if (validate(item)) + save(item); + }; + + function _cacheNames() { + return _.map($scope.caches, function (cache) { + return cache.name; + }); + } + + // Clone cache with new name. + $scope.cloneItem = function () { + if (validate($scope.backupItem)) { + $clone.confirm($scope.backupItem.name, _cacheNames()).then(function (newName) { + var item = angular.copy($scope.backupItem); + + delete item._id; + + item.name = newName; + + save(item); + }); + } + }; + + // Remove cache from db. + $scope.removeItem = function () { + var selectedItem = $scope.selectedItem; + + $confirm.confirm('Are you sure you want to remove cache: "' + selectedItem.name + '"?') + .then(function () { + var _id = selectedItem._id; + + $http.post('/api/v1/configuration/caches/remove', {_id: _id}) + .success(function () { + $common.showInfo('Cache has been removed: ' + selectedItem.name); + + var caches = $scope.caches; + + var idx = _.findIndex(caches, function (cache) { + return cache._id === _id; + }); + + if (idx >= 0) { + caches.splice(idx, 1); + + if (caches.length > 0) + $scope.selectItem(caches[0]); + else + $scope.backupItem = emptyCache; + } + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }); + }; + + // Remove all caches from db. + $scope.removeAllItems = function () { + $confirm.confirm('Are you sure you want to remove all caches?') + .then(function () { + $http.post('/api/v1/configuration/caches/remove/all') + .success(function () { + $common.showInfo('All caches have been removed'); + + $scope.caches = []; + $scope.backupItem = emptyCache; + $scope.ui.inputForm.$setPristine(); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }); + }; + + $scope.resetAll = function () { + $confirm.confirm('Are you sure you want to undo all changes for current cache?') + .then(function () { + $scope.backupItem = $scope.selectedItem ? angular.copy($scope.selectedItem) : prepareNewItem(); + $scope.ui.inputForm.$setPristine(); + }); + }; + }] +); http://git-wip-us.apache.org/repos/asf/ignite/blob/eb5ac0ae/modules/web-console/src/main/js/controllers/clusters-controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/controllers/clusters-controller.js b/modules/web-console/src/main/js/controllers/clusters-controller.js new file mode 100644 index 0000000..8aecc9f --- /dev/null +++ b/modules/web-console/src/main/js/controllers/clusters-controller.js @@ -0,0 +1,555 @@ +/* + * 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. + */ + +// Controller for Clusters screen. +import consoleModule from 'controllers/common-module'; + +consoleModule.controller('clustersController', [ + '$rootScope', '$scope', '$http', '$state', '$timeout', '$common', '$confirm', '$clone', '$loading', '$cleanup', '$unsavedChangesGuard', 'igniteEventGroups', 'DemoInfo', + function ($root, $scope, $http, $state, $timeout, $common, $confirm, $clone, $loading, $cleanup, $unsavedChangesGuard, igniteEventGroups, DemoInfo) { + $unsavedChangesGuard.install($scope); + + var emptyCluster = {empty: true}; + + var __original_value; + + var blank = { + atomicConfiguration: {}, + binaryConfiguration: {}, + communication: {}, + connector: {}, + discovery: {}, + marshaller: {}, + sslContextFactory: {}, + swapSpaceSpi: {}, + transactionConfiguration: {} + }; + + // We need to initialize backupItem with empty object in order to properly used from angular directives. + $scope.backupItem = emptyCluster; + + $scope.ui = $common.formUI(); + $scope.ui.activePanels = [0]; + $scope.ui.topPanels = [0]; + + $scope.hidePopover = $common.hidePopover; + $scope.saveBtnTipText = $common.saveBtnTipText; + $scope.widthIsSufficient = $common.widthIsSufficient; + + var showPopoverMessage = $common.showPopoverMessage; + + $scope.contentVisible = function () { + var item = $scope.backupItem; + + return !item.empty && (!item._id || _.find($scope.displayedRows, {_id: item._id})); + }; + + $scope.toggleExpanded = function () { + $scope.ui.expanded = !$scope.ui.expanded; + + $common.hidePopover(); + }; + + $scope.discoveries = [ + {value: 'Vm', label: 'Static IPs'}, + {value: 'Multicast', label: 'Multicast'}, + {value: 'S3', label: 'AWS S3'}, + {value: 'Cloud', label: 'Apache jclouds'}, + {value: 'GoogleStorage', label: 'Google cloud storage'}, + {value: 'Jdbc', label: 'JDBC'}, + {value: 'SharedFs', label: 'Shared filesystem'}, + {value: 'ZooKeeper', label: 'Apache ZooKeeper'} + ]; + + $scope.swapSpaceSpis = [ + {value: 'FileSwapSpaceSpi', label: 'File-based swap'}, + {value: undefined, label: 'Not set'} + ]; + + $scope.eventGroups = igniteEventGroups; + + $scope.clusters = []; + + function _clusterLbl (cluster) { + return cluster.name + ', ' + _.find($scope.discoveries, {value: cluster.discovery.kind}).label; + } + + function selectFirstItem() { + if ($scope.clusters.length > 0) + $scope.selectItem($scope.clusters[0]); + } + + function clusterCaches(item) { + return _.reduce($scope.caches, function (memo, cache) { + if (item && _.includes(item.caches, cache.value)) { + memo.push(cache.cache); + } + + return memo; + }, []); + } + + $loading.start('loadingClustersScreen'); + + // When landing on the page, get clusters and show them. + $http.post('/api/v1/configuration/clusters/list') + .success(function (data) { + $scope.spaces = data.spaces; + + _.forEach(data.clusters, function (cluster) { + cluster.label = _clusterLbl(cluster); + }); + + $scope.clusters = data.clusters; + + $scope.caches = _.map(data.caches, function (cache) { + return {value: cache._id, label: cache.name, cache: cache}; + }); + + $scope.igfss = _.map(data.igfss, function (igfs) { + return {value: igfs._id, label: igfs.name, igfs: igfs}; + }); + + if ($state.params.id) + $scope.createItem($state.params.id); + else { + var lastSelectedCluster = angular.fromJson(sessionStorage.lastSelectedCluster); + + if (lastSelectedCluster) { + var idx = _.findIndex($scope.clusters, function (cluster) { + return cluster._id === lastSelectedCluster; + }); + + if (idx >= 0) + $scope.selectItem($scope.clusters[idx]); + else { + sessionStorage.removeItem('lastSelectedCluster'); + + selectFirstItem(); + } + } + else + selectFirstItem(); + } + + $scope.$watch('ui.inputForm.$valid', function(valid) { + if (valid && __original_value === JSON.stringify($cleanup($scope.backupItem))) { + $scope.ui.inputForm.$dirty = false; + } + }); + + $scope.$watch('backupItem', function (val) { + var form = $scope.ui.inputForm; + + if (form.$pristine || (form.$valid && __original_value === JSON.stringify($cleanup(val)))) + form.$setPristine(); + else + form.$setDirty(); + }, true); + + if ($root.IgniteDemoMode) { + if (sessionStorage.showDemoInfo !== 'true') { + sessionStorage.showDemoInfo = 'true'; + + DemoInfo.show(); + } + } + + }) + .catch(function (errMsg) { + $common.showError(errMsg); + }) + .finally(function () { + $scope.ui.ready = true; + $scope.ui.inputForm.$setPristine(); + $loading.finish('loadingClustersScreen'); + }); + + $scope.selectItem = function (item, backup) { + function selectItem() { + $scope.selectedItem = item; + + try { + if (item && item._id) + sessionStorage.lastSelectedCluster = angular.toJson(item._id); + else + sessionStorage.removeItem('lastSelectedCluster'); + } + catch (ignored) { + // No-op. + } + + if (backup) + $scope.backupItem = backup; + else if (item) + $scope.backupItem = angular.copy(item); + else + $scope.backupItem = emptyCluster ; + + $scope.backupItem = angular.merge({}, blank, $scope.backupItem); + + __original_value = JSON.stringify($cleanup($scope.backupItem)); + + if ($common.getQueryVariable('new')) + $state.go('base.configuration.clusters'); + } + + $common.confirmUnsavedChanges($scope.backupItem && $scope.ui.inputForm.$dirty, selectItem); + }; + + function prepareNewItem(id) { + var newItem = { + discovery: { + kind: 'Multicast', + Vm: {addresses: ['127.0.0.1:47500..47510']}, + Multicast: {addresses: ['127.0.0.1:47500..47510']} + }, + binaryConfiguration: { + typeConfigurations: [], + compactFooter: true + }, + communication: { + tcpNoDelay: true + }, + connector: { + noDelay: true + } + }; + + newItem = angular.merge({}, blank, newItem); + + newItem.caches = id && _.find($scope.caches, {value: id}) ? [id] : []; + newItem.igfss = id && _.find($scope.igfss, {value: id}) ? [id] : []; + newItem.space = $scope.spaces[0]._id; + + return newItem; + } + + // Add new cluster. + $scope.createItem = function(id) { + $timeout(function () { + $common.ensureActivePanel($scope.ui, "general", 'clusterName'); + }); + + $scope.selectItem(undefined, prepareNewItem(id)); + }; + + $scope.indexOfCache = function (cacheId) { + return _.findIndex($scope.caches, function (cache) { + return cache.value === cacheId; + }); + }; + + // Check cluster logical consistency. + function validate(item) { + $common.hidePopover(); + + if ($common.isEmptyString(item.name)) + return showPopoverMessage($scope.ui, 'general', 'clusterName', 'Cluster name should not be empty!'); + + var form = $scope.ui.inputForm; + var errors = form.$error; + var errKeys = Object.keys(errors); + + if (errKeys && errKeys.length > 0) { + var firstErrorKey = errKeys[0]; + + var firstError = errors[firstErrorKey][0]; + var actualError = firstError.$error[firstErrorKey][0]; + + var errNameFull = actualError.$name; + var errNameShort = errNameFull; + + if (errNameShort.endsWith('TextInput')) + errNameShort = errNameShort.substring(0, errNameShort.length - 9); + + var extractErrorMessage = function (errName) { + try { + return errors[firstErrorKey][0].$errorMessages[errName][firstErrorKey]; + } + catch(ignored) { + try { + msg = form[firstError.$name].$errorMessages[errName][firstErrorKey]; + } + catch(ignited) { + return false; + } + } + }; + + var msg = extractErrorMessage(errNameFull) || extractErrorMessage(errNameShort) || 'Invalid value!'; + + return showPopoverMessage($scope.ui, firstError.$name, errNameFull, msg); + } + + var caches = _.filter(_.map($scope.caches, function (scopeCache) { + return scopeCache.cache; + }), function (cache) { + return _.includes($scope.backupItem.caches, cache._id); + }); + + var checkRes = $common.checkCachesDataSources(caches); + + if (!checkRes.checked) { + return showPopoverMessage($scope.ui, 'general', 'caches', + 'Found caches "' + checkRes.firstCache.name + '" and "' + checkRes.secondCache.name + '" ' + + 'with the same data source bean name "' + checkRes.firstCache.cacheStoreFactory[checkRes.firstCache.cacheStoreFactory.kind].dataSourceBean + + '" and different databases: "' + $common.cacheStoreJdbcDialectsLabel(checkRes.firstDB) + '" in "' + checkRes.firstCache.name + '" and "' + + $common.cacheStoreJdbcDialectsLabel(checkRes.secondDB) + '" in "' + checkRes.secondCache.name + '"', 10000); + } + + var b = item.binaryConfiguration; + + if ($common.isDefined(b)) { + if (!_.isEmpty(b.typeConfigurations)) { + var sameName = function (t, ix) { + return ix < typeIx && t.typeName === type.typeName; + }; + + for (var typeIx = 0; typeIx < b.typeConfigurations.length; typeIx++) { + var type = b.typeConfigurations[typeIx]; + + if ($common.isEmptyString(type.typeName)) + return showPopoverMessage($scope.ui, 'binary', 'typeName' + typeIx, 'Type name should be specified!'); + + if (_.find(b.typeConfigurations, sameName)) + return showPopoverMessage($scope.ui, 'binary', 'typeName' + typeIx, 'Type with such name is already specified!'); + } + } + } + + var c = item.communication; + + if ($common.isDefined(c)) { + if ($common.isDefined(c.unacknowledgedMessagesBufferSize)) { + if ($common.isDefined(c.messageQueueLimit)) + if (c.unacknowledgedMessagesBufferSize < 5 * c.messageQueueLimit) + return showPopoverMessage($scope.ui, 'communication', 'unacknowledgedMessagesBufferSize', 'Maximum number of stored unacknowledged messages should be at least 5 * message queue limit!'); + + if ($common.isDefined(c.ackSendThreshold)) + if (c.unacknowledgedMessagesBufferSize < 5 * c.ackSendThreshold) + return showPopoverMessage($scope.ui, 'communication', 'unacknowledgedMessagesBufferSize', 'Maximum number of stored unacknowledged messages should be at least 5 * ack send threshold!'); + } + + if (c.sharedMemoryPort === 0) + return showPopoverMessage($scope.ui, 'communication', 'sharedMemoryPort', 'Shared memory port should be more than "0" or equals to "-1"!'); + } + + var r = item.connector; + + if ($common.isDefined(r)) { + if (r.sslEnabled && $common.isEmptyString(r.sslFactory)) + return showPopoverMessage($scope.ui, 'connector', 'connectorSslFactory', 'SSL factory should not be empty!'); + } + + var d = item.discovery; + + if (d) { + if ((d.maxAckTimeout != undefined ? d.maxAckTimeout : 600000) < (d.ackTimeout || 5000)) + return showPopoverMessage($scope.ui, 'discovery', 'ackTimeout', 'Acknowledgement timeout should be less than max acknowledgement timeout!'); + + if (d.kind === 'Vm' && d.Vm && d.Vm.addresses.length === 0) + return showPopoverMessage($scope.ui, 'general', 'addresses', 'Addresses are not specified!'); + + if (d.kind === 'S3' && d.S3 && $common.isEmptyString(d.S3.bucketName)) + return showPopoverMessage($scope.ui, 'general', 'bucketName', 'Bucket name should not be empty!'); + + if (d.kind === 'Cloud' && d.Cloud) { + if ($common.isEmptyString(d.Cloud.identity)) + return showPopoverMessage($scope.ui, 'general', 'identity', 'Identity should not be empty!'); + + if ($common.isEmptyString(d.Cloud.provider)) + return showPopoverMessage($scope.ui, 'general', 'provider', 'Provider should not be empty!'); + } + + if (d.kind === 'GoogleStorage' && d.GoogleStorage) { + if ($common.isEmptyString(d.GoogleStorage.projectName)) + return showPopoverMessage($scope.ui, 'general', 'projectName', 'Project name should not be empty!'); + + if ($common.isEmptyString(d.GoogleStorage.bucketName)) + return showPopoverMessage($scope.ui, 'general', 'bucketName', 'Bucket name should not be empty!'); + + if ($common.isEmptyString(d.GoogleStorage.serviceAccountP12FilePath)) + return showPopoverMessage($scope.ui, 'general', 'serviceAccountP12FilePath', 'Private key path should not be empty!'); + + if ($common.isEmptyString(d.GoogleStorage.serviceAccountId)) + return showPopoverMessage($scope.ui, 'general', 'serviceAccountId', 'Account ID should not be empty!'); + } + } + + var swapKind = item.swapSpaceSpi && item.swapSpaceSpi.kind; + + if (swapKind && item.swapSpaceSpi[swapKind]) { + var swap = item.swapSpaceSpi[swapKind]; + + var sparsity = swap.maximumSparsity; + + if ($common.isDefined(sparsity) && (sparsity < 0 || sparsity >= 1)) + return showPopoverMessage($scope.ui, 'swap', 'maximumSparsity', 'Maximum sparsity should be more or equal 0 and less than 1!'); + + var readStripesNumber = swap.readStripesNumber; + + if (readStripesNumber && !(readStripesNumber == -1 || (readStripesNumber & (readStripesNumber - 1)) == 0)) + return showPopoverMessage($scope.ui, 'swap', 'readStripesNumber', 'Read stripe size must be positive and power of two!'); + } + + if (item.sslEnabled) { + if (!$common.isDefined(item.sslContextFactory) || $common.isEmptyString(item.sslContextFactory.keyStoreFilePath)) + return showPopoverMessage($scope.ui, 'sslConfiguration', 'keyStoreFilePath', 'Key store file should not be empty!'); + + if ($common.isEmptyString(item.sslContextFactory.trustStoreFilePath) && _.isEmpty(item.sslContextFactory.trustManagers)) + return showPopoverMessage($scope.ui, 'sslConfiguration', 'sslConfiguration-title', 'Trust storage file or managers should be configured!'); + } + + if (!swapKind && item.caches) { + for (var i = 0; i < item.caches.length; i++) { + var idx = $scope.indexOfCache(item.caches[i]); + + if (idx >= 0) { + var cache = $scope.caches[idx]; + + if (cache.cache.swapEnabled) + return showPopoverMessage($scope.ui, 'swap', 'swapSpaceSpi', + 'Swap space SPI is not configured, but cache "' + cache.label + '" configured to use swap!'); + } + } + } + + if (item.rebalanceThreadPoolSize && item.systemThreadPoolSize && item.systemThreadPoolSize <= item.rebalanceThreadPoolSize) + return showPopoverMessage($scope.ui, 'pools', 'rebalanceThreadPoolSize', + 'Rebalance thread pool size exceed or equals System thread pool size!'); + + return true; + } + + // Save cluster in database. + function save(item) { + $http.post('/api/v1/configuration/clusters/save', item) + .success(function (_id) { + item.label = _clusterLbl(item); + + $scope.ui.inputForm.$setPristine(); + + var idx = _.findIndex($scope.clusters, (cluster) => cluster._id === _id); + + if (idx >= 0) + angular.merge($scope.clusters[idx], item); + else { + item._id = _id; + $scope.clusters.push(item); + } + + $scope.selectItem(item); + + $common.showInfo('Cluster "' + item.name + '" saved.'); + }) + .error((err) => $common.showError(err)); + } + + // Save cluster. + $scope.saveItem = function () { + var item = $scope.backupItem; + + var swapSpi = $common.autoClusterSwapSpiConfiguration(item, clusterCaches(item)); + + if (swapSpi) + angular.extend(item, swapSpi); + + if (validate(item)) + save(item); + }; + + function _clusterNames() { + return _.map($scope.clusters, function (cluster) { + return cluster.name; + }); + } + + // Clone cluster with new name. + $scope.cloneItem = function () { + if (validate($scope.backupItem)) { + $clone.confirm($scope.backupItem.name, _clusterNames()).then(function (newName) { + var item = angular.copy($scope.backupItem); + + delete item._id; + item.name = newName; + + save(item); + }); + } + }; + + // Remove cluster from db. + $scope.removeItem = function () { + var selectedItem = $scope.selectedItem; + + $confirm.confirm('Are you sure you want to remove cluster: "' + selectedItem.name + '"?') + .then(function () { + var _id = selectedItem._id; + + $http.post('/api/v1/configuration/clusters/remove', {_id: _id}) + .success(function () { + $common.showInfo('Cluster has been removed: ' + selectedItem.name); + + var clusters = $scope.clusters; + + var idx = _.findIndex(clusters, function (cluster) { + return cluster._id === _id; + }); + + if (idx >= 0) { + clusters.splice(idx, 1); + + if (clusters.length > 0) + $scope.selectItem(clusters[0]); + else + $scope.backupItem = emptyCluster; + } + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }); + }; + + // Remove all clusters from db. + $scope.removeAllItems = function () { + $confirm.confirm('Are you sure you want to remove all clusters?') + .then(function () { + $http.post('/api/v1/configuration/clusters/remove/all') + .success(function () { + $common.showInfo('All clusters have been removed'); + + $scope.clusters = []; + $scope.backupItem = emptyCluster; + $scope.ui.inputForm.$setPristine(); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }); + }; + + $scope.resetAll = function() { + $confirm.confirm('Are you sure you want to undo all changes for current cluster?') + .then(function() { + $scope.backupItem = $scope.selectedItem ? angular.copy($scope.selectedItem) : prepareNewItem(); + $scope.ui.inputForm.$setPristine(); + }); + }; + }] +);
