http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/models/metadata.json ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/controllers/models/metadata.json b/modules/control-center-web/src/main/js/controllers/models/metadata.json new file mode 100644 index 0000000..7ef0e20 --- /dev/null +++ b/modules/control-center-web/src/main/js/controllers/models/metadata.json @@ -0,0 +1,279 @@ +{ + "screenTip": { + "workflowTitle": "On This Screen:", + "workflowContent": [ + "Manually enter Metadata", + "Load Metadata from Database", + "more-info" + ], + "whatsNextTitle": "Next Steps:", + "whatsNextContent": [ + "Continue to <a href='/configuration/summary'>Summary</a>", + "Back to <a href='/configuration/caches'>Caches</a>", + "Back to <a href='/configuration/clusters'>Clusters</a>" + ] + }, + "moreInfo": { + "title": "Metadata page", + "content": ["Manage you type metadata on current page.", + "Metadata can be assigned to specified <a href='/configuration/caches'>caches</a>.", + "Generated cluster with caches with metadata configuration available on <a href='/configuration/summary'>summary</a> page."] + }, + "metadata": [ + { + "label": "Metadata common", + "group": "general", + "tip": [ + "Metadata properties common to Query and Store" + ], + "fields": [ + { + "label": "Caches", + "id": "caches", + "type": "dropdown-multiple", + "model": "caches", + "placeholder": "Choose caches", + "placeholderEmpty": "No caches configured", + "items": "caches", + "tip": [ + "Select caches to associate database with cache" + ], + "addLink": { + "label": "Add cache(s)", + "ref": "/configuration/caches?new" + } + }, + { + "label": "Key type", + "id": "keyType", + "type": "typeahead", + "items": "javaBuildInClasses", + "model": "keyType", + "required": true, + "placeholder": "Full class name for Key", + "tip": [ + "Key class used to store key in cache" + ] + }, + { + "label": "Value type", + "id": "valueType", + "type": "text", + "model": "valueType", + "required": true, + "placeholder": "Full class name for Value", + "tip": [ + "Value class used to store value in cache" + ] + } + ] + }, + { + "label": "Metadata for SQL query", + "group": "query", + "tip": [ + "Metadata properties for fields queries" + ], + "fields": [ + { + "label": "Not indexed fields", + "id": "queryFields", + "ui": "table-pair", + "type": "queryFieldsFirst", + "model": "queryFields", + "keyName": "name", + "valueName": "className", + "focusId": "QryField", + "addTip": "Add not indexed field to query", + "removeTip": "Remove field", + "tip": [ + "Collection of name-to-type mappings to be queried, in addition to indexed fields" + ] + }, + { + "label": "Ascending indexed fields", + "id": "ascendingFields", + "ui": "table-pair", + "type": "queryFields", + "model": "ascendingFields", + "keyName": "name", + "valueName": "className", + "focusId": "AscField", + "addTip": "Add field to index in ascending order", + "removeTip": "Remove field", + "tip": [ + "Collection of name-to-type mappings to index in ascending order" + ] + }, + { + "label": "Descending indexed fields", + "id": "descendingFields", + "ui": "table-pair", + "type": "queryFields", + "model": "descendingFields", + "keyName": "name", + "valueName": "className", + "focusId": "DescField", + "addTip": "Add field to index in descending order", + "removeTip": "Remove field", + "tip": [ + "Collection of name-to-type mappings to index in descending order" + ] + }, + { + "label": "Text indexed fields", + "id": "textFields", + "type": "table-simple", + "model": "textFields", + "placeholder": "Field name", + "focusId": "TextField", + "addTip": "Add field to index as text", + "removeTip": "Remove field", + "tableTip": [ + "Fields to index as text" + ], + "tip": [ + "Field to index as text" + ] + }, + { + "label": "Group indexes", + "id": "groups", + "type": "table-query-groups", + "model": "groups", + "addTip": "Add new group", + "removeTip": "Remove group", + "addItemTip": "Add new field to group", + "removeItemTip": "Remove field from group", + "tip": [ + "Collection of group indexes" + ] + } + ] + }, + { + "label": "Metadata for cache store", + "group": "store", + "tip": [ + "Metadata properties for binding database with cache via POJO cache store" + ], + "fields": [ + { + "label": "Database schema", + "id": "databaseSchema", + "type": "text", + "model": "databaseSchema", + "placeholder": "Input DB schema name", + "tip": [ + "Schema name in database" + ] + }, + { + "label": "Database table", + "id": "databaseTable", + "type": "text", + "model": "databaseTable", + "placeholder": "Input DB table name", + "tip": [ + "Table name in database" + ] + }, + { + "label": "Key fields", + "id": "keyFields", + "type": "table-db-fields", + "model": "keyFields", + "keyName": "name", + "valueName": "className", + "hide": "isJavaBuildInClass()", + "focusId": "KeyField", + "addTip": "Add key field", + "removeTip": "Remove key field", + "tip": [ + "Collection of key fields descriptions for CacheJdbcPojoStore" + ] + }, + { + "label": "Value fields", + "id": "valueFields", + "type": "table-db-fields", + "model": "valueFields", + "keyName": "name", + "valueName": "className", + "focusId": "ValueField", + "addTip": "Add value field", + "removeTip": "Remove value field", + "tip": [ + "Collection of value fields descriptions for CacheJdbcPojoStore" + ] + } + ] + } + ], + "metadataDb": [ + { + "label": "Driver JAR", + "id": "jdbcDriverJar", + "type": "dropdown", + "container": "false", + "model": "jdbcDriverJar", + "items": "jdbcDriverJars", + "tip": [ + "Select appropriate JAR with JDBC driver", + "To add another driver you need to place it into '/drivers' folder of Ignite Web Agent", + "Refer to Ignite Web Agent README.txt for for more information" + ] + }, + { + "label": "JDBC Driver", + "id": "jdbcDriverClass", + "type": "text", + "model": "jdbcDriverClass", + "placeholder": "Full class name of JDBC driver", + "tip": [ + "Full class name of JDBC driver that will be used to connect to database" + ] + }, + { + "label": "JDBC URL", + "id": "jdbcUrl", + "type": "text", + "model": "jdbcUrl", + "placeholder": "JDBC URL", + "tip": [ + "JDBC URL for connecting to database.", + "Refer to your database documentation for details" + ] + }, + { + "label": "User", + "id": "user", + "type": "text", + "model": "user", + "tip": [ + "User name for connecting to database" + ] + }, + { + "label": "Password", + "id": "password", + "type": "password", + "model": "password", + "onEnter": "loadMetadataNext()", + "tip": [ + "Password for connecting to database", + "Note, password would not be saved" + ] + }, + { + "label": "Tables only", + "id": "tablesOnly", + "type": "check", + "model": "tablesOnly", + "tip": [ + "If selected then only tables metadata will be parsed", + "Otherwise table and view metadata will be parsed" + ] + } + ] +}
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/models/summary.json ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/controllers/models/summary.json b/modules/control-center-web/src/main/js/controllers/models/summary.json new file mode 100644 index 0000000..88ad6b4 --- /dev/null +++ b/modules/control-center-web/src/main/js/controllers/models/summary.json @@ -0,0 +1,172 @@ +{ + "screenTip": { + "workflowTitle": "On This Screen:", + "workflowContent": [ + "Download XML Config", + "Download Java Code", + "Download Docker File", + "more-info" + ], + "whatsNextTitle": "Next Steps:", + "whatsNextContent": [ + "Deploy Ignite Servers", + "Connect Ignite Clients", + "Analyze with SQL" + ] + }, + "moreInfo": { + "title": "Summary page", + "content": ["Generated cluster's configuration showed on this page.", + "Configurations available in XML, Java and Dockerfile format for Server and Client mode.", + "Database table POJO classes for cluster's metadatas available on \"POJO\" tab.", + "Use \"Download\" button to receive configurations in ZIP file.", + "Go back to change configuration on <a href='/configuration/clusters'>clusters</a>, <a href='/configuration/caches'>caches</a> or <a href='/configuration/metadata'>metadata</a> pages." + ] + }, + "clientFields": [ + { + "label": "Near cache start size", + "type": "number", + "path": "nearConfiguration", + "model": "nearStartSize", + "placeholder": 375000, + "tip": [ + "Initial cache size for near cache which will be used to pre-create internal hash table after start" + ] + }, + { + "label": "Near cache eviction policy", + "type": "dropdown-details", + "settings": true, + "path": "nearConfiguration.nearEvictionPolicy", + "model": "kind", + "placeholder": "Choose eviction policy", + "items": "evictionPolicies", + "tip": [ + "Cache expiration policy" + ], + "details": { + "LRU": { + "expanded": false, + "fields": [ + { + "label": "Batch size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.LRU", + "model": "batchSize", + "placeholder": 1, + "tip": [ + "Number of entries to remove on shrink" + ] + }, + { + "label": "Max memory size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.LRU", + "model": "maxMemorySize", + "placeholder": 0, + "tip": [ + "Maximum allowed cache size in bytes" + ] + }, + { + "label": "Max size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.LRU", + "model": "maxSize", + "placeholder": 100000, + "tip": [ + "Maximum allowed size of cache before entry will start getting evicted" + ] + } + ] + }, + "RND": { + "expanded": false, + "fields": [ + { + "label": "Max size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.RND", + "model": "maxSize", + "placeholder": 100000, + "tip": [ + "Maximum allowed size of cache before entry will start getting evicted" + ] + } + ] + }, + "FIFO": { + "expanded": false, + "fields": [ + { + "label": "Batch size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.FIFO", + "model": "batchSize", + "placeholder": 1, + "tip": [ + "Number of entries to remove on shrink" + ] + }, + { + "label": "Max memory size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.FIFO", + "model": "maxMemorySize", + "placeholder": 0, + "tip": [ + "Maximum allowed cache size in bytes" + ] + }, + { + "label": "Max size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.FIFO", + "model": "maxSize", + "placeholder": 100000, + "tip": [ + "Maximum allowed size of cache before entry will start getting evicted" + ] + } + ] + }, + "SORTED": { + "expanded": false, + "fields": [ + { + "label": "Batch size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.SORTED", + "model": "batchSize", + "placeholder": 1, + "tip": [ + "Number of entries to remove on shrink" + ] + }, + { + "label": "Max memory size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.SORTED", + "model": "maxMemorySize", + "placeholder": 0, + "tip": [ + "Maximum allowed cache size in bytes" + ] + }, + { + "label": "Max size", + "type": "number", + "path": "nearConfiguration.nearEvictionPolicy.SORTED", + "model": "maxSize", + "placeholder": 100000, + "tip": [ + "Maximum allowed size of cache before entry will start getting evicted" + ] + } + ] + } + } + } + ] +} http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/profile-controller.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/controllers/profile-controller.js b/modules/control-center-web/src/main/js/controllers/profile-controller.js new file mode 100644 index 0000000..5d4567b --- /dev/null +++ b/modules/control-center-web/src/main/js/controllers/profile-controller.js @@ -0,0 +1,94 @@ +/* + * + * * 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 Profile screen. +consoleModule.controller('profileController', + ['$scope', '$http', '$common', '$focus', '$confirm', function ($scope, $http, $common, $focus, $confirm) { + $scope.profileUser = angular.copy($scope.user); + + if ($scope.profileUser && !$scope.profileUser.token) + $scope.profileUser.token = 'No security token. Regenerate please.'; + + $scope.generateToken = function () { + $confirm.confirm('Are you sure you want to change security token?') + .then(function () { + $scope.profileUser.token = $commonUtils.randomString(20); + }) + }; + + $scope.profileChanged = function () { + var old = $scope.user; + var cur = $scope.profileUser; + + return old.username != cur.username || old.email != cur.email || old.token != cur.token || + (cur.changePassword && !$common.isEmptyString(cur.newPassword)); + }; + + $scope.profileCouldBeSaved = function () { + return $scope.profileForm.$valid && $scope.profileChanged(); + }; + + $scope.saveBtnTipText = function () { + if (!$scope.profileForm.$valid) + return 'Invalid profile settings'; + + return $scope.profileChanged() ? 'Save profile' : 'Nothing to save'; + }; + + $scope.saveUser = function () { + var profile = $scope.profileUser; + + if (profile) { + var userName = profile.username; + var changeUsername = userName != $scope.user.username; + + var email = profile.email; + var changeEmail = email != $scope.user.email; + + var token = profile.token; + var changeToken = token != $scope.user.token; + + if (changeUsername || changeEmail || changeToken || profile.changePassword) { + $http.post('/profile/save', { + _id: profile._id, + userName: changeUsername ? userName : undefined, + email: changeEmail ? email : undefined, + token: changeToken ? token : undefined, + newPassword: profile.changePassword ? profile.newPassword : undefined + }).success(function (user) { + $common.showInfo('Profile saved.'); + + profile.changePassword = false; + profile.newPassword = null; + profile.confirmPassword = null; + + if (changeUsername) + $scope.user.username = userName; + + if (changeEmail) + $scope.user.email = email; + + $focus('profile-username'); + }).error(function (err) { + $common.showError('Failed to save profile: ' + $common.errorMessage(err)); + }); + } + } + }; +}]); http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/sql-controller.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/controllers/sql-controller.js b/modules/control-center-web/src/main/js/controllers/sql-controller.js new file mode 100644 index 0000000..72afcc9 --- /dev/null +++ b/modules/control-center-web/src/main/js/controllers/sql-controller.js @@ -0,0 +1,1097 @@ +/* + * + * * 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 SQL notebook screen. +consoleModule.controller('sqlController', + ['$scope', '$window','$controller', '$http', '$timeout', '$common', '$confirm', '$interval', '$popover', '$loading', + function ($scope, $window, $controller, $http, $timeout, $common, $confirm, $interval, $popover, $loading) { + // Initialize the super class and extend it. + angular.extend(this, $controller('agent-download', {$scope: $scope})); + + $scope.agentGoal = 'execute sql statements'; + $scope.agentTestDriveOption = '--test-drive-sql'; + + $scope.joinTip = $common.joinTip; + + $scope.caches = []; + + $scope.pageSizes = [50, 100, 200, 400, 800, 1000]; + + $scope.modes = $common.mkOptions(['PARTITIONED', 'REPLICATED', 'LOCAL']); + + $scope.timeUnit = [ + {value: 1000, label: 'seconds', short: 's'}, + {value: 60000, label: 'minutes', short: 'm'}, + {value: 3600000, label: 'hours', short: 'h'} + ]; + + $scope.exportDropdown = [{ 'text': 'Export all', 'click': 'exportAll(paragraph)'}]; + + $scope.treeOptions = { + nodeChildren: 'children', + dirSelectable: true, + injectClasses: { + iExpanded: 'fa fa-minus-square-o', + iCollapsed: 'fa fa-plus-square-o' + } + }; + + var TIME_LINE = {value: -1, type: 'java.sql.Date', label: 'TIME_LINE'}; + + var chartHistory = []; + + var HISTORY_LENGTH = 100; + + $scope.chartRemoveKeyColumn = function (paragraph, index) { + paragraph.chartKeyCols.splice(index, 1); + + _chartApplySettings(paragraph, true); + }; + + $scope.chartRemoveValColumn = function (paragraph, index) { + paragraph.chartValCols.splice(index, 1); + + _chartApplySettings(paragraph, true); + }; + + $scope.chartAcceptKeyColumn = function(paragraph, item) { + var accepted = !_.includes(paragraph.chartKeyCols, item); + + if (accepted) { + paragraph.chartKeyCols = [item]; + + _chartApplySettings(paragraph, true); + } + + return false; + }; + + $scope.chartAcceptValColumn = function(paragraph, item) { + var accepted = !_.includes(paragraph.chartValCols, item) && item != TIME_LINE && _numberType(item.type); + + if (accepted) { + paragraph.chartValCols.push(item); + + _chartApplySettings(paragraph, true); + } + + return false; + }; + + var _hideColumn = function (col) { + return !(col.fieldName === '_KEY') && !(col.fieldName == '_VAL'); + }; + + var _allColumn = function () { + return true; + }; + + var paragraphId = 0; + + function enhanceParagraph(paragraph) { + paragraph.nonEmpty = function () { + return this.rows && this.rows.length > 0; + }; + + paragraph.chart = function () { + return this.result != 'table' && this.result != 'none'; + }; + + paragraph.queryExecute = function () { + return this.queryArgs && this.queryArgs.type == 'QUERY'; + }; + + paragraph.table = function () { + return this.result == 'table'; + }; + + paragraph.chartColumnsConfigured = function () { + return !$common.isEmptyArray(this.chartKeyCols) && !$common.isEmptyArray(this.chartValCols); + }; + + paragraph.chartTimeLineEnabled = function () { + return !$common.isEmptyArray(this.chartKeyCols) && angular.equals(this.chartKeyCols[0], TIME_LINE); + }; + + paragraph.timeLineSupported = function () { + return this.result != 'pie'; + }; + + Object.defineProperty(paragraph, 'gridOptions', { value: { + enableColResize: true, + columnDefs: [], + rowData: null + }}); + } + + $scope.aceInit = function (paragraph) { + return function (editor) { + editor.setAutoScrollEditorIntoView(true); + editor.$blockScrolling = Infinity; + + var renderer = editor.renderer; + + renderer.setHighlightGutterLine(false); + renderer.setShowPrintMargin(false); + renderer.setOption('fontFamily', 'monospace'); + renderer.setOption('fontSize', '14px'); + renderer.setOption('minLines', '5'); + renderer.setOption('maxLines', '15'); + + editor.setTheme('ace/theme/chrome'); + + Object.defineProperty(paragraph, 'ace', { value: editor }); + } + }; + + var _setActiveCache = function () { + if ($scope.caches.length > 0) + _.forEach($scope.notebook.paragraphs, function (paragraph) { + if (!paragraph.cacheName || !_.find($scope.caches, {name: paragraph.cacheName})) + paragraph.cacheName = $scope.caches[0].name; + }); + }; + + var loadNotebook = function () { + $http.post('/notebooks/get', {noteId: $scope.noteId}) + .success(function (notebook) { + $scope.notebook = notebook; + + $scope.notebook_name = notebook.name; + + _.forEach(notebook.paragraphs, function (paragraph) { + paragraph.id = paragraphId++; + + enhanceParagraph(paragraph); + }); + + if (!notebook.paragraphs || notebook.paragraphs.length == 0) + $scope.addParagraph(); + + _setActiveCache(); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }; + + loadNotebook(); + + var _saveNotebook = function (f) { + $http.post('/notebooks/save', $scope.notebook) + .success(f || function() {}) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }; + + $scope.renameNotebook = function (name) { + if (!name) + return; + + if ($scope.notebook.name != name) { + $scope.notebook.name = name; + + _saveNotebook(function () { + var idx = _.findIndex($scope.$root.notebooks, function (item) { + return item._id == $scope.notebook._id; + }); + + if (idx >= 0) { + $scope.$root.notebooks[idx].name = name; + + $scope.$root.rebuildDropdown(); + } + + $scope.notebook.edit = false; + }); + } + else + $scope.notebook.edit = false + }; + + $scope.removeNotebook = function () { + $confirm.confirm('Are you sure you want to remove: "' + $scope.notebook.name + '"?') + .then(function () { + $http.post('/notebooks/remove', {_id: $scope.notebook._id}) + .success(function () { + var idx = _.findIndex($scope.$root.notebooks, function (item) { + return item._id == $scope.notebook._id; + }); + + if (idx >= 0) { + $scope.$root.notebooks.splice(idx, 1); + + if ($scope.$root.notebooks.length > 0) + $window.location = '/sql/' + + $scope.$root.notebooks[Math.min(idx, $scope.$root.notebooks.length - 1)]._id; + else + $window.location = '/configuration/clusters'; + } + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }); + }; + + $scope.renameParagraph = function (paragraph, newName) { + if (!newName) + return; + + if (paragraph.name != newName) { + paragraph.name = newName; + + _saveNotebook(function () { paragraph.edit = false; }); + } + else + paragraph.edit = false + }; + + $scope.addParagraph = function () { + if (!$scope.notebook.paragraphs) + $scope.notebook.paragraphs = []; + + var sz = $scope.notebook.paragraphs.length; + + var paragraph = { + id: paragraphId++, + name: 'Query' + (sz ==0 ? '' : sz), + editor: true, + query: '', + pageSize: $scope.pageSizes[0], + result: 'none', + rate: { + value: 1, + unit: 60000, + installed: false + } + }; + + enhanceParagraph(paragraph); + + if ($scope.caches && $scope.caches.length > 0) + paragraph.cacheName = $scope.caches[0].name; + + $scope.notebook.expandedParagraphs.push($scope.notebook.paragraphs.length); + + $scope.notebook.paragraphs.push(paragraph); + }; + + $scope.setResult = function (paragraph, new_result) { + var changed = paragraph.result != new_result; + + paragraph.result = paragraph.result === new_result ? 'none' : new_result; + + if (changed) { + if (paragraph.chart()) + _chartApplySettings(paragraph, changed); + else + setTimeout(function () { + paragraph.gridOptions.api.sizeColumnsToFit(); + }); + } + }; + + $scope.resultEq = function(paragraph, result) { + return (paragraph.result === result); + }; + + $scope.removeParagraph = function(paragraph) { + $confirm.confirm('Are you sure you want to remove: "' + paragraph.name + '"?') + .then(function () { + var paragraph_idx = _.findIndex($scope.notebook.paragraphs, function (item) { + return paragraph == item; + }); + + var panel_idx = _.findIndex($scope.notebook.expandedParagraphs, function (item) { + return paragraph_idx == item; + }); + + if (panel_idx >= 0) + $scope.notebook.expandedParagraphs.splice(panel_idx, 1); + + $scope.notebook.paragraphs.splice(paragraph_idx, 1); + }); + }; + + function getTopology(onSuccess, onException) { + $http.post('/agent/topology') + .success(function (caches) { + onSuccess(); + + var oldCaches = $scope.caches; + + $scope.caches = _.sortBy(caches, 'name'); + + _.forEach(caches, function (cache) { + var old = _.find(oldCaches, { name: cache.name }); + + if (old && old.metadata) + cache.metadata = old.metadata; + }); + + _setActiveCache(); + }) + .error(function (err, status) { + onException(err, status); + }); + } + + $scope.checkNodeConnection(getTopology); + + var _columnFilter = function(paragraph) { + return paragraph.disabledSystemColumns || paragraph.systemColumns ? _allColumn : _hideColumn; + }; + + var _notObjectType = function(cls) { + return $common.isJavaBuildInClass(cls); + }; + + var _numberClasses = ['java.math.BigDecimal', 'java.lang.Byte', 'java.lang.Double', + 'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Short']; + + var _numberType = function(cls) { + return _.contains(_numberClasses, cls); + }; + + var _rebuildColumns = function (paragraph) { + var columnDefs = []; + + _.forEach(paragraph.meta, function (meta, idx) { + if (paragraph.columnFilter(meta)) { + if (_notObjectType(meta.fieldTypeName)) + paragraph.chartColumns.push({value: idx, type: meta.fieldTypeName, label: meta.fieldName}); + + // Index for explain, execute and fieldName for scan. + var colValue = 'data[' + (paragraph.queryArgs.query ? idx : '"' + meta.fieldName + '"') + ']'; + + columnDefs.push({ + headerName: meta.fieldName, + valueGetter: $common.isJavaBuildInClass(meta.fieldTypeName) ? colValue : 'JSON.stringify(' + colValue + ')' + }); + } + }); + + paragraph.gridOptions.api.setColumnDefs(columnDefs); + + if (paragraph.chartColumns.length > 0) + paragraph.chartColumns.push(TIME_LINE); + + // We could accept onl not object columns for X axis. + paragraph.chartKeyCols = _retainColumns(paragraph.chartColumns, paragraph.chartKeyCols, _notObjectType); + + // We could accept only numeric columns for Y axis. + paragraph.chartValCols = _retainColumns(paragraph.chartColumns, paragraph.chartValCols, _numberType, paragraph.chartKeyCols[0]); + }; + + $scope.toggleSystemColumns = function (paragraph) { + if (paragraph.disabledSystemColumns) + return; + + paragraph.systemColumns = !paragraph.systemColumns; + + paragraph.columnFilter = _columnFilter(paragraph); + + paragraph.chartColumns = []; + + _rebuildColumns(paragraph); + + setTimeout(function () { + paragraph.gridOptions.api.sizeColumnsToFit(); + }); + }; + + function _retainColumns(allCols, curCols, acceptableType, dfltCol) { + var retainedCols = []; + + var allColsLen = allCols.length; + + if (allColsLen > 0) { + curCols.forEach(function (curCol) { + var col = _.find(allCols, {label: curCol.label}); + + if (col && acceptableType(col.type)) + retainedCols.push(col); + }); + + if ($common.isEmptyArray(retainedCols)) + for (idx = 0; idx < allColsLen; idx++) { + var col = allCols[idx]; + + if (acceptableType(col.type) && col != dfltCol) { + retainedCols.push(col); + + break; + } + } + + if ($common.isEmptyArray(retainedCols) && dfltCol && acceptableType(dfltCol.type)) + retainedCols.push(dfltCol); + } + + return retainedCols; + } + + var _processQueryResult = function (paragraph, refreshMode) { + return function (res) { + if (res.meta && !refreshMode) { + paragraph.meta = []; + + paragraph.chartColumns = []; + + if (!$common.isDefined(paragraph.chartKeyCols)) + paragraph.chartKeyCols = []; + + if (!$common.isDefined(paragraph.chartValCols )) + paragraph.chartValCols = []; + + if (res.meta.length <= 2) { + var _key = _.find(res.meta, {fieldName: '_KEY'}); + var _val = _.find(res.meta, {fieldName: '_VAL'}); + + paragraph.disabledSystemColumns = (res.meta.length == 2 && _key && _val) || + (res.meta.length == 1 && (_key || _val)); + } + + paragraph.columnFilter = _columnFilter(paragraph); + + paragraph.meta = res.meta; + + _rebuildColumns(paragraph); + } + + paragraph.page = 1; + + paragraph.total = 0; + + paragraph.queryId = res.queryId; + + delete paragraph.errMsg; + + // Prepare explain results for display in table. + if (paragraph.queryArgs.type == "EXPLAIN" && res.rows) { + paragraph.rows = []; + + res.rows.forEach(function (row, i) { + var line = res.rows.length - 1 == i ? row[0] : row[0] + '\n'; + + line.replace(/\"/g, '').split('\n').forEach(function (line) { + paragraph.rows.push([line]); + }); + }); + } + else + paragraph.rows = res.rows; + + paragraph.gridOptions.api.setRowData(paragraph.rows); + + if (!refreshMode) + setTimeout(function () { paragraph.gridOptions.api.sizeColumnsToFit(); }); + + // Add results to history. + chartHistory.push({tm: new Date(), rows: paragraph.rows}); + + if (chartHistory.length > HISTORY_LENGTH) + chartHistory.shift(); + + _showLoading(paragraph, false); + + if (paragraph.result == 'none' || paragraph.queryArgs.type != "QUERY") + paragraph.result = 'table'; + else if (paragraph.chart()) + _chartApplySettings(paragraph); + } + }; + + var _executeRefresh = function (paragraph) { + $http.post('/agent/query', paragraph.queryArgs) + .success(_processQueryResult(paragraph, true)) + .error(function (errMsg) { + paragraph.errMsg = errMsg; + }); + }; + + var _showLoading = function (paragraph, enable) { + if (paragraph.table()) + paragraph.gridOptions.api.showLoading(enable); + + paragraph.loading = enable; + }; + + $scope.execute = function (paragraph) { + _saveNotebook(); + + paragraph.queryArgs = { type: "QUERY", query: paragraph.query, pageSize: paragraph.pageSize, cacheName: paragraph.cacheName }; + + _showLoading(paragraph, true); + + $http.post('/agent/query', paragraph.queryArgs) + .success(function (res) { + _processQueryResult(paragraph)(res); + + _tryStartRefresh(paragraph); + }) + .error(function (errMsg) { + paragraph.errMsg = errMsg; + + _showLoading(paragraph, false); + + $scope.stopRefresh(paragraph); + }); + + paragraph.ace.focus(); + }; + + $scope.explain = function (paragraph) { + _saveNotebook(); + + _cancelRefresh(paragraph); + + paragraph.queryArgs = { type: "EXPLAIN", query: 'EXPLAIN ' + paragraph.query, pageSize: paragraph.pageSize, cacheName: paragraph.cacheName }; + + _showLoading(paragraph, true); + + $http.post('/agent/query', paragraph.queryArgs) + .success(_processQueryResult(paragraph)) + .error(function (errMsg) { + paragraph.errMsg = errMsg; + + _showLoading(paragraph, false); + }); + + paragraph.ace.focus(); + }; + + $scope.scan = function (paragraph) { + _saveNotebook(); + + _cancelRefresh(paragraph); + + paragraph.queryArgs = { type: "SCAN", pageSize: paragraph.pageSize, cacheName: paragraph.cacheName }; + + _showLoading(paragraph, true); + + $http.post('/agent/scan', paragraph.queryArgs) + .success(_processQueryResult(paragraph)) + .error(function (errMsg) { + paragraph.errMsg = errMsg; + + _showLoading(paragraph, false); + }); + + paragraph.ace.focus(); + }; + + $scope.nextPage = function(paragraph) { + _showLoading(paragraph, true); + + $http.post('/agent/query/fetch', {queryId: paragraph.queryId, pageSize: paragraph.pageSize, cacheName: paragraph.queryArgs.cacheName}) + .success(function (res) { + paragraph.page++; + + paragraph.total += paragraph.rows.length; + + paragraph.rows = res.rows; + + paragraph.gridOptions.api.setRowData(res.rows); + + _showLoading(paragraph, false); + + if (res.last) + delete paragraph.queryId; + }) + .error(function (errMsg) { + paragraph.errMsg = errMsg; + + _showLoading(paragraph, false); + }); + }; + + var _export = function(fileName, meta, rows) { + var csvContent = ''; + + if (meta) { + csvContent += meta.map(function (col) { + var res = []; + + if (col.schemaName) + res.push(col.schemaName); + if (col.typeName) + res.push(col.typeName); + + res.push(col.fieldName); + + return res.join('.'); + }).join(',') + '\n'; + } + + rows.forEach(function (row) { + if (Array.isArray(row)) { + csvContent += row.map(function (elem) { + return elem ? JSON.stringify(elem) : ''; + }).join(','); + } + else { + var first = true; + + meta.forEach(function (prop) { + if (first) + first = false; + else + csvContent += ','; + + var elem = row[prop.fieldName]; + + csvContent += elem ? JSON.stringify(elem) : ''; + }); + } + + csvContent += '\n'; + }); + + $common.download('application/octet-stream;charset=utf-8', fileName, escape(csvContent)); + }; + + $scope.exportPage = function(paragraph) { + _export(paragraph.name + '.csv', paragraph.meta, paragraph.rows); + }; + + $scope.exportAll = function(paragraph) { + $http.post('/agent/query/getAll', {query: paragraph.query, cacheName: paragraph.cacheName}) + .success(function (item) { + _export(paragraph.name + '-all.csv', item.meta, item.rows); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + }; + + $scope.rateAsString = function (paragraph) { + if (paragraph.rate && paragraph.rate.installed) { + var idx = _.findIndex($scope.timeUnit, function (unit) { + return unit.value == paragraph.rate.unit; + }); + + if (idx >= 0) + return ' ' + paragraph.rate.value + $scope.timeUnit[idx].short; + + paragraph.rate.installed = false; + } + + return ''; + }; + + var _cancelRefresh = function (paragraph) { + if (paragraph.rate && paragraph.rate.stopTime) { + delete paragraph.queryArgs; + + paragraph.rate.installed = false; + + $interval.cancel(paragraph.rate.stopTime); + + delete paragraph.rate.stopTime; + } + }; + + var _tryStopRefresh = function (paragraph) { + if (paragraph.rate && paragraph.rate.stopTime) { + $interval.cancel(paragraph.rate.stopTime); + + delete paragraph.rate.stopTime; + } + }; + + var _tryStartRefresh = function (paragraph) { + _tryStopRefresh(paragraph); + + if (paragraph.rate && paragraph.rate.installed && paragraph.queryArgs) { + $scope.chartAcceptKeyColumn(paragraph, TIME_LINE); + + _executeRefresh(paragraph); + + var delay = paragraph.rate.value * paragraph.rate.unit; + + paragraph.rate.stopTime = $interval(_executeRefresh, delay, 0, false, paragraph); + } + }; + + $scope.startRefresh = function (paragraph, value, unit) { + paragraph.rate.value = value; + paragraph.rate.unit = unit; + paragraph.rate.installed = true; + + _tryStartRefresh(paragraph); + }; + + $scope.stopRefresh = function (paragraph) { + paragraph.rate.installed = false; + + _tryStopRefresh(paragraph); + }; + + function _chartNumber(arr, idx, dflt) { + if (arr && arr.length > idx && _.isNumber(arr[idx])) { + return arr[idx]; + } + + return dflt; + } + + function _chartLabel(arr, idx, dflt) { + if (arr && arr.length > idx && _.isString(arr[idx])) + return arr[idx]; + + return dflt; + } + + function _chartDatum(paragraph) { + var datum = []; + + if (paragraph.chartColumnsConfigured()) { + paragraph.chartValCols.forEach(function (valCol) { + var index = 0; + var values = []; + + if (paragraph.chartTimeLineEnabled()) { + if (paragraph.charts && paragraph.charts.length == 1) + datum = paragraph.charts[0].data; + + var chartData = _.find(datum, {key: valCol.label}); + + if (chartData) { + var history = _.last(chartHistory); + + chartData.values.push({ + x: history.tm, + y: _chartNumber(history.rows[0], valCol.value, index++) + }); + + if (chartData.length > HISTORY_LENGTH) + chartData.shift(); + } + else { + values = _.map(chartHistory, function (history) { + return { + x: history.tm, + y: _chartNumber(history.rows[0], valCol.value, index++) + } + }); + + datum.push({key: valCol.label, values: values}); + } + } + else { + values = _.map(paragraph.rows, function (row) { + return { + x: _chartNumber(row, paragraph.chartKeyCols[0].value, index), + xLbl: _chartLabel(row, paragraph.chartKeyCols[0].value, undefined), + y: _chartNumber(row, valCol.value, index++) + } + }); + + datum.push({key: valCol.label, values: values}); + } + }); + } + + return datum; + } + + function _pieChartDatum(paragraph) { + var datum = []; + + if (paragraph.chartColumnsConfigured() && !paragraph.chartTimeLineEnabled()) { + paragraph.chartValCols.forEach(function (valCol) { + var index = 0; + + var values = _.map(paragraph.rows, function (row) { + return { + x: row[paragraph.chartKeyCols[0].value], + y: _chartNumber(row, valCol.value, index++) + } + }); + + datum.push({key: valCol.label, values: values}); + }); + } + + return datum; + } + + function _chartApplySettings(paragraph, resetCharts) { + if (resetCharts) + paragraph.charts = []; + + if (paragraph.chart() && paragraph.nonEmpty()) { + switch (paragraph.result) { + case 'bar': + _barChart(paragraph); + break; + + case 'pie': + _pieChart(paragraph); + break; + + case 'line': + _lineChart(paragraph); + break; + + case 'area': + _areaChart(paragraph); + break; + } + } + } + + function _colLabel(col) { + return col.label; + } + + function _chartAxisLabel(cols, dflt) { + return $common.isEmptyArray(cols) ? dflt : _.map(cols, _colLabel).join(', '); + } + + function _xX(d) { + return d.x; + } + + function _yY(d) { + return d.y; + } + + function _xAxisTimeFormat(d) { + return d3.time.format('%X')(new Date(d)); + } + + var _xAxisWithLabelFormat = function(values) { + return function (d) { + var dx = values[d]; + + if (!dx) + return d3.format(',.2f')(d); + + var lbl = values[d]['xLbl']; + + return lbl ? lbl : d3.format(',.2f')(d); + } + }; + + function _barChart(paragraph) { + var datum = _chartDatum(paragraph); + + if ($common.isEmptyArray(paragraph.charts)) { + var options = { + chart: { + type: 'multiBarChart', + height: 400, + margin: {left: 70}, + duration: 0, + x: _xX, + y: _yY, + xAxis: { + axisLabel: _chartAxisLabel(paragraph.chartKeyCols, 'X'), + tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(datum[0].values), + showMaxMin: false + }, + yAxis: { + axisLabel: _chartAxisLabel(paragraph.chartValCols, 'Y'), + tickFormat: d3.format(',.2f') + }, + showControls: true + } + }; + + paragraph.charts = [{options: options, data: datum}]; + + $timeout(function () { + paragraph.charts[0].api.update(); + }, 100); + } + else + $timeout(function () { + if (paragraph.chartTimeLineEnabled()) + paragraph.charts[0].api.update(); + else + paragraph.charts[0].api.updateWithData(datum); + }); + } + + function _pieChart(paragraph) { + var datum = _pieChartDatum(paragraph); + + if (datum.length == 0) + datum = [{values: []}]; + + paragraph.charts = _.map(datum, function (data) { + return { + options: { + chart: { + type: 'pieChart', + height: 400, + duration: 0, + x: _xX, + y: _yY, + showLabels: true, + labelThreshold: 0.05, + labelType: 'percent', + donut: true, + donutRatio: 0.35 + }, + title: { + enable: true, + text: data.key + } + }, + data: data.values + } + }); + + $timeout(function () { + paragraph.charts[0].api.update(); + }, 100); + } + + function _lineChart(paragraph) { + var datum = _chartDatum(paragraph); + + if ($common.isEmptyArray(paragraph.charts)) { + var options = { + chart: { + type: 'lineChart', + height: 400, + margin: { left: 70 }, + duration: 0, + x: _xX, + y: _yY, + xAxis: { + axisLabel: _chartAxisLabel(paragraph.chartKeyCols, 'X'), + tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(datum[0].values), + showMaxMin: false + }, + yAxis: { + axisLabel: _chartAxisLabel(paragraph.chartValCols, 'Y'), + tickFormat: d3.format(',.2f') + }, + useInteractiveGuideline: true + } + }; + + paragraph.charts = [{options: options, data: datum}]; + + $timeout(function () { + paragraph.charts[0].api.update(); + }, 100); + } + else + $timeout(function () { + if (paragraph.chartTimeLineEnabled()) + paragraph.charts[0].api.update(); + else + paragraph.charts[0].api.updateWithData(datum); + }); + } + + function _areaChart(paragraph) { + var datum = _chartDatum(paragraph); + + if ($common.isEmptyArray(paragraph.charts)) { + var options = { + chart: { + type: 'stackedAreaChart', + height: 400, + margin: {left: 70}, + duration: 0, + x: _xX, + y: _yY, + xAxis: { + axisLabel: _chartAxisLabel(paragraph.chartKeyCols, 'X'), + tickFormat: paragraph.chartTimeLineEnabled() ? _xAxisTimeFormat : _xAxisWithLabelFormat(datum[0].values), + showMaxMin: false + }, + yAxis: { + axisLabel: _chartAxisLabel(paragraph.chartValCols, 'Y'), + tickFormat: d3.format(',.2f') + } + } + }; + + paragraph.charts = [{options: options, data: datum}]; + + $timeout(function () { + paragraph.charts[0].api.update(); + }, 100); + } + else + $timeout(function () { + if (paragraph.chartTimeLineEnabled()) + paragraph.charts[0].api.update(); + else + paragraph.charts[0].api.updateWithData(datum); + }); + } + + $scope.actionAvailable = function (paragraph, needQuery) { + return $scope.caches.length > 0 && paragraph.cacheName && (!needQuery || paragraph.query) && !paragraph.loading; + }; + + $scope.actionTooltip = function (paragraph, action, needQuery) { + if ($scope.actionAvailable(paragraph, needQuery)) + return; + + if (paragraph.loading) + return 'Wating for server response'; + + return 'To ' + action + ' query select cache' + (needQuery ? ' and input query' : ''); + }; + + $scope.clickableMetadata = function (node) { + return node.type.slice(0, 5) != 'index'; + }; + + $scope.dblclickMetadata = function (paragraph, node) { + paragraph.ace.insert(node.name); + var position = paragraph.ace.selection.getCursor(); + + paragraph.query = paragraph.ace.getValue(); + + setTimeout(function () { + paragraph.ace.selection.moveCursorToPosition(position); + + paragraph.ace.focus(); + }, 1); + }; + + $scope.tryLoadMetadata = function (cache) { + if (!cache.metadata) { + $loading.start('loadingCacheMetadata'); + + $http.post('/agent/cache/metadata', {cacheName: cache.name}) + .success(function (metadata) { + cache.metadata = _.sortBy(metadata, 'name'); + }) + .error(function (errMsg) { + $common.showError(errMsg); + }) + .finally(function() { + $loading.finish('loadingCacheMetadata'); + }); + } + } +}]); http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/controllers/summary-controller.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/controllers/summary-controller.js b/modules/control-center-web/src/main/js/controllers/summary-controller.js new file mode 100644 index 0000000..a3d2dd1 --- /dev/null +++ b/modules/control-center-web/src/main/js/controllers/summary-controller.js @@ -0,0 +1,233 @@ +/* + * + * * 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 Summary screen. +consoleModule.controller('summaryController', [ + '$scope', '$http', '$common', '$loading', '$message', + function ($scope, $http, $common, $loading, $message) { + $scope.joinTip = $common.joinTip; + $scope.getModel = $common.getModel; + + $scope.showMoreInfo = $message.message; + + $scope.javaClassItems = [ + {label: 'snippet', value: 1}, + {label: 'factory class', value: 2} + ]; + + $scope.evictionPolicies = [ + {value: 'LRU', label: 'LRU'}, + {value: 'RND', label: 'Random'}, + {value: 'FIFO', label: 'FIFO'}, + {value: 'SORTED', label: 'Sorted'}, + {value: undefined, label: 'Not set'} + ]; + + $scope.tabsServer = { activeTab: 0 }; + $scope.tabsClient = { activeTab: 0 }; + + $scope.pojoClasses = function() { + var classes = []; + + _.forEach($generatorJava.metadatas, function(meta) { + classes.push(meta.keyType); + classes.push(meta.valueType); + }); + + return classes; + }; + + $scope.oss = ['debian:8', 'ubuntu:14.10']; + + $scope.configServer = {javaClassServer: 1, os: undefined}; + $scope.configClient = {}; + + $scope.backupItem = {javaClassClient: 1}; + + $http.get('/models/summary.json') + .success(function (data) { + $scope.screenTip = data.screenTip; + $scope.moreInfo = data.moreInfo; + $scope.clientFields = data.clientFields; + }) + .error(function (errMsg) { + $common.showError(errMsg); + }); + + $scope.clusters = []; + + $scope.aceInit = function (editor) { + editor.setReadOnly(true); + editor.setOption('highlightActiveLine', false); + editor.setAutoScrollEditorIntoView(true); + editor.$blockScrolling = Infinity; + + var renderer = editor.renderer; + + renderer.setHighlightGutterLine(false); + renderer.setShowPrintMargin(false); + renderer.setOption('fontSize', '14px'); + renderer.setOption('minLines', '3'); + renderer.setOption('maxLines', '50'); + + editor.setTheme('ace/theme/chrome'); + }; + + $scope.generateJavaServer = function () { + $scope.javaServer = $generatorJava.cluster($scope.selectedItem, $scope.configServer.javaClassServer === 2); + }; + + function selectPojoClass(config) { + _.forEach($generatorJava.metadatas, function(meta) { + if (meta.keyType == config.pojoClass) + return config.pojoClassBody = meta.keyClass; + + if (meta.valueType == config.pojoClass) + return config.pojoClassBody = meta.valueClass; + }); + } + + function pojoClsListener(config) { + return function () { + selectPojoClass(config); + }; + } + + $scope.updatePojos = function() { + if ($common.isDefined($scope.selectedItem)) { + var curServCls = $scope.configServer.pojoClass; + var curCliCls = $scope.configClient.pojoClass; + + $generatorJava.pojos($scope.selectedItem.caches, $scope.configServer.useConstructor, $scope.configServer.includeKeyFields); + + function restoreSelected(selected, config, tabs) { + if (!$common.isDefined(selected) || _.findIndex($generatorJava.metadatas, function (meta) { + return meta.keyType == selected || meta.valueType == selected; + }) < 0) { + if ($generatorJava.metadatas.length > 0) { + if ($common.isDefined($generatorJava.metadatas[0].keyType)) + config.pojoClass = $generatorJava.metadatas[0].keyType; + else + config.pojoClass = $generatorJava.metadatas[0].valueType; + } + else { + config.pojoClass = undefined; + + if (tabs.activeTab == 2) + tabs.activeTab = 0; + } + } + else + config.pojoClass = selected; + + selectPojoClass(config); + } + + restoreSelected(curServCls, $scope.configServer, $scope.tabsServer); + restoreSelected(curCliCls, $scope.configClient, $scope.tabsClient); + } + }; + + $scope.$watch('configServer.javaClassServer', $scope.generateJavaServer, true); + + $scope.$watch('configServer.pojoClass', pojoClsListener($scope.configServer), true); + $scope.$watch('configClient.pojoClass', pojoClsListener($scope.configClient), true); + + $scope.$watch('configServer.useConstructor', $scope.updatePojos, true); + + $scope.$watch('configServer.includeKeyFields', $scope.updatePojos, true); + + $scope.generateDockerServer = function() { + var os = $scope.configServer.os ? $scope.configServer.os : $scope.oss[0]; + + $scope.dockerServer = $generatorDocker.clusterDocker($scope.selectedItem, os); + }; + + $scope.$watch('configServer.os', $scope.generateDockerServer, true); + + $scope.generateClient = function () { + $scope.xmlClient = $generatorXml.cluster($scope.selectedItem, $scope.backupItem.nearConfiguration); + $scope.javaClient = $generatorJava.cluster($scope.selectedItem, $scope.backupItem.javaClassClient === 2, + $scope.backupItem.nearConfiguration, $scope.configServer.useConstructor); + }; + + $scope.$watch('backupItem', $scope.generateClient, true); + + $scope.selectItem = function (cluster) { + if (!cluster) + return; + + $scope.selectedItem = cluster; + + $scope.xmlServer = $generatorXml.cluster(cluster); + + $scope.generateJavaServer(); + + $scope.generateDockerServer(); + + $scope.generateClient(); + + $scope.updatePojos(); + }; + + $scope.pojoAvailable = function() { + return $common.isDefined($generatorJava.metadatas) && $generatorJava.metadatas.length > 0; + }; + + $loading.start('loadingSummaryScreen'); + + $http.post('clusters/list') + .success(function (data) { + $scope.clusters = data.clusters; + + if ($scope.clusters.length > 0) { + // Populate clusters with caches. + _.forEach($scope.clusters, function (cluster) { + cluster.caches = _.filter(data.caches, function (cache) { + return _.contains(cluster.caches, cache._id); + }); + }); + + var restoredId = sessionStorage.summarySelectedId; + + var selectIdx = 0; + + if (restoredId) { + var idx = _.findIndex($scope.clusters, function (cluster) { + return cluster._id == restoredId; + }); + + if (idx >= 0) + selectIdx = idx; + else + delete sessionStorage.summarySelectedId; + } + + $scope.selectItem($scope.clusters[selectIdx]); + + $scope.$watch('selectedItem', function (val) { + if (val) + sessionStorage.summarySelectedId = val._id; + }, true); + } + }) + .finally(function () { + $loading.finish('loadingSummaryScreen'); + }); +}]); http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/db.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/db.js b/modules/control-center-web/src/main/js/db.js new file mode 100644 index 0000000..c9c6b39 --- /dev/null +++ b/modules/control-center-web/src/main/js/db.js @@ -0,0 +1,431 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var config = require('./helpers/configuration-loader.js'); + +// Mongoose for mongodb. +var mongoose = require('mongoose'), + Schema = mongoose.Schema, + ObjectId = mongoose.Schema.Types.ObjectId, + passportLocalMongoose = require('passport-local-mongoose'); + +var deepPopulate = require('mongoose-deep-populate')( mongoose); + +// Connect to mongoDB database. +mongoose.connect(config.get('mongoDB:url'), {server: {poolSize: 4}}); + +// Define Account schema. +var AccountSchema = new Schema({ + username: String, + email: String, + lastLogin: Date, + admin: Boolean, + token: String, + resetPasswordToken: String +}); + +// Install passport plugin. +AccountSchema.plugin(passportLocalMongoose, {usernameField: 'email', limitAttempts: true, lastLoginField: 'lastLogin', + usernameLowerCase: true}); + +// Configure transformation to JSON. +AccountSchema.set('toJSON', { + transform: function(doc, ret) { + return { + _id: ret._id, + email: ret.email, + username: ret.username, + admin: ret.admin, + token: ret.token, + lastLogin: ret.lastLogin + }; + } +}); + +// Define Account model. +exports.Account = mongoose.model('Account', AccountSchema); + +// Define Space model. +exports.Space = mongoose.model('Space', new Schema({ + name: String, + owner: {type: ObjectId, ref: 'Account'}, + usedBy: [{ + permission: {type: String, enum: ['VIEW', 'FULL']}, + account: {type: ObjectId, ref: 'Account'} + }] +})); + +// Define Cache type metadata schema. +var CacheTypeMetadataSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + caches: [{type: ObjectId, ref: 'Cache'}], + kind: {type: String, enum: ['query', 'store', 'both']}, + databaseSchema: String, + databaseTable: String, + keyType: String, + valueType: String, + keyFields: [{databaseName: String, databaseType: String, javaName: String, javaType: String}], + valueFields: [{databaseName: String, databaseType: String, javaName: String, javaType: String}], + queryFields: [{name: String, className: String}], + ascendingFields: [{name: String, className: String}], + descendingFields: [{name: String, className: String}], + textFields: [String], + groups: [{name: String, fields: [{name: String, className: String, direction: Boolean}]}] +}); + +// Define Cache type metadata model. +exports.CacheTypeMetadata = mongoose.model('CacheTypeMetadata', CacheTypeMetadataSchema); + +// Define Cache schema. +var CacheSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + name: String, + clusters: [{type: ObjectId, ref: 'Cluster'}], + metadatas: [{type: ObjectId, ref: 'CacheTypeMetadata'}], + 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', 'RND', 'FIFO', 'Sorted']}, + LRU: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + RND: { + maxSize: Number + }, + FIFO: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + SORTED: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + } + }, + + rebalanceMode: {type: String, enum: ['SYNC', 'ASYNC', 'NONE']}, + rebalanceThreadPoolSize: Number, + rebalanceBatchSize: Number, + rebalanceOrder: Number, + rebalanceDelay: Number, + rebalanceTimeout: Number, + rebalanceThrottle: Number, + + cacheStoreFactory: { + kind: { + type: String, + enum: ['CacheJdbcPojoStoreFactory', 'CacheJdbcBlobStoreFactory', 'CacheHibernateBlobStoreFactory'] + }, + CacheJdbcPojoStoreFactory: { + dataSourceBean: String, + dialect: { + type: String, + enum: ['Oracle', 'DB2', 'SQLServer', 'MySQL', 'PosgreSQL', 'H2'] + } + }, + CacheJdbcBlobStoreFactory: { + user: String, + dataSourceBean: String, + initSchema: Boolean, + createTableQuery: String, + loadQuery: String, + insertQuery: String, + updateQuery: String, + deleteQuery: String + }, + CacheHibernateBlobStoreFactory: { + hibernateProperties: [String] + } + }, + loadPreviousValue: Boolean, + readThrough: Boolean, + writeThrough: Boolean, + + writeBehindEnabled: Boolean, + writeBehindBatchSize: Number, + writeBehindFlushSize: Number, + writeBehindFlushFrequency: Number, + writeBehindFlushThreadCount: Number, + + invalidate: Boolean, + defaultLockTimeout: Number, + + sqlEscapeAll: Boolean, + sqlOnheapRowCacheSize: Number, + longQueryWarningTimeout: Number, + indexedTypes: [{keyClass: String, valueClass: String}], + sqlFunctionClasses: [String], + statisticsEnabled: Boolean, + managementEnabled: Boolean, + readFromBackup: Boolean, + copyOnRead: Boolean, + maxConcurrentAsyncOperations: Number, + nearCacheEnabled: Boolean, + nearConfiguration: { + nearStartSize: Number, + nearEvictionPolicy: { + kind: {type: String, enum: ['LRU', 'RND', 'FIFO', 'Sorted']}, + LRU: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + RND: { + maxSize: Number + }, + FIFO: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + }, + SORTED: { + batchSize: Number, + maxMemorySize: Number, + maxSize: Number + } + } + } +}); + +// Install deep populate plugin. +CacheSchema.plugin(deepPopulate, { + whitelist: ['metadatas'] +}); + +// Define Cache model. +exports.Cache = mongoose.model('Cache', CacheSchema); + +// Define Cluster schema. +var ClusterSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + name: String, + discovery: { + localAddress: String, + localPort: Number, + localPortRange: Number, + addressResolver: String, + socketTimeout: Number, + ackTimeout: Number, + maxAckTimeout: Number, + networkTimeout: Number, + joinTimeout: Number, + threadPriority: Number, + heartbeatFrequency: Number, + maxMissedHeartbeats: Number, + maxMissedClientHeartbeats: Number, + topHistorySize: Number, + listener: String, + dataExchange: String, + metricsProvider: String, + reconnectCount: Number, + statisticsPrintFrequency: Number, + ipFinderCleanFrequency: Number, + authenticator: String, + forceServerMode: Boolean, + clientReconnectDisabled: Boolean, + kind: {type: String, enum: ['Vm', 'Multicast', 'S3', 'Cloud', 'GoogleStorage', 'Jdbc', 'SharedFs']}, + Vm: { + addresses: [String] + }, + Multicast: { + multicastGroup: String, + multicastPort: Number, + responseWaitTime: Number, + addressRequestAttempts: Number, + localAddress: String, + addresses: [String] + }, + S3: { + bucketName: String + }, + Cloud: { + credential: String, + credentialPath: String, + identity: String, + provider: String, + regions: [String], + zones: [String] + }, + GoogleStorage: { + projectName: String, + bucketName: String, + serviceAccountP12FilePath: String, + serviceAccountId: String, + addrReqAttempts: String + }, + Jdbc: { + initSchema: Boolean + }, + SharedFs: { + path: String + } + }, + atomicConfiguration: { + backups: Number, + cacheMode: {type: String, enum: ['LOCAL', 'REPLICATED', 'PARTITIONED']}, + atomicSequenceReserveSize: Number + }, + caches: [{type: ObjectId, ref: 'Cache'}], + clockSyncSamples: Number, + clockSyncFrequency: Number, + deploymentMode: {type: String, enum: ['PRIVATE', 'ISOLATED', 'SHARED', 'CONTINUOUS']}, + discoveryStartupDelay: Number, + igfsThreadPoolSize: Number, + includeEventTypes: [{ + type: String, enum: ['EVTS_CHECKPOINT', 'EVTS_DEPLOYMENT', 'EVTS_ERROR', 'EVTS_DISCOVERY', + 'EVTS_JOB_EXECUTION', 'EVTS_TASK_EXECUTION', 'EVTS_CACHE', 'EVTS_CACHE_REBALANCE', 'EVTS_CACHE_LIFECYCLE', + 'EVTS_CACHE_QUERY', 'EVTS_SWAPSPACE', 'EVTS_IGFS'] + }], + 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, + 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']}, + transactionIsolation: {type: String, enum: ['READ_COMMITTED', 'REPEATABLE_READ', 'SERIALIZABLE']}, + defaultTxTimeout: Number, + pessimisticTxLogLinger: Number, + pessimisticTxLogSize: Number, + txSerializableEnabled: Boolean, + txManagerLookupClassName: String + }, + sslEnabled: Boolean, + sslContextFactory: { + keyAlgorithm: String, + keyStoreFilePath: String, + keyStoreType: String, + protocol: String, + trustStoreFilePath: String, + trustStoreType: String, + trustManagers: [String] + } +}); + +// Install deep populate plugin. +ClusterSchema.plugin(deepPopulate, { + whitelist: [ + 'caches', + 'caches.metadatas' + ] +}); + +// Define Cluster model. +exports.Cluster = mongoose.model('Cluster', ClusterSchema); + +// Define Notebook schema. +var NotebookSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + name: String, + expandedParagraphs: [Number], + paragraphs: [{ + name: String, + query: String, + editor: Boolean, + result: {type: String, enum: ['none', 'table', 'bar', 'pie', 'line', 'area']}, + pageSize: Number, + hideSystemColumns: Boolean, + cacheName: String, + rate: { + value: Number, + unit: Number + } + }] +}); + +// Define Notebook model. +exports.Notebook = mongoose.model('Notebook', NotebookSchema); + +// Define Database preset schema. +var DatabasePresetSchema = new Schema({ + space: {type: ObjectId, ref: 'Space'}, + jdbcDriverJar: String, + jdbcDriverClass: String, + jdbcUrl: String, + user: String, + packageName: String +}); + +// Define Database preset model. +exports.DatabasePreset = mongoose.model('DatabasePreset', DatabasePresetSchema); + +exports.upsert = function (model, data, cb) { + if (data._id) { + var id = data._id; + + delete data._id; + + model.findOneAndUpdate({_id: id}, data, cb); + } + else + new model(data).save(cb); +}; + +exports.processed = function(err, res) { + if (err) { + res.status(500).send(err); + + return false; + } + + return true; +}; + +exports.mongoose = mongoose; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/helpers/common-utils.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/helpers/common-utils.js b/modules/control-center-web/src/main/js/helpers/common-utils.js new file mode 100644 index 0000000..598cdf8 --- /dev/null +++ b/modules/control-center-web/src/main/js/helpers/common-utils.js @@ -0,0 +1,104 @@ +/* + * + * * 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. + * + */ + +// Entry point for common utils. +$commonUtils = {}; + +/** + * @param v Value to check. + * @returns {boolean} 'true' if value defined. + */ +$commonUtils.isDefined = function (v) { + return !(v === undefined || v === null); +}; + +/** + * @param v Value to check. + * @returns {boolean} 'true' if value defined and not empty string. + */ +$commonUtils.isDefinedAndNotEmpty = function (v) { + var defined = $commonUtils.isDefined(v); + + if (defined && (typeof(v) == 'string' || + Object.prototype.toString.call(v) === '[object Array]')) + defined = v.length > 0; + + return defined; +}; + +/** + * @param obj Object to check. + * @param props Properties names. + * @returns {boolean} 'true' if object contains at least one from specified properties. + */ +$commonUtils.hasProperty = function (obj, props) { + for (var propName in props) { + if (props.hasOwnProperty(propName)) { + if (obj[propName]) + return true; + } + } + + return false; +}; + +/** + * @param obj Object to check. + * @param props Array of properties names. + * @returns {boolean} 'true' if + */ +$commonUtils.hasAtLeastOneProperty = function (obj, props) { + if (obj && props) { + return _.findIndex(props, function (prop) { + return $commonUtils.isDefined(obj[prop]); + }) >= 0; + } + + return false; +}; + +/** + * Convert some name to valid java name. + * + * @param prefix To append to java name. + * @param name to convert. + * @returns {string} Valid java name. + */ +$commonUtils.toJavaName = function (prefix, name) { + var javaName = name ? name.replace(/[^A-Za-z_0-9]+/, '_') : 'dflt'; + + return prefix + javaName.charAt(0).toLocaleUpperCase() + javaName.slice(1); +}; + +$commonUtils.randomString = function (len) { + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var possibleLen = possible.length; + + var res = ''; + + for (var i = 0; i < len; i++) + res += possible.charAt(Math.floor(Math.random() * possibleLen)); + + return res; +}; + +// For server side we should export Java code generation entry point. +if (typeof window === 'undefined') { + module.exports = $commonUtils; +} http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/helpers/configuration-loader.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/helpers/configuration-loader.js b/modules/control-center-web/src/main/js/helpers/configuration-loader.js new file mode 100644 index 0000000..45616aa --- /dev/null +++ b/modules/control-center-web/src/main/js/helpers/configuration-loader.js @@ -0,0 +1,43 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var config = require('nconf'); + +config.file({'file': 'config/default.json'}); + +/** + * Normalize a port into a number, string, or false. + */ +config.normalizePort = function (val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +}; + +module.exports = config;
