http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js index a0bd44c..eb52e51 100644 --- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js +++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/ipc.directive.js @@ -15,13 +15,13 @@ * limitations under the License. */ -import template from './ipc.jade!'; +import templateUrl from './ipc.jade'; export default ['igniteConfigurationIgfsIpc', [() => { return { scope: true, restrict: 'E', - template, + templateUrl, replace: true }; }]];
http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js index 6a6ca12..810944f 100644 --- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js +++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.directive.js @@ -15,13 +15,13 @@ * limitations under the License. */ -import template from './misc.jade!'; +import templateUrl from './misc.jade'; export default ['igniteConfigurationIgfsMisc', [() => { return { scope: true, restrict: 'E', - template, + templateUrl, replace: true }; }]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade index fd42805..dc48d07 100644 --- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade +++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/misc.jade @@ -32,7 +32,7 @@ mixin table-igfs-path-mode-edit(prefix, focusId, index) .col-xs-8.col-sm-8.col-md-8 .fieldSep / .input-tip - input.form-control(id=keyFocusId enter-focus-next=valFocusId type='text' ng-model=keyModel placeholder='Path' on-escape='tableReset()') + input.form-control(id=keyFocusId ignite-on-enter-focus-move=valFocusId type='text' ng-model=keyModel placeholder='Path' ignite-on-escape='tableReset()') .col-xs-4.col-sm-4.col-md-4 -var arg = keyModel + ', ' + valModel -var btnVisible = 'tablePairSaveVisible(tblPathModes, ' + index + ')' @@ -40,7 +40,7 @@ mixin table-igfs-path-mode-edit(prefix, focusId, index) -var btnVisibleAndSave = btnVisible + ' && ' + btnSave +btn-save(btnVisible, btnSave) .input-tip - button.select-toggle.form-control(id=valFocusId bs-select ng-model=valModel data-placeholder='Mode' bs-options='item.value as item.label for item in igfsModes' tabindex='0' on-enter=btnVisibleAndSave on-escape='tableReset()') + button.select-toggle.form-control(id=valFocusId bs-select ng-model=valModel data-placeholder='Mode' bs-options='item.value as item.label for item in igfsModes' tabindex='0' ignite-on-enter=btnVisibleAndSave ignite-on-escape='tableReset()') form.panel.panel-default(name=form novalidate) .panel-heading(bs-collapse-toggle='' ng-click='ui.loadPanel("#{form}")') @@ -75,10 +75,10 @@ form.panel.panel-default(name=form novalidate) .settings-row +checkbox('Colocate metadata', model + '.colocateMetadata', 'colocateMetadata', 'Whether to co-locate metadata on a single node') .settings-row - +checkbox('Relaxed consistency ', model + '.relaxedConsistency', 'relaxedConsistency', - 'If value of this flag is <b>true</b>, IGFS will skip expensive consistency checks. It is recommended to set\ - this flag to <b>false</b>b> if your application has conflicting operations, or you do not how exactly users will\ - use your system.') + +checkbox('Relaxed consistency', model + '.relaxedConsistency', 'relaxedConsistency', + 'If value of this flag is <b>true</b>, IGFS will skip expensive consistency checks<br/>\ + It is recommended to set this flag to <b>false</b> if your application has conflicting\ + operations, or you do not know how exactly users will use your system') .settings-row ignite-form-group(ng-model=pathModes ng-form=pathModesForm) ignite-form-field-label http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js index 8b3b37b..69179c0 100644 --- a/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js +++ b/modules/web-console/src/main/js/app/modules/states/configuration/igfs/secondary.directive.js @@ -15,13 +15,13 @@ * limitations under the License. */ -import template from './secondary.jade!'; +import templateUrl from './secondary.jade'; export default ['igniteConfigurationIgfsSecondary', [() => { return { scope: true, restrict: 'E', - template, + templateUrl, replace: true }; }]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js b/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js index fb67326..be7bf1e 100644 --- a/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js +++ b/modules/web-console/src/main/js/app/modules/states/configuration/preview-panel.directive.js @@ -15,13 +15,13 @@ * limitations under the License. */ -import ace from 'ace'; +import ace from 'brace'; export default ['previewPanel', ['$interval', '$timeout', ($interval, $timeout) => { let animation = {editor: null, stage: 0, start: 0, stop: 0}; let prevContent = []; - const Range = ace.require('ace/range').Range; + const Range = ace.acequire('ace/range').Range; const _clearSelection = (editor) => { _.forEach(editor.session.getMarkers(false), (marker) => { http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js b/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js index 866d97f..1b19e3a 100644 --- a/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js +++ b/modules/web-console/src/main/js/app/modules/states/configuration/summary/summary.controller.js @@ -17,15 +17,16 @@ import _ from 'lodash'; import JSZip from 'jszip'; +import saver from 'file-saver'; export default [ - '$rootScope', '$scope', '$http', '$common', '$loading', '$filter', 'ConfigurationSummaryResource', 'JavaTypes', 'IgniteVersion', 'GeneratorDocker', 'GeneratorPom', - function($root, $scope, $http, $common, $loading, $filter, Resource, JavaTypes, IgniteVersion, docker, pom) { + '$rootScope', '$scope', '$http', 'IgniteLegacyUtils', 'IgniteLoading', '$filter', 'ConfigurationSummaryResource', 'JavaTypes', 'IgniteVersion', 'GeneratorDocker', 'GeneratorPom', + function($root, $scope, $http, LegacyUtils, Loading, $filter, Resource, JavaTypes, IgniteVersion, docker, pom) { const ctrl = this; $scope.ui = { ready: false }; - $loading.start('summaryPage'); + Loading.start('summaryPage'); Resource.read().then(({clusters}) => { $scope.clusters = clusters; @@ -38,7 +39,7 @@ export default [ return { _id, name }; }); - $loading.finish('summaryPage'); + Loading.finish('summaryPage'); $scope.ui.ready = true; @@ -53,7 +54,7 @@ export default [ return !row || !row._id || _.findIndex(rows, (item) => item._id === row._id) >= 0; }; - $scope.widthIsSufficient = $common.widthIsSufficient; + $scope.widthIsSufficient = LegacyUtils.widthIsSufficient; $scope.dialects = {}; $scope.projectStructureOptions = { @@ -326,10 +327,8 @@ export default [ $generatorOptional.optionalContent(zip, cluster); - const blob = zip.generate({type: 'blob', compression: 'DEFLATE', mimeType: 'application/octet-stream'}); - - // Download archive. - saveAs(blob, cluster.name + '-project.zip'); + zip.generateAsync({type: 'blob', compression: 'DEFLATE', mimeType: 'application/octet-stream'}) + .then((blob) => saver.saveAs(blob, cluster.name + '-project.zip')); }; /** http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/states/signin.state.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/states/signin.state.js b/modules/web-console/src/main/js/app/modules/states/signin.state.js index 91c84a3..a23a496 100644 --- a/modules/web-console/src/main/js/app/modules/states/signin.state.js +++ b/modules/web-console/src/main/js/app/modules/states/signin.state.js @@ -16,6 +16,7 @@ */ import angular from 'angular'; +import templateUrl from 'views/signin.jade'; angular .module('ignite-console.states.login', [ @@ -28,7 +29,7 @@ angular $stateProvider .state('signin', { url: '/', - templateUrl: '/signin.html', + templateUrl, metaTags: { } }); http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/modules/user/Auth.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/modules/user/Auth.service.js b/modules/web-console/src/main/js/app/modules/user/Auth.service.js index f0aa397..080f4af 100644 --- a/modules/web-console/src/main/js/app/modules/user/Auth.service.js +++ b/modules/web-console/src/main/js/app/modules/user/Auth.service.js @@ -15,8 +15,8 @@ * limitations under the License. */ -export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common', 'gettingStarted', 'User', 'IgniteAgentMonitor', - ($http, $root, $state, $window, $common, gettingStarted, User, agentMonitor) => { +export default ['Auth', ['$http', '$rootScope', '$state', '$window', 'IgniteLegacyUtils', 'IgniteMessages', 'gettingStarted', 'User', 'IgniteAgentMonitor', + ($http, $root, $state, $window, LegacyUtils, Messages, gettingStarted, User, agentMonitor) => { let _auth = false; try { @@ -44,7 +44,7 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common', forgotPassword(userInfo) { return $http.post('/api/v1/password/forgot', userInfo) .success(() => $state.go('password.send')) - .error((err) => $common.showPopoverMessage(null, null, 'forgot_email', $common.errorMessage(err))); + .error((err) => LegacyUtils.showPopoverMessage(null, null, 'forgot_email', Messages.errorMessage(null, err))); }, auth(action, userInfo) { return $http.post('/api/v1/' + action, userInfo) @@ -61,7 +61,7 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common', agentMonitor.init(); }); }) - .error((err) => $common.showPopoverMessage(null, null, action + '_email', $common.errorMessage(err))); + .error((err) => LegacyUtils.showPopoverMessage(null, null, action + '_email', Messages.errorMessage(null, err))); }, logout() { return $http.post('/api/v1/logout') @@ -70,7 +70,7 @@ export default ['Auth', ['$http', '$rootScope', '$state', '$window', '$common', $window.open($state.href('signin'), '_self'); }) - .catch((err) => $common.showError(err)); + .catch(Messages.showError); } }; }]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/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 index ec3f365..843aa5c 100644 --- a/modules/web-console/src/main/js/app/services/ChartColors.service.js +++ b/modules/web-console/src/main/js/app/services/ChartColors.service.js @@ -15,7 +15,7 @@ * limitations under the License. */ -import COLORS from 'app/data/colors.json!'; +import COLORS from 'app/data/colors.json'; export default ['IgniteChartColors', function() { return COLORS; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Clone.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/Clone.service.js b/modules/web-console/src/main/js/app/services/Clone.service.js new file mode 100644 index 0000000..52a4e4e --- /dev/null +++ b/modules/web-console/src/main/js/app/services/Clone.service.js @@ -0,0 +1,64 @@ +/* + * 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. + */ + +// Service for clone objects. +export default ['IgniteClone', ['$rootScope', '$q', '$modal', ($root, $q, $modal) => { + const scope = $root.$new(); + + let _names = []; + let deferred; + let _validator; + + function _nextAvailableName(name) { + let num = 1; + let tmpName = name; + + while (_.includes(_names, tmpName)) { + tmpName = name + '_' + num.toString(); + + num++; + } + + return tmpName; + } + + const cloneModal = $modal({templateUrl: '/templates/clone.html', scope, placement: 'center', show: false}); + + scope.ok = function(newName) { + if (!_validator || _validator(newName)) { + deferred.resolve(_nextAvailableName(newName)); + + cloneModal.hide(); + } + }; + + cloneModal.confirm = function(oldName, names, validator) { + _names = names; + + scope.newName = _nextAvailableName(oldName); + + _validator = validator; + + deferred = $q.defer(); + + cloneModal.$promise.then(cloneModal.show); + + return deferred.promise; + }; + + return cloneModal; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/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..c4e25a8 --- /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 ['IgniteConfirm', ['$rootScope', '$q', '$modal', '$animate', ($root, $q, $modal, $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/c38b3ba2/modules/web-console/src/main/js/app/services/ConfirmBatch.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/ConfirmBatch.service.js b/modules/web-console/src/main/js/app/services/ConfirmBatch.service.js new file mode 100644 index 0000000..ef66335 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/ConfirmBatch.service.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. + */ + +// Service for confirm or skip several steps. +export default ['IgniteConfirmBatch', ['$rootScope', '$q', '$modal', ($root, $q, $modal) => { + const scope = $root.$new(); + + scope.confirmModal = $modal({ + templateUrl: '/templates/batch-confirm.html', + scope, + placement: 'center', + show: false, + backdrop: 'static', + keyboard: false + }); + + const _done = (cancel) => { + scope.confirmModal.hide(); + + if (cancel) + scope.deferred.reject('cancelled'); + else + scope.deferred.resolve(); + }; + + const _nextElement = (skip) => { + scope.items[scope.curIx++].skip = skip; + + if (scope.curIx < scope.items.length) + scope.content = scope.contentGenerator(scope.items[scope.curIx]); + else + _done(); + }; + + scope.cancel = () => { + _done(true); + }; + + scope.skip = (applyToAll) => { + if (applyToAll) { + for (let i = scope.curIx; i < scope.items.length; i++) + scope.items[i].skip = true; + + _done(); + } + else + _nextElement(true); + }; + + scope.overwrite = (applyToAll) => { + if (applyToAll) + _done(); + else + _nextElement(false); + }; + + return { + /** + * Show confirm all dialog. + * + * @param confirmMessageFn Function to generate a confirm message. + * @param itemsToConfirm Array of element to process by confirm. + */ + confirm(confirmMessageFn, itemsToConfirm) { + scope.deferred = $q.defer(); + + scope.contentGenerator = confirmMessageFn; + + scope.items = itemsToConfirm; + scope.curIx = 0; + scope.content = (scope.items && scope.items.length > 0) ? scope.contentGenerator(scope.items[0]) : null; + + scope.confirmModal.$promise.then(scope.confirmModal.show); + + return scope.deferred.promise; + } + }; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js b/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js new file mode 100644 index 0000000..74c4764 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/CopyToClipboard.service.js @@ -0,0 +1,50 @@ +/* + * 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. + */ + +// Service to copy some value to OS clipboard. +export default ['IgniteCopyToClipboard', ['$window', 'IgniteMessages', ($window, Messages) => { + const body = angular.element($window.document.body); + + const textArea = angular.element('<textarea/>'); + + textArea.css({ + position: 'fixed', + opacity: '0' + }); + + return { + copy(toCopy) { + textArea.val(toCopy); + + body.append(textArea); + + textArea[0].select(); + + try { + if (document.execCommand('copy')) + Messages.showInfo('Value copied to clipboard'); + else + window.prompt('Copy to clipboard: Ctrl+C, Enter', toCopy); // eslint-disable-line no-alert + } + catch (err) { + window.prompt('Copy to clipboard: Ctrl+C, Enter', toCopy); // eslint-disable-line no-alert + } + + textArea.remove(); + } + }; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/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 index 87d11fd..5ad3e6a 100644 --- a/modules/web-console/src/main/js/app/services/Countries.service.js +++ b/modules/web-console/src/main/js/app/services/Countries.service.js @@ -15,7 +15,7 @@ * limitations under the License. */ -import COUNTRIES from 'app/data/countries.json!'; +import COUNTRIES from 'app/data/countries.json'; export default ['IgniteCountries', function() { const indexByName = _.keyBy(COUNTRIES, 'name'); http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Focus.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/Focus.service.js b/modules/web-console/src/main/js/app/services/Focus.service.js new file mode 100644 index 0000000..a07e181 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/Focus.service.js @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Service to transfer focus for specified element. +export default ['IgniteFocus', ['$timeout', ($timeout) => { + return { + move(id) { + // Timeout makes sure that is invoked after any other event has been triggered. + // E.g. click events that need to run before the focus or inputs elements that are + // in a disabled state but are enabled when those events are triggered. + $timeout(() => { + const elem = $('#' + id); + + if (elem.length > 0) + elem[0].focus(); + }, 100); + } + }; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/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 index a755e13..d34d669 100644 --- a/modules/web-console/src/main/js/app/services/JavaTypes.service.js +++ b/modules/web-console/src/main/js/app/services/JavaTypes.service.js @@ -16,12 +16,12 @@ */ // Java built-in class names. -import JAVA_CLASSES from 'app/data/java-classes.json!'; +import JAVA_CLASSES from 'app/data/java-classes.json'; // Java build-in primitive. -import JAVA_PRIMITIVES from 'app/data/java-primitives.json!'; +import JAVA_PRIMITIVES from 'app/data/java-primitives.json'; -import JAVA_KEYWORDS from 'app/data/java-keywords.json!'; +import JAVA_KEYWORDS from 'app/data/java-keywords.json'; export default ['JavaTypes', function() { return { http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/LegacyTable.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/LegacyTable.service.js b/modules/web-console/src/main/js/app/services/LegacyTable.service.js new file mode 100644 index 0000000..8f3b791 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/LegacyTable.service.js @@ -0,0 +1,205 @@ +/* + * 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. + */ + +// TODO: Refactor this service for legacy tables with more than one input field. +export default ['IgniteLegacyTable', ['IgniteLegacyUtils', 'IgniteFocus', (LegacyUtils, Focus) => { + function _model(item, field) { + return LegacyUtils.getModel(item, field); + } + + const table = {name: 'none', editIndex: -1}; + + function _tableReset() { + delete table.field; + table.name = 'none'; + table.editIndex = -1; + + LegacyUtils.hidePopover(); + } + + function _tableSaveAndReset() { + const field = table.field; + + const save = LegacyUtils.isDefined(field) && LegacyUtils.isDefined(field.save); + + if (!save || !LegacyUtils.isDefined(field) || field.save(field, table.editIndex, true)) { + _tableReset(); + + return true; + } + + return false; + } + + function _tableState(field, editIndex, specName) { + table.field = field; + table.name = specName || field.model; + table.editIndex = editIndex; + } + + function _tableUI(field) { + const ui = field.ui; + + return ui ? ui : field.type; + } + + function _tableFocus(focusId, index) { + Focus.move((index < 0 ? 'new' : 'cur') + focusId + (index >= 0 ? index : '')); + } + + function _tablePairValue(filed, index) { + return index < 0 ? {key: filed.newKey, value: filed.newValue} : {key: filed.curKey, value: filed.curValue}; + } + + function _tableStartEdit(item, tbl, index, save) { + _tableState(tbl, index); + + const val = _.get(_model(item, tbl), tbl.model)[index]; + + const ui = _tableUI(tbl); + + tbl.save = save; + + if (ui === 'table-pair') { + tbl.curKey = val[tbl.keyName]; + tbl.curValue = val[tbl.valueName]; + + _tableFocus('Key' + tbl.focusId, index); + } + else if (ui === 'table-db-fields') { + tbl.curDatabaseFieldName = val.databaseFieldName; + tbl.curDatabaseFieldType = val.databaseFieldType; + tbl.curJavaFieldName = val.javaFieldName; + tbl.curJavaFieldType = val.javaFieldType; + + _tableFocus('DatabaseFieldName' + tbl.focusId, index); + } + else if (ui === 'table-indexes') { + tbl.curIndexName = val.name; + tbl.curIndexType = val.indexType; + tbl.curIndexFields = val.fields; + + _tableFocus(tbl.focusId, index); + } + } + + function _tableNewItem(tbl) { + _tableState(tbl, -1); + + const ui = _tableUI(tbl); + + if (ui === 'table-pair') { + tbl.newKey = null; + tbl.newValue = null; + + _tableFocus('Key' + tbl.focusId, -1); + } + else if (ui === 'table-db-fields') { + tbl.newDatabaseFieldName = null; + tbl.newDatabaseFieldType = null; + tbl.newJavaFieldName = null; + tbl.newJavaFieldType = null; + + _tableFocus('DatabaseFieldName' + tbl.focusId, -1); + } + else if (ui === 'table-indexes') { + tbl.newIndexName = null; + tbl.newIndexType = 'SORTED'; + tbl.newIndexFields = null; + + _tableFocus(tbl.focusId, -1); + } + } + + return { + tableState: _tableState, + tableReset: _tableReset, + tableSaveAndReset: _tableSaveAndReset, + tableNewItem: _tableNewItem, + tableNewItemActive(tbl) { + return table.name === tbl.model && table.editIndex < 0; + }, + tableEditing(tbl, index) { + return table.name === tbl.model && table.editIndex === index; + }, + tableEditedRowIndex() { + return table.editIndex; + }, + tableField() { + return table.field; + }, + tableStartEdit: _tableStartEdit, + tableRemove(item, field, index) { + _tableReset(); + + _.get(_model(item, field), field.model).splice(index, 1); + }, + tablePairValue: _tablePairValue, + tablePairSave(pairValid, item, field, index, stopEdit) { + const valid = pairValid(item, field, index); + + if (valid) { + const pairValue = _tablePairValue(field, index); + + let pairModel = {}; + + const container = _.get(item, field.model); + + if (index < 0) { + pairModel[field.keyName] = pairValue.key; + pairModel[field.valueName] = pairValue.value; + + if (container) + container.push(pairModel); + else + _.set(item, field.model, [pairModel]); + + if (!stopEdit) + _tableNewItem(field); + } + else { + pairModel = container[index]; + + pairModel[field.keyName] = pairValue.key; + pairModel[field.valueName] = pairValue.value; + + if (!stopEdit) { + if (index < container.length - 1) + _tableStartEdit(item, field, index + 1); + else + _tableNewItem(field); + } + } + } + + return valid; + }, + tablePairSaveVisible(field, index) { + const pairValue = _tablePairValue(field, index); + + return !LegacyUtils.isEmptyString(pairValue.key) && !LegacyUtils.isEmptyString(pairValue.value); + }, + tableFocusInvalidField(index, id) { + _tableFocus(id, index); + + return false; + }, + tableFieldId(index, id) { + return (index < 0 ? 'new' : 'cur') + id + (index >= 0 ? index : ''); + } + }; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/LegacyUtils.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/LegacyUtils.service.js b/modules/web-console/src/main/js/app/services/LegacyUtils.service.js new file mode 100644 index 0000000..c4c83e9 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/LegacyUtils.service.js @@ -0,0 +1,948 @@ +/* + * 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. + */ + +// TODO: Refactor this service for legacy tables with more than one input field. +export default ['IgniteLegacyUtils', [ + '$alert', '$popover', '$anchorScroll', '$location', '$timeout', '$window', 'IgniteFocus', + ($alert, $popover, $anchorScroll, $location, $timeout, $window, Focus) => { + $anchorScroll.yOffset = 55; + + function isDefined(v) { + return !_.isNil(v); + } + + function isEmptyString(s) { + if (isDefined(s)) + return s.trim().length === 0; + + return true; + } + + const javaBuiltInClasses = [ + 'BigDecimal', 'Boolean', 'Byte', 'Date', 'Double', 'Float', 'Integer', 'Long', 'Object', 'Short', 'String', 'Time', 'Timestamp', 'UUID' + ]; + + const javaBuiltInTypes = [ + 'BigDecimal', 'boolean', 'Boolean', 'byte', 'Byte', 'Date', 'double', 'Double', 'float', 'Float', + 'int', 'Integer', 'long', 'Long', 'Object', 'short', 'Short', 'String', 'Time', 'Timestamp', 'UUID' + ]; + + const javaBuiltInFullNameClasses = [ + 'java.math.BigDecimal', 'java.lang.Boolean', 'java.lang.Byte', 'java.sql.Date', 'java.lang.Double', + 'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Object', 'java.lang.Short', + 'java.lang.String', 'java.sql.Time', 'java.sql.Timestamp', 'java.util.UUID' + ]; + + /** + * @param clsName Class name to check. + * @returns {Boolean} 'true' if given class name is a java build-in type. + */ + function isJavaBuiltInClass(clsName) { + if (isEmptyString(clsName)) + return false; + + return _.includes(javaBuiltInClasses, clsName) || _.includes(javaBuiltInFullNameClasses, clsName); + } + + const SUPPORTED_JDBC_TYPES = [ + 'BIGINT', + 'BIT', + 'BOOLEAN', + 'BLOB', + 'CHAR', + 'CLOB', + 'DATE', + 'DECIMAL', + 'DOUBLE', + 'FLOAT', + 'INTEGER', + 'LONGNVARCHAR', + 'LONGVARCHAR', + 'NCHAR', + 'NUMERIC', + 'NVARCHAR', + 'REAL', + 'SMALLINT', + 'TIME', + 'TIMESTAMP', + 'TINYINT', + 'VARCHAR' + ]; + + const ALL_JDBC_TYPES = [ + {dbName: 'BIT', dbType: -7, javaType: 'Boolean', primitiveType: 'boolean'}, + {dbName: 'TINYINT', dbType: -6, javaType: 'Byte', primitiveType: 'byte'}, + {dbName: 'SMALLINT', dbType: 5, javaType: 'Short', primitiveType: 'short'}, + {dbName: 'INTEGER', dbType: 4, javaType: 'Integer', primitiveType: 'int'}, + {dbName: 'BIGINT', dbType: -5, javaType: 'Long', primitiveType: 'long'}, + {dbName: 'FLOAT', dbType: 6, javaType: 'Float', primitiveType: 'float'}, + {dbName: 'REAL', dbType: 7, javaType: 'Double', primitiveType: 'double'}, + {dbName: 'DOUBLE', dbType: 8, javaType: 'Double', primitiveType: 'double'}, + {dbName: 'NUMERIC', dbType: 2, javaType: 'BigDecimal'}, + {dbName: 'DECIMAL', dbType: 3, javaType: 'BigDecimal'}, + {dbName: 'CHAR', dbType: 1, javaType: 'String'}, + {dbName: 'VARCHAR', dbType: 12, javaType: 'String'}, + {dbName: 'LONGVARCHAR', dbType: -1, javaType: 'String'}, + {dbName: 'DATE', dbType: 91, javaType: 'Date'}, + {dbName: 'TIME', dbType: 92, javaType: 'Time'}, + {dbName: 'TIMESTAMP', dbType: 93, javaType: 'Timestamp'}, + {dbName: 'BINARY', dbType: -2, javaType: 'Object'}, + {dbName: 'VARBINARY', dbType: -3, javaType: 'Object'}, + {dbName: 'LONGVARBINARY', dbType: -4, javaType: 'Object'}, + {dbName: 'NULL', dbType: 0, javaType: 'Object'}, + {dbName: 'OTHER', dbType: 1111, javaType: 'Object'}, + {dbName: 'JAVA_OBJECT', dbType: 2000, javaType: 'Object'}, + {dbName: 'DISTINCT', dbType: 2001, javaType: 'Object'}, + {dbName: 'STRUCT', dbType: 2002, javaType: 'Object'}, + {dbName: 'ARRAY', dbType: 2003, javaType: 'Object'}, + {dbName: 'BLOB', dbType: 2004, javaType: 'Object'}, + {dbName: 'CLOB', dbType: 2005, javaType: 'String'}, + {dbName: 'REF', dbType: 2006, javaType: 'Object'}, + {dbName: 'DATALINK', dbType: 70, javaType: 'Object'}, + {dbName: 'BOOLEAN', dbType: 16, javaType: 'Boolean', primitiveType: 'boolean'}, + {dbName: 'ROWID', dbType: -8, javaType: 'Object'}, + {dbName: 'NCHAR', dbType: -15, javaType: 'String'}, + {dbName: 'NVARCHAR', dbType: -9, javaType: 'String'}, + {dbName: 'LONGNVARCHAR', dbType: -16, javaType: 'String'}, + {dbName: 'NCLOB', dbType: 2011, javaType: 'String'}, + {dbName: 'SQLXML', dbType: 2009, javaType: 'Object'} + ]; + + /*eslint-disable */ + const JAVA_KEYWORDS = [ + 'abstract', 'assert', 'boolean', 'break', 'byte', + 'case', 'catch', 'char', 'class', 'const', + 'continue', 'default', 'do', 'double', 'else', + 'enum', 'extends', 'false', 'final', 'finally', + 'float', 'for', 'goto', 'if', 'implements', + 'import', 'instanceof', 'int', 'interface', 'long', + 'native', 'new', 'null', 'package', 'private', + 'protected', 'public', 'return', 'short', 'static', + 'strictfp', 'super', 'switch', 'synchronized', 'this', + 'throw', 'throws', 'transient', 'true', 'try', + 'void', 'volatile', 'while' + ]; + /*eslint-enable */ + + const VALID_JAVA_IDENTIFIER = new RegExp('^[a-zA-Z_$][a-zA-Z\\d_$]*$'); + + let popover = null; + + function isElementInViewport(el) { + const rect = el.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + } + + const _showPopoverMessage = (id, message, showTime) => { + const body = $('body'); + + let el = body.find('#' + id); + + if (!el || el.length === 0) + el = body.find('[name="' + id + '"]'); + + if (el && el.length > 0) { + if (!isElementInViewport(el[0])) { + $location.hash(el[0].id); + + $anchorScroll(); + } + + const newPopover = $popover(el, {content: message}); + + popover = newPopover; + + $timeout(() => newPopover.$promise.then(() => { + newPopover.show(); + + // Workaround to fix popover location when content is longer than content template. + // https://github.com/mgcrea/angular-strap/issues/1497 + $timeout(newPopover.$applyPlacement); + }), 400); + $timeout(() => newPopover.hide(), showTime || 5000); + } + }; + + function ensureActivePanel(ui, pnl, focusId) { + if (ui) { + const collapses = $('div.panel-collapse'); + + ui.loadPanel(pnl); + + const idx = _.findIndex(collapses, function(collapse) { + return collapse.id === pnl; + }); + + if (idx >= 0) { + const activePanels = ui.activePanels; + + if (!_.includes(ui.topPanels, idx)) { + ui.expanded = true; + + const customExpanded = ui[pnl]; + + if (customExpanded) + ui[customExpanded] = true; + } + + if (!activePanels || activePanels.length < 1) + ui.activePanels = [idx]; + else if (!_.includes(activePanels, idx)) { + const newActivePanels = angular.copy(activePanels); + + newActivePanels.push(idx); + + ui.activePanels = newActivePanels; + } + } + + if (isDefined(focusId)) + Focus.move(focusId); + } + } + + function showPopoverMessage(ui, panelId, id, message, showTime) { + if (popover) + popover.hide(); + + if (ui) { + ensureActivePanel(ui, panelId); + + $timeout(() => _showPopoverMessage(id, message, showTime), ui.isPanelLoaded(panelId) ? 200 : 500); + } + else + _showPopoverMessage(id, message, showTime); + + return false; + } + + function isValidJavaIdentifier(msg, ident, elemId, panels, panelId) { + if (isEmptyString(ident)) + return showPopoverMessage(panels, panelId, elemId, msg + ' is invalid!'); + + if (_.includes(JAVA_KEYWORDS, ident)) + return showPopoverMessage(panels, panelId, elemId, msg + ' could not contains reserved java keyword: "' + ident + '"!'); + + if (!VALID_JAVA_IDENTIFIER.test(ident)) + return showPopoverMessage(panels, panelId, elemId, msg + ' contains invalid identifier: "' + ident + '"!'); + + return true; + } + + let context = null; + + /** + * Calculate width of specified text in body's font. + * + * @param text Text to calculate width. + * @returns {Number} Width of text in pixels. + */ + function measureText(text) { + if (!context) { + const canvas = document.createElement('canvas'); + + context = canvas.getContext('2d'); + + const style = window.getComputedStyle(document.getElementsByTagName('body')[0]); + + context.font = style.fontSize + ' ' + style.fontFamily; + } + + return context.measureText(text).width; + } + + /** + * Compact java full class name by max number of characters. + * + * @param names Array of class names to compact. + * @param nameLength Max available width in characters for simple name. + * @returns {*} Array of compacted class names. + */ + function compactByMaxCharts(names, nameLength) { + for (let nameIx = 0; nameIx < names.length; nameIx++) { + const s = names[nameIx]; + + if (s.length > nameLength) { + let totalLength = s.length; + + const packages = s.split('.'); + + const packageCnt = packages.length - 1; + + for (let i = 0; i < packageCnt && totalLength > nameLength; i++) { + if (packages[i].length > 0) { + totalLength -= packages[i].length - 1; + + packages[i] = packages[i][0]; + } + } + + if (totalLength > nameLength) { + const className = packages[packageCnt]; + + const classNameLen = className.length; + + let remains = Math.min(nameLength - totalLength + classNameLen, classNameLen); + + if (remains < 3) + remains = Math.min(3, classNameLen); + + packages[packageCnt] = className.substring(0, remains) + '...'; + } + + let result = packages[0]; + + for (let i = 1; i < packages.length; i++) + result += '.' + packages[i]; + + names[nameIx] = result; + } + } + + return names; + } + + /** + * Compact java full class name by max number of pixels. + * + * @param names Array of class names to compact. + * @param nameLength Max available width in characters for simple name. Used for calculation optimization. + * @param nameWidth Maximum available width in pixels for simple name. + * @returns {*} Array of compacted class names. + */ + function compactByMaxPixels(names, nameLength, nameWidth) { + if (nameWidth <= 0) + return names; + + const fitted = []; + + const widthByName = []; + + const len = names.length; + + let divideTo = len; + + for (let nameIx = 0; nameIx < len; nameIx++) { + fitted[nameIx] = false; + + widthByName[nameIx] = nameWidth; + } + + // Try to distribute space from short class names to long class names. + let remains = 0; + + do { + for (let nameIx = 0; nameIx < len; nameIx++) { + if (!fitted[nameIx]) { + const curNameWidth = measureText(names[nameIx]); + + if (widthByName[nameIx] > curNameWidth) { + fitted[nameIx] = true; + + remains += widthByName[nameIx] - curNameWidth; + + divideTo -= 1; + + widthByName[nameIx] = curNameWidth; + } + } + } + + const remainsByName = remains / divideTo; + + for (let nameIx = 0; nameIx < len; nameIx++) { + if (!fitted[nameIx]) + widthByName[nameIx] += remainsByName; + } + } + while (remains > 0); + + // Compact class names to available for each space. + for (let nameIx = 0; nameIx < len; nameIx++) { + const s = names[nameIx]; + + if (s.length > (nameLength / 2 | 0)) { + let totalWidth = measureText(s); + + if (totalWidth > widthByName[nameIx]) { + const packages = s.split('.'); + + const packageCnt = packages.length - 1; + + for (let i = 0; i < packageCnt && totalWidth > widthByName[nameIx]; i++) { + if (packages[i].length > 1) { + totalWidth -= measureText(packages[i].substring(1, packages[i].length)); + + packages[i] = packages[i][0]; + } + } + + let shortPackage = ''; + + for (let i = 0; i < packageCnt; i++) + shortPackage += packages[i] + '.'; + + const className = packages[packageCnt]; + + const classLen = className.length; + + let minLen = Math.min(classLen, 3); + + totalWidth = measureText(shortPackage + className); + + // Compact class name if shorten package path is very long. + if (totalWidth > widthByName[nameIx]) { + let maxLen = classLen; + let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0; + + while (middleLen !== minLen && middleLen !== maxLen) { + const middleLenPx = measureText(shortPackage + className.substr(0, middleLen) + '...'); + + if (middleLenPx > widthByName[nameIx]) + maxLen = middleLen; + else + minLen = middleLen; + + middleLen = (minLen + (maxLen - minLen) / 2 ) | 0; + } + + names[nameIx] = shortPackage + className.substring(0, middleLen) + '...'; + } + else + names[nameIx] = shortPackage + className; + } + } + } + + return names; + } + + /** + * Compact any string by max number of pixels. + * + * @param label String to compact. + * @param nameWidth Maximum available width in pixels for simple name. + * @returns {*} Compacted string. + */ + function compactLabelByPixels(label, nameWidth) { + if (nameWidth <= 0) + return label; + + const totalWidth = measureText(label); + + if (totalWidth > nameWidth) { + let maxLen = label.length; + let minLen = Math.min(maxLen, 3); + let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0; + + while (middleLen !== minLen && middleLen !== maxLen) { + const middleLenPx = measureText(label.substr(0, middleLen) + '...'); + + if (middleLenPx > nameWidth) + maxLen = middleLen; + else + minLen = middleLen; + + middleLen = (minLen + (maxLen - minLen) / 2 ) | 0; + } + + return label.substring(0, middleLen) + '...'; + } + + return label; + } + + /** + * Calculate available width for text in link to edit element. + * + * @param index Showed index of element for calculation of maximum width in pixels. + * @param id Id of contains link table. + * @returns {*[]} First element is length of class for single value, second element is length for pair vlaue. + */ + function availableWidth(index, id) { + const idElem = $('#' + id); + + let width = 0; + + switch (idElem.prop('tagName')) { + // Detection of available width in presentation table row. + case 'TABLE': + const cont = $(idElem.find('tr')[index - 1]).find('td')[0]; + + width = cont.clientWidth; + + if (width > 0) { + const children = $(cont).children(':not("a")'); + + _.forEach(children, function(child) { + if ('offsetWidth' in child) + width -= $(child).outerWidth(true); + }); + } + + break; + + // Detection of available width in dropdown row. + case 'A': + width = idElem.width(); + + $(idElem).children(':not("span")').each(function(ix, child) { + if ('offsetWidth' in child) + width -= child.offsetWidth; + }); + + break; + + default: + } + + return width | 0; + } + + function getModel(obj, field) { + let path = field.path; + + if (!isDefined(path) || !isDefined(obj)) + return obj; + + path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties + path = path.replace(/^\./, ''); // strip a leading dot + + const segs = path.split('.'); + let root = obj; + + while (segs.length > 0) { + const pathStep = segs.shift(); + + if (typeof root[pathStep] === 'undefined') + root[pathStep] = {}; + + root = root[pathStep]; + } + + return root; + } + + function extractDataSource(cache) { + if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) { + const storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind]; + + if (storeFactory.dialect || (storeFactory.connectVia === 'DataSource')) + return storeFactory; + } + + return null; + } + + const cacheStoreJdbcDialects = [ + {value: 'Generic', label: 'Generic JDBC'}, + {value: 'Oracle', label: 'Oracle'}, + {value: 'DB2', label: 'IBM DB2'}, + {value: 'SQLServer', label: 'Microsoft SQL Server'}, + {value: 'MySQL', label: 'MySQL'}, + {value: 'PostgreSQL', label: 'PostgreSQL'}, + {value: 'H2', label: 'H2 database'} + ]; + + function domainForStoreConfigured(domain) { + const isEmpty = !isDefined(domain) || (isEmptyString(domain.databaseSchema) && + isEmptyString(domain.databaseTable) && + _.isEmpty(domain.keyFields) && + _.isEmpty(domain.valueFields)); + + return !isEmpty; + } + + const DS_CHECK_SUCCESS = { checked: true }; + + function compareDataSources(firstCache, secondCache) { + const firstDs = extractDataSource(firstCache); + const secondDs = extractDataSource(secondCache); + + if (firstDs && secondDs) { + const firstDB = firstDs.dialect; + const secondDB = secondDs.dialect; + + if (firstDs.dataSourceBean === secondDs.dataSourceBean && firstDB !== secondDB) + return {checked: false, firstCache, firstDB, secondCache, secondDB}; + } + + return DS_CHECK_SUCCESS; + } + + function compareSQLSchemaNames(firstCache, secondCache) { + const firstName = firstCache.sqlSchema; + const secondName = secondCache.sqlSchema; + + if (firstName && secondName && (firstName === secondName)) + return {checked: false, firstCache, secondCache}; + + return DS_CHECK_SUCCESS; + } + + function toJavaName(prefix, name) { + const javaName = name ? name.replace(/[^A-Za-z_0-9]+/g, '_') : 'dflt'; + + return prefix + javaName.charAt(0).toLocaleUpperCase() + javaName.slice(1); + } + + return { + getModel, + mkOptions(options) { + return _.map(options, (option) => { + return {value: option, label: isDefined(option) ? option : 'Not set'}; + }); + }, + isDefined, + hasProperty(obj, props) { + for (const propName in props) { + if (props.hasOwnProperty(propName)) { + if (obj[propName]) + return true; + } + } + + return false; + }, + isEmptyString, + SUPPORTED_JDBC_TYPES, + findJdbcType(jdbcType) { + const res = _.find(ALL_JDBC_TYPES, function(item) { + return item.dbType === jdbcType; + }); + + return res ? res : {dbName: 'Unknown', javaType: 'Unknown'}; + }, + javaBuiltInClasses, + javaBuiltInTypes, + isJavaBuiltInClass, + isValidJavaIdentifier, + isValidJavaClass(msg, ident, allowBuiltInClass, elemId, packageOnly, panels, panelId) { + if (isEmptyString(ident)) + return showPopoverMessage(panels, panelId, elemId, msg + ' could not be empty!'); + + const parts = ident.split('.'); + + const len = parts.length; + + if (!allowBuiltInClass && isJavaBuiltInClass(ident)) + return showPopoverMessage(panels, panelId, elemId, msg + ' should not be the Java build-in class!'); + + if (len < 2 && !isJavaBuiltInClass(ident) && !packageOnly) + return showPopoverMessage(panels, panelId, elemId, msg + ' does not have package specified!'); + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + + if (!isValidJavaIdentifier(msg, part, elemId, panels, panelId)) + return false; + } + + return true; + }, + domainForQueryConfigured(domain) { + const isEmpty = !isDefined(domain) || (_.isEmpty(domain.fields) && + _.isEmpty(domain.aliases) && + _.isEmpty(domain.indexes)); + + return !isEmpty; + }, + domainForStoreConfigured, + /** + * Cut class name by width in pixel or width in symbol count. + * + * @param id Id of parent table. + * @param index Row number in table. + * @param maxLength Maximum length in symbols for all names. + * @param names Array of class names to compact. + * @param divider String to visualy divide items. + * @returns {*} Array of compacted class names. + */ + compactJavaName(id, index, maxLength, names, divider) { + divider = ' ' + divider + ' '; + + const prefix = index + ') '; + + const nameCnt = names.length; + + const nameLength = ((maxLength - 3 * (nameCnt - 1)) / nameCnt) | 0; + + try { + const nameWidth = (availableWidth(index, id) - measureText(prefix) - (nameCnt - 1) * measureText(divider)) / + nameCnt | 0; + + // HTML5 calculation of showed message width. + names = compactByMaxPixels(names, nameLength, nameWidth); + } + catch (err) { + names = compactByMaxCharts(names, nameLength); + } + + let result = prefix + names[0]; + + for (let nameIx = 1; nameIx < names.length; nameIx++) + result += divider + names[nameIx]; + + return result; + }, + /** + * Compact text by width in pixels or symbols count. + * + * @param id Id of parent table. + * @param index Row number in table. + * @param maxLength Maximum length in symbols for all names. + * @param label Text to compact. + * @returns Compacted label text. + */ + compactTableLabel(id, index, maxLength, label) { + label = index + ') ' + label; + + try { + const nameWidth = availableWidth(index, id) | 0; + + // HTML5 calculation of showed message width. + label = compactLabelByPixels(label, nameWidth); + } + catch (err) { + const nameLength = maxLength - 3 | 0; + + label = label.length > maxLength ? label.substr(0, nameLength) + '...' : label; + } + + return label; + }, + widthIsSufficient(id, index, text) { + try { + const available = availableWidth(index, id); + + const required = measureText(text); + + return !available || available >= Math.floor(required); + } + catch (err) { + return true; + } + }, + ensureActivePanel(panels, id, focusId) { + ensureActivePanel(panels, id, focusId); + }, + showPopoverMessage, + hidePopover() { + if (popover) + popover.hide(); + }, + confirmUnsavedChanges(dirty, selectFunc) { + if (dirty) { + if ($window.confirm('You have unsaved changes.\n\nAre you sure you want to discard them?')) + selectFunc(); + } + else + selectFunc(); + }, + saveBtnTipText(dirty, objectName) { + if (dirty) + return 'Save ' + objectName; + + return 'Nothing to save'; + }, + download(type, name, data) { + const file = document.createElement('a'); + + file.setAttribute('href', 'data:' + type + ';charset=utf-8,' + data); + file.setAttribute('download', name); + file.setAttribute('target', '_self'); + + file.style.display = 'none'; + + document.body.appendChild(file); + + file.click(); + + document.body.removeChild(file); + }, + formUI() { + return { + ready: false, + expanded: false, + loadedPanels: [], + loadPanel(pnl) { + if (!_.includes(this.loadedPanels, pnl)) + this.loadedPanels.push(pnl); + }, + isPanelLoaded(pnl) { + return _.includes(this.loadedPanels, pnl); + } + }; + }, + getQueryVariable(name) { + const attrs = window.location.search.substring(1).split('&'); + const attr = _.find(attrs, (a) => a === name || (a.indexOf('=') >= 0 && a.substr(0, a.indexOf('=')) === name)); + + if (!isDefined(attr)) + return null; + + if (attr === name) + return true; + + return attr.substr(attr.indexOf('=') + 1); + }, + cacheStoreJdbcDialects, + cacheStoreJdbcDialectsLabel(dialect) { + const found = _.find(cacheStoreJdbcDialects, function(dialectVal) { + return dialectVal.value === dialect; + }); + + return found ? found.label : null; + }, + checkCachesDataSources(caches, checkCacheExt) { + let res = DS_CHECK_SUCCESS; + + _.find(caches, function(curCache, curIx) { + if (isDefined(checkCacheExt)) { + if (checkCacheExt._id !== curCache._id) { + res = compareDataSources(checkCacheExt, curCache); + + return !res.checked; + } + + return false; + } + + return _.find(caches, function(checkCache, checkIx) { + if (checkIx < curIx) { + res = compareDataSources(checkCache, curCache); + + return !res.checked; + } + + return false; + }); + }); + + return res; + }, + checkCacheSQLSchemas(caches, checkCacheExt) { + let res = DS_CHECK_SUCCESS; + + _.find(caches, (curCache, curIx) => { + if (isDefined(checkCacheExt)) { + if (checkCacheExt._id !== curCache._id) { + res = compareSQLSchemaNames(checkCacheExt, curCache); + + return !res.checked; + } + + return false; + } + + return _.find(caches, function(checkCache, checkIx) { + if (checkIx < curIx) { + res = compareSQLSchemaNames(checkCache, curCache); + + return !res.checked; + } + + return false; + }); + }); + + return res; + }, + autoCacheStoreConfiguration(cache, domains) { + const cacheStoreFactory = isDefined(cache.cacheStoreFactory) && + isDefined(cache.cacheStoreFactory.kind); + + if (!cacheStoreFactory && _.findIndex(domains, domainForStoreConfigured) >= 0) { + const dflt = !cache.readThrough && !cache.writeThrough; + + return { + cacheStoreFactory: { + kind: 'CacheJdbcPojoStoreFactory', + CacheJdbcPojoStoreFactory: { + dataSourceBean: toJavaName('ds', cache.name), + dialect: 'Generic' + }, + CacheJdbcBlobStoreFactory: { connectVia: 'DataSource' } + }, + readThrough: dflt || cache.readThrough, + writeThrough: dflt || cache.writeThrough + }; + } + }, + autoClusterSwapSpiConfiguration(cluster, caches) { + const swapConfigured = cluster.swapSpaceSpi && cluster.swapSpaceSpi.kind; + + if (!swapConfigured && _.find(caches, (cache) => cache.swapEnabled)) + return {swapSpaceSpi: {kind: 'FileSwapSpaceSpi'}}; + + return null; + }, + randomString(len) { + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const possibleLen = possible.length; + + let res = ''; + + for (let i = 0; i < len; i++) + res += possible.charAt(Math.floor(Math.random() * possibleLen)); + + return res; + }, + checkFieldValidators(ui) { + const form = ui.inputForm; + const errors = form.$error; + const errKeys = Object.keys(errors); + + if (errKeys && errKeys.length > 0) { + const firstErrorKey = errKeys[0]; + + const firstError = errors[firstErrorKey][0]; + const actualError = firstError.$error[firstErrorKey][0]; + + const errNameFull = actualError.$name; + const errNameShort = errNameFull.endsWith('TextInput') ? errNameFull.substring(0, errNameFull.length - 9) : errNameFull; + + const extractErrorMessage = function(errName) { + try { + return errors[firstErrorKey][0].$errorMessages[errName][firstErrorKey]; + } + catch (ignored) { + try { + return form[firstError.$name].$errorMessages[errName][firstErrorKey]; + } + catch (ignited) { + return false; + } + } + }; + + const msg = extractErrorMessage(errNameFull) || extractErrorMessage(errNameShort) || 'Invalid value!'; + + return showPopoverMessage(ui, firstError.$name, errNameFull, msg); + } + + return true; + } + }; + } +]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/Messages.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/Messages.service.js b/modules/web-console/src/main/js/app/services/Messages.service.js new file mode 100644 index 0000000..e679488 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/Messages.service.js @@ -0,0 +1,63 @@ +/* + * 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. + */ + +// Service to show various information and error messages. +export default ['IgniteMessages', ['$alert', ($alert) => { + // Common instance of alert modal. + let msgModal; + + const errorMessage = (prefix, err) => { + prefix = prefix || ''; + + if (err) { + if (err.hasOwnProperty('message')) + return prefix + err.message; + + return prefix + err; + } + + return prefix + 'Internal error.'; + }; + + const hideAlert = () => { + if (msgModal) + msgModal.hide(); + }; + + const _showMessage = (err, type, duration, icon) => { + hideAlert(); + + const title = errorMessage(null, err); + + msgModal = $alert({type, title, duration}); + + msgModal.$scope.icon = icon; + }; + + return { + errorMessage, + hideAlert, + showError(err) { + _showMessage(err, 'danger', 10, 'fa-exclamation-triangle'); + + return false; + }, + showInfo(err) { + _showMessage(err, 'success', 3, 'fa-check-circle-o'); + } + }; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js b/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js new file mode 100644 index 0000000..4c7052b --- /dev/null +++ b/modules/web-console/src/main/js/app/services/ModelNormalizer.service.js @@ -0,0 +1,59 @@ +/* + * 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. + */ + +// Service to normalize objects for dirty checks. +export default ['IgniteModelNormalizer', () => { + /** + * Normalize object for dirty checks. + * + * @param original + * @param dest + * @returns {*} + */ + const normalize = (original, dest) => { + if (_.isUndefined(original)) + return dest; + + if (_.isObject(original)) { + _.forOwn(original, (value, key) => { + if (/\$\$hashKey/.test(key)) + return; + + const attr = normalize(value); + + if (!_.isNil(attr)) { + dest = dest || {}; + dest[key] = attr; + } + }); + } else if (_.isBoolean(original) && original === true) + dest = original; + else if ((_.isString(original) && original.length) || _.isNumber(original)) + dest = original; + else if (_.isArray(original) && original.length) + dest = _.map(original, (value) => normalize(value, {})); + + return dest; + }; + + return { + normalize, + isEqual(prev, cur) { + return _.isEqual(prev, normalize(cur)); + } + }; +}]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js b/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js new file mode 100644 index 0000000..91244b0 --- /dev/null +++ b/modules/web-console/src/main/js/app/services/UnsavedChangesGuard.service.js @@ -0,0 +1,38 @@ +/* + * 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. + */ + +const MSG = 'You have unsaved changes.\n\nAre you sure you want to discard them?'; + +// Service that show confirmation about unsaved changes on user change location. +export default ['IgniteUnsavedChangesGuard', ['$rootScope', ($root) => { + return { + install(scope, customDirtyCheck = () => scope.ui.inputForm.$dirty) { + scope.$on('$destroy', () => window.onbeforeunload = null); + + const unbind = $root.$on('$stateChangeStart', (event) => { + if (_.get(scope, 'ui.inputForm', false) && customDirtyCheck()) { + if (!confirm(MSG)) // eslint-disable-line no-alert + event.preventDefault(); + else + unbind(); + } + }); + + window.onbeforeunload = () => _.get(scope, 'ui.inputForm.$dirty', false) ? MSG : null; + } + }; +}]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/c38b3ba2/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 deleted file mode 100644 index bec9ea3..0000000 --- a/modules/web-console/src/main/js/app/services/cleanup.service.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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 (_.isBoolean(original) && original === true) - dist = original; - else if ((_.isString(original) && original.length) || _.isNumber(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/c38b3ba2/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 deleted file mode 100644 index bb07cfd..0000000 --- a/modules/web-console/src/main/js/app/services/confirm.service.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// 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/c38b3ba2/modules/web-console/src/main/js/app/vendor.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/app/vendor.js b/modules/web-console/src/main/js/app/vendor.js new file mode 100644 index 0000000..a8eeea7 --- /dev/null +++ b/modules/web-console/src/main/js/app/vendor.js @@ -0,0 +1,54 @@ +/* + * 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 'jquery'; +import 'angular'; +import 'angular-animate'; +import 'angular-sanitize'; +import 'angular-strap'; +import 'angular-strap/dist/angular-strap.tpl'; +import 'angular-socket-io'; +import 'angular-retina'; +import 'angular-ui-router'; +import 'ui-router-metatags/dist/ui-router-metatags'; +import 'angular-smart-table'; +import 'angular-ui-grid/ui-grid'; +import 'angular-drag-and-drop-lists'; +import 'angular-nvd3'; +import 'angular-tree-control'; +import 'angular-gridster'; +import 'bootstrap-sass/assets/javascripts/bootstrap/transition'; +import 'bootstrap-sass/assets/javascripts/bootstrap/carousel'; +import 'brace'; +import 'brace/mode/xml'; +import 'brace/mode/sql'; +import 'brace/mode/java'; +import 'brace/mode/dockerfile'; +import 'brace/mode/snippets'; +import 'brace/theme/chrome'; +import 'brace/ext/language_tools'; +import 'brace/ext/searchbox'; +import 'file-saver'; +import 'jszip'; +import 'nvd3'; +import 'query-command-supported'; +import 'angular-gridster/dist/angular-gridster.min.css'; +import 'angular-tree-control/css/tree-control-attribute.css'; +import 'angular-tree-control/css/tree-control.css'; +import 'angular-ui-grid/ui-grid.css'; +import 'angular-motion/dist/angular-motion.css'; +import 'nvd3/build/nv.d3.css';
