http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js b/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js deleted file mode 100644 index 8f6a6fa..0000000 --- a/modules/web-console/frontend/app/modules/states/configuration/summary/summary.controller.js +++ /dev/null @@ -1,350 +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. - */ - -import _ from 'lodash'; -import saver from 'file-saver'; - -import summaryProjectStructureTemplateUrl from 'views/configuration/summary-project-structure.tpl.pug'; - -const escapeFileName = (name) => name.replace(/[\\\/*\"\[\],\.:;|=<>?]/g, '-').replace(/ /g, '_'); - -export default [ - '$rootScope', '$scope', '$http', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteLoading', '$filter', 'IgniteConfigurationResource', 'JavaTypes', 'IgniteVersion', 'IgniteConfigurationGenerator', 'SpringTransformer', 'JavaTransformer', 'IgniteDockerGenerator', 'IgniteMavenGenerator', 'IgnitePropertiesGenerator', 'IgniteReadmeGenerator', 'IgniteFormUtils', 'IgniteSummaryZipper', 'IgniteActivitiesData', - function($root, $scope, $http, LegacyUtils, Messages, Loading, $filter, Resource, JavaTypes, Version, generator, spring, java, docker, pom, propsGenerator, readme, FormUtils, SummaryZipper, ActivitiesData) { - const ctrl = this; - - // Define template urls. - ctrl.summaryProjectStructureTemplateUrl = summaryProjectStructureTemplateUrl; - - $scope.ui = { - isSafari: !!(/constructor/i.test(window.HTMLElement) || window.safari), - ready: false - }; - - Loading.start('summaryPage'); - - Resource.read() - .then(Resource.populate) - .then(({clusters}) => { - $scope.clusters = clusters; - $scope.clustersMap = {}; - $scope.clustersView = _.map(clusters, (item) => { - const {_id, name} = item; - - $scope.clustersMap[_id] = item; - - return {_id, name}; - }); - - Loading.finish('summaryPage'); - - $scope.ui.ready = true; - - if (!_.isEmpty(clusters)) { - const idx = sessionStorage.summarySelectedId || 0; - - $scope.selectItem(clusters[idx]); - } - }) - .catch(Messages.showError); - - $scope.contentVisible = (rows, row) => { - return !row || !row._id || _.findIndex(rows, (item) => item._id === row._id) >= 0; - }; - - $scope.widthIsSufficient = FormUtils.widthIsSufficient; - $scope.dialects = {}; - - $scope.projectStructureOptions = { - nodeChildren: 'children', - dirSelectable: false, - injectClasses: { - iExpanded: 'fa fa-folder-open-o', - iCollapsed: 'fa fa-folder-o' - }, - equality: (node1, node2) => { - return node1 === node2; - } - }; - - const javaConfigFolder = { - type: 'folder', - name: 'config', - children: [ - { type: 'file', name: 'ClientConfigurationFactory.java' }, - { type: 'file', name: 'ServerConfigurationFactory.java' } - ] - }; - - const loadFolder = { - type: 'folder', - name: 'load', - children: [ - { type: 'file', name: 'LoadCaches.java' } - ] - }; - - const javaStartupFolder = { - type: 'folder', - name: 'startup', - children: [ - { type: 'file', name: 'ClientNodeCodeStartup.java' }, - { type: 'file', name: 'ClientNodeSpringStartup.java' }, - { type: 'file', name: 'ServerNodeCodeStartup.java' }, - { type: 'file', name: 'ServerNodeSpringStartup.java' } - ] - }; - - const demoFolder = { - type: 'folder', - name: 'demo', - children: [ - { type: 'file', name: 'DemoStartup.java' } - ] - }; - - const clnCfg = { type: 'file', name: 'client.xml' }; - const srvCfg = { type: 'file', name: 'server.xml' }; - - const resourcesFolder = { - type: 'folder', - name: 'resources', - children: [ - { - type: 'folder', - name: 'META-INF', - children: [clnCfg, srvCfg] - } - ] - }; - - const javaFolder = { - type: 'folder', - name: 'java', - children: [ - { - type: 'folder', - name: 'config', - children: [ - javaConfigFolder, - javaStartupFolder - ] - } - ] - }; - - const mainFolder = { - type: 'folder', - name: 'main', - children: [javaFolder] - }; - - const projectStructureRoot = { - type: 'folder', - name: 'project.zip', - children: [ - { - type: 'folder', - name: 'jdbc-drivers', - children: [ - { type: 'file', name: 'README.txt' } - ] - }, - { - type: 'folder', - name: 'src', - children: [mainFolder] - }, - { type: 'file', name: '.dockerignore' }, - { type: 'file', name: 'Dockerfile' }, - { type: 'file', name: 'pom.xml' }, - { type: 'file', name: 'README.txt' } - ] - }; - - $scope.projectStructure = [projectStructureRoot]; - - $scope.projectStructureExpanded = [projectStructureRoot]; - - $scope.tabsServer = { activeTab: 0 }; - $scope.tabsClient = { activeTab: 0 }; - - /** - * - * @param {Object} node - Tree node. - * @param {string[]} path - Path to find. - * @returns {Object} Tree node. - */ - function getOrCreateFolder(node, path) { - if (_.isEmpty(path)) - return node; - - const leaf = path.shift(); - - let children = null; - - if (!_.isEmpty(node.children)) { - children = _.find(node.children, {type: 'folder', name: leaf}); - - if (children) - return getOrCreateFolder(children, path); - } - - children = {type: 'folder', name: leaf, children: []}; - - node.children.push(children); - - node.children = _.orderBy(node.children, ['type', 'name'], ['desc', 'asc']); - - return getOrCreateFolder(children, path); - } - - function addClass(fullClsName) { - const path = fullClsName.split('.'); - const leaf = {type: 'file', name: path.pop() + '.java'}; - const folder = getOrCreateFolder(javaFolder, path); - - if (!_.find(folder.children, leaf)) - folder.children.push(leaf); - } - - function cacheHasDatasource(cache) { - if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) { - const storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind]; - - return !!(storeFactory && (storeFactory.connectVia ? (storeFactory.connectVia === 'DataSource' ? storeFactory.dialect : false) : storeFactory.dialect)); // eslint-disable-line no-nested-ternary - } - - return false; - } - - $scope.selectItem = (cluster) => { - delete ctrl.cluster; - - if (!cluster) - return; - - cluster = $scope.clustersMap[cluster._id]; - - ctrl.cluster = cluster; - - $scope.cluster = cluster; - $scope.selectedItem = cluster; - $scope.dialects = {}; - - sessionStorage.summarySelectedId = $scope.clusters.indexOf(cluster); - - mainFolder.children = [javaFolder, resourcesFolder]; - - if (_.find(cluster.caches, (cache) => !_.isNil(cache.cacheStoreFactory))) - javaFolder.children = [javaConfigFolder, loadFolder, javaStartupFolder]; - else - javaFolder.children = [javaConfigFolder, javaStartupFolder]; - - if (_.nonNil(_.find(cluster.caches, cacheHasDatasource)) || cluster.sslEnabled) - resourcesFolder.children.push({ type: 'file', name: 'secret.properties' }); - - if (java.isDemoConfigured(cluster, $root.IgniteDemoMode)) - javaFolder.children.push(demoFolder); - - if (cluster.discovery.kind === 'Jdbc' && cluster.discovery.Jdbc.dialect) - $scope.dialects[cluster.discovery.Jdbc.dialect] = true; - - if (cluster.discovery.kind === 'Kubernetes') - resourcesFolder.children.push({ type: 'file', name: 'ignite-service.yaml' }); - - _.forEach(cluster.caches, (cache) => { - if (cache.cacheStoreFactory) { - const store = cache.cacheStoreFactory[cache.cacheStoreFactory.kind]; - - if (store && store.dialect) - $scope.dialects[store.dialect] = true; - } - - _.forEach(cache.domains, (domain) => { - if (domain.generatePojo && _.nonEmpty(domain.keyFields)) { - if (JavaTypes.nonBuiltInClass(domain.keyType)) - addClass(domain.keyType); - - addClass(domain.valueType); - } - }); - }); - - projectStructureRoot.name = cluster.name + '-project.zip'; - clnCfg.name = cluster.name + '-client.xml'; - srvCfg.name = cluster.name + '-server.xml'; - }; - - $scope.$watch('cluster', (cluster) => { - if (!cluster) - return; - - if (!$filter('hasPojo')(cluster) && $scope.tabsClient.activeTab === 3) - $scope.tabsClient.activeTab = 0; - }); - - $scope.$watch('cluster._id', () => { - $scope.tabsClient.init = []; - $scope.tabsServer.init = []; - }); - - // TODO IGNITE-2114: implemented as independent logic for download. - $scope.downloadConfiguration = function() { - if ($scope.isPrepareDownloading) - return; - - const cluster = $scope.cluster; - - $scope.isPrepareDownloading = true; - - ActivitiesData.post({ action: '/configuration/download' }); - - return new SummaryZipper({ cluster, data: ctrl.data || {}, demo: $root.IgniteDemoMode, targetVer: Version.currentSbj.getValue() }) - .then((data) => { - saver.saveAs(data, escapeFileName(cluster.name) + '-project.zip'); - }) - .catch((err) => Messages.showError('Failed to generate project files. ' + err.message)) - .then(() => $scope.isPrepareDownloading = false); - }; - - /** - * @returns {boolean} 'true' if at least one proprietary JDBC driver is configured for cache store. - */ - $scope.downloadJdbcDriversVisible = function() { - const dialects = $scope.dialects; - - return !!(dialects.Oracle || dialects.DB2 || dialects.SQLServer); - }; - - /** - * Open download proprietary JDBC driver pages. - */ - $scope.downloadJdbcDrivers = function() { - const dialects = $scope.dialects; - - if (dialects.Oracle) - window.open('http://www.oracle.com/technetwork/database/features/jdbc/default-2280470.html'); - - if (dialects.DB2) - window.open('http://www-01.ibm.com/support/docview.wss?uid=swg21363866'); - - if (dialects.SQLServer) - window.open('https://www.microsoft.com/en-us/download/details.aspx?id=11774'); - }; - } -];
http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/modules/states/configuration/summary/summary.worker.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/states/configuration/summary/summary.worker.js b/modules/web-console/frontend/app/modules/states/configuration/summary/summary.worker.js index 1939906..c80d698 100644 --- a/modules/web-console/frontend/app/modules/states/configuration/summary/summary.worker.js +++ b/modules/web-console/frontend/app/modules/states/configuration/summary/summary.worker.js @@ -26,6 +26,11 @@ import IgniteConfigurationGenerator from 'app/modules/configuration/generator/Co import IgniteJavaTransformer from 'app/modules/configuration/generator/JavaTransformer.service'; import IgniteSpringTransformer from 'app/modules/configuration/generator/SpringTransformer.service'; +import {nonEmpty, nonNil} from 'app/utils/lodashMixins'; +import get from 'lodash/get'; +import filter from 'lodash/filter'; +import isEmpty from 'lodash/isEmpty'; + const maven = new IgniteMavenGenerator(); const docker = new IgniteDockerGenerator(); const readme = new IgniteReadmeGenerator(); @@ -71,8 +76,8 @@ onmessage = function(e) { const cfg = generator.igniteConfiguration(cluster, targetVer, false); const clientCfg = generator.igniteConfiguration(cluster, targetVer, true); - const clientNearCaches = _.filter(cluster.caches, (cache) => - cache.cacheMode === 'PARTITIONED' && _.get(cache, 'clientNearConfiguration.enabled')); + const clientNearCaches = filter(cluster.caches, (cache) => + cache.cacheMode === 'PARTITIONED' && get(cache, 'clientNearConfiguration.enabled')); const secProps = properties.generate(cfg); @@ -104,9 +109,9 @@ onmessage = function(e) { } // Generate loader for caches with configured store. - const cachesToLoad = _.filter(cluster.caches, (cache) => _.nonNil(cache.cacheStoreFactory)); + const cachesToLoad = filter(cluster.caches, (cache) => nonNil(cache.cacheStoreFactory)); - if (_.nonEmpty(cachesToLoad)) + if (nonEmpty(cachesToLoad)) zip.file(`${srcPath}/load/LoadCaches.java`, java.loadCaches(cachesToLoad, 'load', 'LoadCaches', `"${clientXml}"`)); const startupPath = `${srcPath}/startup`; @@ -124,8 +129,8 @@ onmessage = function(e) { zip.file('README.txt', readme.generate()); zip.file('jdbc-drivers/README.txt', readme.generateJDBC()); - if (_.isEmpty(data.pojos)) - data.pojos = java.pojos(cluster.caches); + if (isEmpty(data.pojos)) + data.pojos = java.pojos(cluster.caches, true); for (const pojo of data.pojos) { if (pojo.keyClass) http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/btn/index.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/btn/index.scss b/modules/web-console/frontend/app/primitives/btn/index.scss index 162fde4..061d411 100644 --- a/modules/web-console/frontend/app/primitives/btn/index.scss +++ b/modules/web-console/frontend/app/primitives/btn/index.scss @@ -250,6 +250,14 @@ $btn-content-padding-with-border: 9px 11px; @include btn-ignite--link-dashed($color, $activeHover, $disabled); } +.btn-ignite--link-dashed-primary { + $color: $ignite-brand-primary; + $activeHover: change-color($color, $lightness: 26%); + $disabled: change-color($color, $saturation: 57%, $lightness: 68%); + + @include btn-ignite--link-dashed($color, $activeHover, $disabled); +} + .btn-ignite--link-dashed-secondary { $activeHover: change-color($ignite-brand-success, $lightness: 26%); @include btn-ignite--link-dashed($text-color, $activeHover, $gray-light); @@ -302,6 +310,10 @@ $btn-content-padding-with-border: 9px 11px; $line-color: $ignite-brand-primary; border-right-color: change-color($line-color, $lightness: 41%); } + .btn-ignite.btn-ignite--success { + $line-color: $ignite-brand-success; + border-right-color: change-color($line-color, $saturation: 63%, $lightness: 33%); + } } @mixin ignite-link($color, $color-hover) { @@ -336,3 +348,12 @@ $btn-content-padding-with-border: 9px 11px; $color-hover: change-color($ignite-brand-success, $lightness: 26%) ); } + +.btn-ignite--link { + background: transparent; + + @include ignite-link( + $color: $ignite-brand-success, + $color-hover: change-color($ignite-brand-success, $lightness: 26%) + ); +} http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/checkbox/index.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/checkbox/index.scss b/modules/web-console/frontend/app/primitives/checkbox/index.scss new file mode 100644 index 0000000..d1e1e83 --- /dev/null +++ b/modules/web-console/frontend/app/primitives/checkbox/index.scss @@ -0,0 +1,52 @@ +/* + * 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. + */ + +input[type='checkbox'] { + background-image: url(/images/checkbox.svg); + width: 12px !important; + height: 12px !important; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-repeat: no-repeat; + background-size: 100%; + padding: 0; + border: none; + + &:checked { + background-image: url(/images/checkbox-active.svg); + } + &:disabled { + opacity: 0.5; + } +} + +.theme--ignite { + .form-field-checkbox { + z-index: 2; + padding-left: 8px; + padding-right: 8px; + + input[type='checkbox'] { + margin-right: 8px; + vertical-align: -1px; + } + .tipLabel { + vertical-align: -3px; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/datepicker/index.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/datepicker/index.pug b/modules/web-console/frontend/app/primitives/datepicker/index.pug index e789a1f..7120111 100644 --- a/modules/web-console/frontend/app/primitives/datepicker/index.pug +++ b/modules/web-console/frontend/app/primitives/datepicker/index.pug @@ -22,10 +22,10 @@ mixin ignite-form-field-datepicker(label, model, name, mindate, maxdate, minview placeholder=placeholder - data-ng-model=model + ng-model=model - data-ng-required=required && `${required}` - data-ng-disabled=disabled && `${disabled}` + ng-required=required && `${required}` + ng-disabled=disabled && `${disabled}` bs-datepicker @@ -42,8 +42,6 @@ mixin ignite-form-field-datepicker(label, model, name, mindate, maxdate, minview tabindex='0' onkeydown='return false' - - data-ignite-form-panel-field='' )&attributes(attributes.attributes) .datepicker--ignite.ignite-form-field http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/dropdown/index.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/dropdown/index.pug b/modules/web-console/frontend/app/primitives/dropdown/index.pug index c145244..0099457 100644 --- a/modules/web-console/frontend/app/primitives/dropdown/index.pug +++ b/modules/web-console/frontend/app/primitives/dropdown/index.pug @@ -17,10 +17,10 @@ mixin ignite-form-field-bsdropdown({label, model, name, disabled, required, options, tip}) .dropdown--ignite.ignite-form-field .btn-ignite.btn-ignite--primary-outline( - data-ng-model=model + ng-model=model - data-ng-required=required && `${required}` - data-ng-disabled=disabled && `${disabled}` || `!${options}.length` + ng-required=required && `${required}` + ng-disabled=disabled && `${disabled}` || `!${options}.length` bs-dropdown='' http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/file/index.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/file/index.pug b/modules/web-console/frontend/app/primitives/file/index.pug index 4ce9ef4..7bdd3cc 100644 --- a/modules/web-console/frontend/app/primitives/file/index.pug +++ b/modules/web-console/frontend/app/primitives/file/index.pug @@ -32,6 +32,6 @@ mixin ignite-form-field-file(label, model, name, disabled, required, options, ti input( id=`{{ ${name} }}Input` type='file' - data-ng-model=model + ng-model=model )&attributes(attributes) | {{ `${model}` }} http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/form-field/index.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/form-field/index.scss b/modules/web-console/frontend/app/primitives/form-field/index.scss index e6c0a58..7d9ea1f 100644 --- a/modules/web-console/frontend/app/primitives/form-field/index.scss +++ b/modules/web-console/frontend/app/primitives/form-field/index.scss @@ -18,21 +18,35 @@ @import '../../../public/stylesheets/variables'; .theme--ignite { + [ignite-icon='info'], .tipLabel { + color: $ignite-brand-success; + } + .ignite-form-field { width: 100%; &.radio--ignite { width: auto; + } - &__label { - float: left; - width: 100%; - margin: 0 10px 4px; + &.ignite-form-field-dropdown { + .ignite-form-field__control button { + display: inline-block; + overflow: hidden !important; + text-overflow: ellipsis; + } + } - color: $gray-light; - font-size: 12px; - line-height: 12px; + [ignite-icon='info'], .tipLabel { + margin-left: 4px; + flex: 0 0 auto; + } + + + label.required { + content: '*'; + margin-left: 0.25em; } .ignite-form-field__control { @@ -75,10 +89,20 @@ &:disabled { opacity: .5; } + + + &:focus { + border-color: $ignite-brand-success; + box-shadow: inset 0 1px 3px 0 rgba($ignite-brand-success, .5); + } + + &:disabled { + opacity: .5; + } } & > input[type='number'] { - text-align: right; + text-align: left; } } @@ -87,6 +111,62 @@ } } } + .ignite-form-field__label { + float: left; + width: 100%; + margin: 0 0 2px; + padding: 0 10px; + height: 16px; + display: inline-flex; + align-items: center; + + color: $gray-light; + font-size: 12px; + line-height: 12px; + + &-disabled { + opacity: 0.5; + } + } + .ignite-form-field__errors { + color: $ignite-brand-primary; + padding: 5px 10px 0px; + line-height: 14px; + font-size: 12px; + clear: both; + + &:empty { + display: none; + } + + [ng-message] + [ng-message] { + margin-top: 10px; + } + } + @keyframes error-pulse { + from { + color: $ignite-brand-primary; + } + 50% { + color: transparent; + } + to { + color: $ignite-brand-primary; + } + } + .ignite-form-field__error-blink { + .ignite-form-field__errors { + animation-name: error-pulse; + animation-iteration-count: 2; + animation-duration: 500ms; + } + } + + .ignite-form-field.form-field-checkbox { + input[disabled] ~ * { + opacity: 0.5; + } + } } .form-field { http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/index.js b/modules/web-console/frontend/app/primitives/index.js index 5a2f45c..f9d8591 100644 --- a/modules/web-console/frontend/app/primitives/index.js +++ b/modules/web-console/frontend/app/primitives/index.js @@ -33,4 +33,5 @@ import './switcher/index.scss'; import './form-field/index.scss'; import './typography/index.scss'; import './grid/index.scss'; +import './checkbox/index.scss'; import './tooltip/index.scss'; http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/modal/index.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/modal/index.scss b/modules/web-console/frontend/app/primitives/modal/index.scss index fcf9885..802a241 100644 --- a/modules/web-console/frontend/app/primitives/modal/index.scss +++ b/modules/web-console/frontend/app/primitives/modal/index.scss @@ -63,6 +63,7 @@ .modal-body { max-height: calc(100vh - 150px); + overflow: auto; } } http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/radio/index.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/radio/index.pug b/modules/web-console/frontend/app/primitives/radio/index.pug index 2b2223a..f47fd17 100644 --- a/modules/web-console/frontend/app/primitives/radio/index.pug +++ b/modules/web-console/frontend/app/primitives/radio/index.pug @@ -26,14 +26,10 @@ mixin form-field-radio(label, model, name, value, disabled, required, tip) name=`{{ ${name} }}` type='radio' - data-ng-model=model - data-ng-value=value - data-ng-required=required && `${required}` - data-ng-disabled=disabled && `${disabled}` - - data-ng-focus='tableReset()' - - data-ignite-form-panel-field='' + ng-model=model + ng-value=value + ng-required=required && `${required}` + ng-disabled=disabled && `${disabled}` ) div span #{label} http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/tabs/index.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/tabs/index.scss b/modules/web-console/frontend/app/primitives/tabs/index.scss index 022d66b..811d847 100644 --- a/modules/web-console/frontend/app/primitives/tabs/index.scss +++ b/modules/web-console/frontend/app/primitives/tabs/index.scss @@ -23,6 +23,7 @@ ul.tabs { $offset-vertical: 11px; $offset-horizontal: 25px; $font-size: 14px; + $border-width: 5px; list-style: none; @@ -34,10 +35,13 @@ ul.tabs { li { position: relative; top: 1px; + height: $height + $border-width; display: inline-block; - border-bottom: 5px solid transparent; + border-bottom: 0px solid transparent; + transition-property: border-bottom; + transition-duration: 0.2s; a { display: inline-block; @@ -61,6 +65,10 @@ ul.tabs { } } + &.active, &:hover { + border-bottom-width: $border-width; + } + &.active { border-color: $brand-primary; } http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/timepicker/index.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/timepicker/index.pug b/modules/web-console/frontend/app/primitives/timepicker/index.pug index 9f1f6ec..54ce8c1 100644 --- a/modules/web-console/frontend/app/primitives/timepicker/index.pug +++ b/modules/web-console/frontend/app/primitives/timepicker/index.pug @@ -23,10 +23,10 @@ mixin ignite-form-field-timepicker(label, model, name, mindate, maxdate, disable placeholder=placeholder - data-ng-model=model + ng-model=model - data-ng-required=required && `${required}` - data-ng-disabled=disabled && `${disabled}` + ng-required=required && `${required}` + ng-disabled=disabled && `${disabled}` bs-timepicker data-time-format='HH:mm' @@ -40,8 +40,6 @@ mixin ignite-form-field-timepicker(label, model, name, mindate, maxdate, disable tabindex='0' onkeydown="return false" - - data-ignite-form-panel-field='' )&attributes(attributes.attributes) .timepicker--ignite.ignite-form-field http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/tooltip/index.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/tooltip/index.pug b/modules/web-console/frontend/app/primitives/tooltip/index.pug index 632fc61..ea6a344 100644 --- a/modules/web-console/frontend/app/primitives/tooltip/index.pug +++ b/modules/web-console/frontend/app/primitives/tooltip/index.pug @@ -16,7 +16,8 @@ mixin tooltip(title, options, tipClass = 'tipField') if title - i.icon-help( + svg( + ignite-icon='info' bs-tooltip='' data-title=title http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/primitives/ui-grid/index.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/ui-grid/index.scss b/modules/web-console/frontend/app/primitives/ui-grid/index.scss index 25ba390..2ffffff 100644 --- a/modules/web-console/frontend/app/primitives/ui-grid/index.scss +++ b/modules/web-console/frontend/app/primitives/ui-grid/index.scss @@ -40,7 +40,7 @@ } .ui-grid-cell { - height: $height; + height: $height - 1px; border-color: transparent; } @@ -78,6 +78,10 @@ } } + .ui-grid-row:last-child .ui-grid-cell { + border-bottom-width: 0; + } + .ui-grid-header-viewport { .ui-grid-header-canvas { .ui-grid-header-cell { @@ -239,6 +243,7 @@ .ui-grid-row { height: $height; + border-bottom: 1px solid $table-border-color; &:nth-child(odd) { .ui-grid-cell { @@ -252,10 +257,6 @@ } } - &:not(:first-child) { - border-top: 1px solid $table-border-color; - } - &.ui-grid-row-selected > [ui-grid-row] > .ui-grid-cell { background-color: #e5f2f9; http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/Caches.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/Caches.js b/modules/web-console/frontend/app/services/Caches.js index 938094e..add63f8 100644 --- a/modules/web-console/frontend/app/services/Caches.js +++ b/modules/web-console/frontend/app/services/Caches.js @@ -15,14 +15,218 @@ * limitations under the License. */ +import ObjectID from 'bson-objectid'; +import omit from 'lodash/fp/omit'; + export default class Caches { static $inject = ['$http']; + /** @type {ig.menu<ig.config.cache.CacheModes>} */ + cacheModes = [ + {value: 'LOCAL', label: 'LOCAL'}, + {value: 'REPLICATED', label: 'REPLICATED'}, + {value: 'PARTITIONED', label: 'PARTITIONED'} + ]; + + /** @type {ig.menu<ig.config.cache.AtomicityModes>} */ + atomicityModes = [ + {value: 'ATOMIC', label: 'ATOMIC'}, + {value: 'TRANSACTIONAL', label: 'TRANSACTIONAL'} + ]; + + /** + * @param {ng.IHttpService} $http + */ constructor($http) { - Object.assign(this, {$http}); + this.$http = $http; } saveCache(cache) { return this.$http.post('/api/v1/configuration/caches/save', cache); } + + /** + * @param {string} cacheID + */ + getCache(cacheID) { + return this.$http.get(`/api/v1/configuration/caches/${cacheID}`); + } + + /** + * @param {string} cacheID + */ + removeCache(cacheID) { + return this.$http.post(`/api/v1/configuration/caches/remove/${cacheID}`); + } + + getBlankCache() { + return { + _id: ObjectID.generate(), + evictionPolicy: {}, + cacheMode: 'PARTITIONED', + atomicityMode: 'ATOMIC', + readFromBackup: true, + copyOnRead: true, + cacheStoreFactory: { + CacheJdbcBlobStoreFactory: { + connectVia: 'DataSource' + }, + CacheHibernateBlobStoreFactory: { + hibernateProperties: [] + } + }, + writeBehindCoalescing: true, + nearConfiguration: {}, + sqlFunctionClasses: [], + domains: [] + }; + } + + /** + * @param {object} cache + * @returns {ig.config.cache.ShortCache} + */ + toShortCache(cache) { + return { + _id: cache._id, + name: cache.name, + backups: cache.backups, + cacheMode: cache.cacheMode, + atomicityMode: cache.atomicityMode + }; + } + + normalize = omit(['__v', 'space', 'clusters']); + + nodeFilterKinds = [ + {value: 'IGFS', label: 'IGFS nodes'}, + {value: 'Custom', label: 'Custom'}, + {value: null, label: 'Not set'} + ]; + + memoryModes = [ + {value: 'ONHEAP_TIERED', label: 'ONHEAP_TIERED'}, + {value: 'OFFHEAP_TIERED', label: 'OFFHEAP_TIERED'}, + {value: 'OFFHEAP_VALUES', label: 'OFFHEAP_VALUES'} + ]; + + offHeapMode = { + _val(cache) { + return (cache.offHeapMode === null || cache.offHeapMode === void 0) ? -1 : cache.offHeapMode; + }, + onChange: (cache) => { + const offHeapMode = this.offHeapMode._val(cache); + switch (offHeapMode) { + case 1: + return cache.offHeapMaxMemory = cache.offHeapMaxMemory > 0 ? cache.offHeapMaxMemory : null; + case 0: + case -1: + return cache.offHeapMaxMemory = cache.offHeapMode; + default: break; + } + }, + required: (cache) => cache.memoryMode === 'OFFHEAP_TIERED', + offheapDisabled: (cache) => !(cache.memoryMode === 'OFFHEAP_TIERED' && this.offHeapMode._val(cache) === -1), + default: 'Disabled' + }; + + offHeapModes = [ + {value: -1, label: 'Disabled'}, + {value: 1, label: 'Limited'}, + {value: 0, label: 'Unlimited'} + ]; + + offHeapMaxMemory = { + min: 1 + }; + + memoryMode = { + default: 'ONHEAP_TIERED', + offheapAndDomains: (cache) => { + return !(cache.memoryMode === 'OFFHEAP_VALUES' && cache.domains.length); + } + }; + + evictionPolicy = { + required: (cache) => { + return (cache.memoryMode || this.memoryMode.default) === 'ONHEAP_TIERED' + && cache.offHeapMaxMemory > 0 + && !cache.evictionPolicy.kind; + }, + values: [ + {value: 'LRU', label: 'LRU'}, + {value: 'FIFO', label: 'FIFO'}, + {value: 'SORTED', label: 'Sorted'}, + {value: null, label: 'Not set'} + ], + kind: { + default: 'Not set' + }, + maxMemorySize: { + min: (evictionPolicy) => { + const policy = evictionPolicy[evictionPolicy.kind]; + if (!policy) return true; + const maxSize = policy.maxSize === null || policy.maxSize === void 0 + ? this.evictionPolicy.maxSize.default + : policy.maxSize; + + return maxSize ? 0 : 1; + }, + default: 0 + }, + maxSize: { + min: (evictionPolicy) => { + const policy = evictionPolicy[evictionPolicy.kind]; + if (!policy) return true; + const maxMemorySize = policy.maxMemorySize === null || policy.maxMemorySize === void 0 + ? this.evictionPolicy.maxMemorySize.default + : policy.maxMemorySize; + + return maxMemorySize ? 0 : 1; + }, + default: 100000 + } + }; + + cacheStoreFactory = { + kind: { + default: 'Not set' + }, + values: [ + {value: 'CacheJdbcPojoStoreFactory', label: 'JDBC POJO store factory'}, + {value: 'CacheJdbcBlobStoreFactory', label: 'JDBC BLOB store factory'}, + {value: 'CacheHibernateBlobStoreFactory', label: 'Hibernate BLOB store factory'}, + {value: null, label: 'Not set'} + ], + storeDisabledValueOff: (cache, value) => { + return cache && cache.cacheStoreFactory.kind ? true : !value; + }, + storeEnabledReadOrWriteOn: (cache) => { + return cache && cache.cacheStoreFactory.kind ? (cache.readThrough || cache.writeThrough) : true; + } + }; + + writeBehindFlush = { + min: (cache) => { + return cache.writeBehindFlushSize === 0 && cache.writeBehindFlushFrequency === 0 + ? 1 + : 0; + } + }; + + /** + * @param {ig.config.cache.ShortCache} cache + */ + getCacheBackupsCount(cache) { + return this.shouldShowCacheBackupsCount(cache) + ? (cache.backups || 0) + : void 0; + } + + /** + * @param {ig.config.cache.ShortCache} cache + */ + shouldShowCacheBackupsCount(cache) { + return cache && cache.cacheMode === 'PARTITIONED'; + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/Clusters.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/Clusters.js b/modules/web-console/frontend/app/services/Clusters.js index dd2f598..4e057fc 100644 --- a/modules/web-console/frontend/app/services/Clusters.js +++ b/modules/web-console/frontend/app/services/Clusters.js @@ -15,9 +15,21 @@ * limitations under the License. */ +import get from 'lodash/get'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/observable/fromPromise'; +import ObjectID from 'bson-objectid/objectid'; +import {uniqueName} from 'app/utils/uniqueName'; +import omit from 'lodash/fp/omit'; + +const uniqueNameValidator = (defaultName = '') => (a, items = []) => { + return a && !items.some((b) => b._id !== a._id && (a.name || defaultName) === (b.name || defaultName)); +}; + export default class Clusters { static $inject = ['$http']; + /** @type {ig.menu<ig.config.cluster.DiscoveryKinds>}>} */ discoveries = [ {value: 'Vm', label: 'Static IPs'}, {value: 'Multicast', label: 'Multicast'}, @@ -30,19 +42,109 @@ export default class Clusters { {value: 'Kubernetes', label: 'Kubernetes'} ]; - // In bytes - minMemoryPolicySize = 10485760; + minMemoryPolicySize = 10485760; // In bytes + ackSendThreshold = { + min: 1, + default: 16 + }; + messageQueueLimit = { + min: 0, + default: 1024 + }; + unacknowledgedMessagesBufferSize = { + min: ( + currentValue = this.unacknowledgedMessagesBufferSize.default, + messageQueueLimit = this.messageQueueLimit.default, + ackSendThreshold = this.ackSendThreshold.default + ) => { + if (currentValue === this.unacknowledgedMessagesBufferSize.default) return currentValue; + const {validRatio} = this.unacknowledgedMessagesBufferSize; + return Math.max(messageQueueLimit * validRatio, ackSendThreshold * validRatio); + }, + validRatio: 5, + default: 0 + }; + sharedMemoryPort = { + default: 48100, + min: -1, + max: 65535, + invalidValues: [0] + }; + /** + * Cluster-related configuration stuff + * @param {ng.IHttpService} $http + */ constructor($http) { - Object.assign(this, {$http}); + this.$http = $http; + } + + getConfiguration(clusterID) { + return this.$http.get(`/api/v1/configuration/${clusterID}`); + } + + getAllConfigurations() { + return this.$http.get('/api/v1/configuration/list'); + } + + getCluster(clusterID) { + return this.$http.get(`/api/v1/configuration/clusters/${clusterID}`); + } + + getClusterCaches(clusterID) { + return this.$http.get(`/api/v1/configuration/clusters/${clusterID}/caches`); + } + + /** + * @param {string} clusterID + * @returns {ng.IPromise<ng.IHttpResponse<{data: Array<ig.config.model.ShortDomainModel>}>>} + */ + getClusterModels(clusterID) { + return this.$http.get(`/api/v1/configuration/clusters/${clusterID}/models`); + } + + getClusterIGFSs(clusterID) { + return this.$http.get(`/api/v1/configuration/clusters/${clusterID}/igfss`); + } + + /** + * @returns {ng.IPromise<ng.IHttpResponse<{data: Array<ig.config.cluster.ShortCluster>}>>} + */ + getClustersOverview() { + return this.$http.get('/api/v1/configuration/clusters/'); + } + + getClustersOverview$() { + return Observable.fromPromise(this.getClustersOverview()); } saveCluster(cluster) { return this.$http.post('/api/v1/configuration/clusters/save', cluster); } + saveCluster$(cluster) { + return Observable.fromPromise(this.saveCluster(cluster)); + } + + removeCluster(cluster) { + return this.$http.post('/api/v1/configuration/clusters/remove', {_id: cluster}); + } + + removeCluster$(cluster) { + return Observable.fromPromise(this.removeCluster(cluster)); + } + + saveBasic(changedItems) { + return this.$http.put('/api/v1/configuration/clusters/basic', changedItems); + } + + saveAdvanced(changedItems) { + return this.$http.put('/api/v1/configuration/clusters/', changedItems); + } + getBlankCluster() { return { + _id: ObjectID.generate(), activeOnStart: true, cacheSanityCheckEnabled: true, atomicConfiguration: {}, @@ -61,12 +163,15 @@ export default class Clusters { swapSpaceSpi: {}, transactionConfiguration: {}, dataStorageConfiguration: { + pageSize: null, + concurrencyLevel: null, defaultDataRegionConfiguration: { name: 'default' }, dataRegionConfigurations: [] }, memoryConfiguration: { + pageSize: null, memoryPolicies: [{ name: 'default', maxSize: null @@ -80,6 +185,9 @@ export default class Clusters { sqlConnectorConfiguration: { tcpNoDelay: true }, + clientConnectorConfiguration: { + tcpNoDelay: true + }, space: void 0, discovery: { kind: 'Multicast', @@ -95,7 +203,374 @@ export default class Clusters { failoverSpi: [], logger: {Log4j: { mode: 'Default'}}, caches: [], - igfss: [] + igfss: [], + models: [], + checkpointSpi: [], + loadBalancingSpi: [] }; } + + /** @type {ig.menu<ig.config.cluster.FailoverSPIs>} */ + failoverSpis = [ + {value: 'JobStealing', label: 'Job stealing'}, + {value: 'Never', label: 'Never'}, + {value: 'Always', label: 'Always'}, + {value: 'Custom', label: 'Custom'} + ]; + + toShortCluster(cluster) { + return { + _id: cluster._id, + name: cluster.name, + discovery: cluster.discovery.kind, + cachesCount: (cluster.caches || []).length, + modelsCount: (cluster.models || []).length, + igfsCount: (cluster.igfss || []).length + }; + } + + requiresProprietaryDrivers(cluster) { + return get(cluster, 'discovery.kind') === 'Jdbc' && ['Oracle', 'DB2', 'SQLServer'].includes(get(cluster, 'discovery.Jdbc.dialect')); + } + + JDBCDriverURL(cluster) { + return ({ + Oracle: 'http://www.oracle.com/technetwork/database/features/jdbc/default-2280470.html', + DB2: 'http://www-01.ibm.com/support/docview.wss?uid=swg21363866', + SQLServer: 'https://www.microsoft.com/en-us/download/details.aspx?id=11774' + })[get(cluster, 'discovery.Jdbc.dialect')]; + } + + dataRegion = { + name: { + default: 'default', + invalidValues: ['sysMemPlc'] + }, + initialSize: { + default: 268435456, + min: 10485760 + }, + maxSize: { + default: '0.2 * totalMemoryAvailable', + min: (dataRegion) => { + if (!dataRegion) return; + return dataRegion.initialSize || this.dataRegion.initialSize.default; + } + }, + evictionThreshold: { + step: 0.05, + max: 0.999, + min: 0.5, + default: 0.9 + }, + emptyPagesPoolSize: { + default: 100, + min: 11, + max: (cluster, dataRegion) => { + if (!cluster || !dataRegion || !dataRegion.maxSize) return; + const perThreadLimit = 10; // Took from Ignite + const maxSize = dataRegion.maxSize; + const pageSize = cluster.dataStorageConfiguration.pageSize || this.dataStorageConfiguration.pageSize.default; + const maxPoolSize = Math.floor(maxSize / pageSize / perThreadLimit); + return maxPoolSize; + } + }, + subIntervals: { + default: 5, + min: 1, + step: 1 + }, + rateTimeInterval: { + min: 1000, + default: 60000, + step: 1000 + } + }; + + makeBlankDataRegionConfiguration() { + return {_id: ObjectID.generate()}; + } + + addDataRegionConfiguration(cluster) { + const dataRegionConfigurations = get(cluster, 'dataStorageConfiguration.dataRegionConfigurations'); + if (!dataRegionConfigurations) return; + return dataRegionConfigurations.push(Object.assign(this.makeBlankDataRegionConfiguration(), { + name: uniqueName('New data region', dataRegionConfigurations.concat(cluster.dataStorageConfiguration.defaultDataRegionConfiguration)) + })); + } + + memoryPolicy = { + name: { + default: 'default', + invalidValues: ['sysMemPlc'] + }, + initialSize: { + default: 268435456, + min: 10485760 + }, + maxSize: { + default: '0.8 * totalMemoryAvailable', + min: (memoryPolicy) => { + return memoryPolicy.initialSize || this.memoryPolicy.initialSize.default; + } + }, + customValidators: { + defaultMemoryPolicyExists: (name, items = []) => { + const def = this.memoryPolicy.name.default; + const normalizedName = (name || def); + if (normalizedName === def) return true; + return items.some((policy) => (policy.name || def) === normalizedName); + }, + uniqueMemoryPolicyName: (a, items = []) => { + const def = this.memoryPolicy.name.default; + return !items.some((b) => b._id !== a._id && (a.name || def) === (b.name || def)); + } + }, + emptyPagesPoolSize: { + default: 100, + min: 11, + max: (cluster, memoryPolicy) => { + if (!memoryPolicy || !memoryPolicy.maxSize) return; + const perThreadLimit = 10; // Took from Ignite + const maxSize = memoryPolicy.maxSize; + const pageSize = cluster.memoryConfiguration.pageSize || this.memoryConfiguration.pageSize.default; + const maxPoolSize = Math.floor(maxSize / pageSize / perThreadLimit); + return maxPoolSize; + } + } + }; + + getDefaultClusterMemoryPolicy(cluster) { + const def = this.memoryPolicy.name.default; + const normalizedName = get(cluster, 'memoryConfiguration.defaultMemoryPolicyName') || def; + return get(cluster, 'memoryConfiguration.memoryPolicies', []).find((p) => { + return (p.name || def) === normalizedName; + }); + } + + makeBlankCheckpointSPI() { + return { + FS: { + directoryPaths: [] + }, + S3: { + awsCredentials: { + kind: 'Basic' + }, + clientConfiguration: { + retryPolicy: { + kind: 'Default' + }, + useReaper: true + } + } + }; + } + + addCheckpointSPI(cluster) { + const item = this.makeBlankCheckpointSPI(); + cluster.checkpointSpi.push(item); + return item; + } + + makeBlankLoadBalancingSpi() { + return { + Adaptive: { + loadProbe: { + Job: {useAverage: true}, + CPU: { + useAverage: true, + useProcessors: true + }, + ProcessingTime: {useAverage: true} + } + } + }; + } + + addLoadBalancingSpi(cluster) { + return cluster.loadBalancingSpi.push(this.makeBlankLoadBalancingSpi()); + } + + /** @type {ig.menu<ig.config.cluster.LoadBalancingKinds>} */ + loadBalancingKinds = [ + {value: 'RoundRobin', label: 'Round-robin'}, + {value: 'Adaptive', label: 'Adaptive'}, + {value: 'WeightedRandom', label: 'Random'}, + {value: 'Custom', label: 'Custom'} + ]; + + makeBlankMemoryPolicy() { + return {_id: ObjectID.generate()}; + } + + addMemoryPolicy(cluster) { + const memoryPolicies = get(cluster, 'memoryConfiguration.memoryPolicies'); + if (!memoryPolicies) return; + return memoryPolicies.push(Object.assign(this.makeBlankMemoryPolicy(), { + // Blank name for default policy if there are not other policies + name: memoryPolicies.length ? uniqueName('New memory policy', memoryPolicies) : '' + })); + } + + // For versions 2.1-2.2, use dataStorageConfiguration since 2.3 + memoryConfiguration = { + pageSize: { + default: 1024 * 2, + values: [ + {value: null, label: 'Default (2kb)'}, + {value: 1024 * 1, label: '1 kb'}, + {value: 1024 * 2, label: '2 kb'}, + {value: 1024 * 4, label: '4 kb'}, + {value: 1024 * 8, label: '8 kb'}, + {value: 1024 * 16, label: '16 kb'} + ] + }, + systemCacheInitialSize: { + default: 41943040, + min: 10485760 + }, + systemCacheMaxSize: { + default: 104857600, + min: (cluster) => { + return get(cluster, 'memoryConfiguration.systemCacheInitialSize') || this.memoryConfiguration.systemCacheInitialSize.default; + } + } + }; + + // Added in 2.3 + dataStorageConfiguration = { + pageSize: { + default: 1024 * 4, + values: [ + {value: null, label: 'Default (4kb)'}, + {value: 1024 * 1, label: '1 kb'}, + {value: 1024 * 2, label: '2 kb'}, + {value: 1024 * 4, label: '4 kb'}, + {value: 1024 * 8, label: '8 kb'}, + {value: 1024 * 16, label: '16 kb'} + ] + }, + systemRegionInitialSize: { + default: 41943040, + min: 10485760 + }, + systemRegionMaxSize: { + default: 104857600, + min: (cluster) => { + return get(cluster, 'dataStorageConfiguration.systemRegionInitialSize') || this.dataStorageConfiguration.systemRegionInitialSize.default; + } + } + }; + + swapSpaceSpi = { + readStripesNumber: { + default: 'availableProcessors', + customValidators: { + powerOfTwo: (value) => { + return !value || ((value & -value) === value); + } + } + } + }; + + makeBlankServiceConfiguration() { + return {_id: ObjectID.generate()}; + } + + addServiceConfiguration(cluster) { + if (!cluster.serviceConfigurations) cluster.serviceConfigurations = []; + cluster.serviceConfigurations.push(Object.assign(this.makeBlankServiceConfiguration(), { + name: uniqueName('New service configuration', cluster.serviceConfigurations) + })); + } + + serviceConfigurations = { + serviceConfiguration: { + name: { + customValidators: { + uniqueName: uniqueNameValidator('') + } + } + } + }; + + systemThreadPoolSize = { + default: 'max(8, availableProcessors) * 2', + min: 2 + }; + + rebalanceThreadPoolSize = { + default: 1, + min: 1, + max: (cluster) => { + return cluster.systemThreadPoolSize ? cluster.systemThreadPoolSize - 1 : void 0; + } + }; + + addExecutorConfiguration(cluster) { + if (!cluster.executorConfiguration) cluster.executorConfiguration = []; + const item = {_id: ObjectID.generate(), name: ''}; + cluster.executorConfiguration.push(item); + return item; + } + + executorConfigurations = { + allNamesExist: (executorConfigurations = []) => { + return executorConfigurations.every((ec) => ec && ec.name); + }, + allNamesUnique: (executorConfigurations = []) => { + const uniqueNames = new Set(executorConfigurations.map((ec) => ec.name)); + return uniqueNames.size === executorConfigurations.length; + } + }; + + executorConfiguration = { + name: { + customValidators: { + uniqueName: uniqueNameValidator() + } + } + }; + + marshaller = { + kind: { + default: 'BinaryMarshaller' + } + }; + + odbc = { + odbcEnabled: { + correctMarshaller: (cluster, odbcEnabled) => { + const marshallerKind = get(cluster, 'marshaller.kind') || this.marshaller.kind.default; + return !odbcEnabled || marshallerKind === this.marshaller.kind.default; + }, + correctMarshallerWatch: (root) => `${root}.marshaller.kind` + } + }; + + swapSpaceSpis = [ + {value: 'FileSwapSpaceSpi', label: 'File-based swap'}, + {value: null, label: 'Not set'} + ]; + + affinityFunctions = [ + {value: 'Rendezvous', label: 'Rendezvous'}, + {value: 'Custom', label: 'Custom'}, + {value: null, label: 'Default'} + ]; + + normalize = omit(['__v', 'space']); + + addPeerClassLoadingLocalClassPathExclude(cluster) { + if (!cluster.peerClassLoadingLocalClassPathExclude) cluster.peerClassLoadingLocalClassPathExclude = []; + return cluster.peerClassLoadingLocalClassPathExclude.push(''); + } + + addBinaryTypeConfiguration(cluster) { + if (!cluster.binaryConfiguration.typeConfigurations) cluster.binaryConfiguration.typeConfigurations = []; + const item = {_id: ObjectID.generate()}; + cluster.binaryConfiguration.typeConfigurations.push(item); + return item; + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/Confirm.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/Confirm.service.js b/modules/web-console/frontend/app/services/Confirm.service.js index 6fe7ab8..c2eaf35 100644 --- a/modules/web-console/frontend/app/services/Confirm.service.js +++ b/modules/web-console/frontend/app/services/Confirm.service.js @@ -18,6 +18,44 @@ import templateUrl from 'views/templates/confirm.tpl.pug'; import {CancellationError} from 'app/errors/CancellationError'; +export class Confirm { + static $inject = ['$modal', '$q']; + /** + * @param {mgcrea.ngStrap.modal.IModalService} $modal + * @param {ng.IQService} $q + */ + constructor($modal, $q) { + this.$modal = $modal; + this.$q = $q; + } + /** + * @param {string} content - Confirmation text/html content + * @param {boolean} yesNo - Show "Yes/No" buttons instead of "Config" + * @return {ng.IPromise} + */ + confirm(content = 'Confirm?', yesNo = false) { + return this.$q((resolve, reject) => { + this.$modal({ + templateUrl, + backdrop: true, + onBeforeHide: () => reject(new CancellationError()), + controller: ['$scope', ($scope) => { + $scope.yesNo = yesNo; + $scope.content = content; + $scope.confirmCancel = $scope.confirmNo = () => { + reject(new CancellationError()); + $scope.$hide(); + }; + $scope.confirmYes = () => { + resolve(); + $scope.$hide(); + }; + }] + }); + }); + } +} + // Confirm popup service. export default ['IgniteConfirm', ['$rootScope', '$q', '$modal', '$animate', ($root, $q, $modal, $animate) => { const scope = $root.$new(); http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/ConfirmBatch.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/ConfirmBatch.service.js b/modules/web-console/frontend/app/services/ConfirmBatch.service.js index 2739f29..5c6961c 100644 --- a/modules/web-console/frontend/app/services/ConfirmBatch.service.js +++ b/modules/web-console/frontend/app/services/ConfirmBatch.service.js @@ -19,65 +19,72 @@ import templateUrl from 'views/templates/batch-confirm.tpl.pug'; import {CancellationError} from 'app/errors/CancellationError'; // Service for confirm or skip several steps. -export default ['IgniteConfirmBatch', ['$rootScope', '$q', '$modal', ($root, $q, $modal) => { - const scope = $root.$new(); - - scope.confirmModal = $modal({ - templateUrl, - scope, - show: false, - backdrop: 'static', - keyboard: false - }); - - const _done = (cancel) => { - scope.confirmModal.hide(); - - if (cancel) - scope.deferred.reject(new CancellationError()); - 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 { +export default class IgniteConfirmBatch { + static $inject = ['$rootScope', '$q', '$modal']; + + /** + * @param {ng.IRootScopeService} $root + * @param {ng.IQService} $q + * @param {mgcrea.ngStrap.modal.IModalService} $modal + */ + constructor($root, $q, $modal) { + const scope = $root.$new(); + + scope.confirmModal = $modal({ + templateUrl, + scope, + show: false, + backdrop: 'static', + keyboard: false + }); + + const _done = (cancel) => { + scope.confirmModal.hide(); + + if (cancel) + scope.deferred.reject(new CancellationError()); + 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); + }; + /** * Show confirm all dialog. - * - * @param confirmMessageFn Function to generate a confirm message. - * @param itemsToConfirm Array of element to process by confirm. + * @template T + * @param {(T) => string} confirmMessageFn Function to generate a confirm message. + * @param {Array<T>} [itemsToConfirm] Array of element to process by confirm. */ - confirm(confirmMessageFn, itemsToConfirm) { + this.confirm = function confirm(confirmMessageFn, itemsToConfirm) { scope.deferred = $q.defer(); scope.contentGenerator = confirmMessageFn; @@ -89,6 +96,6 @@ export default ['IgniteConfirmBatch', ['$rootScope', '$q', '$modal', ($root, $q, scope.confirmModal.$promise.then(scope.confirmModal.show); return scope.deferred.promise; - } - }; -}]]; + }; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/ErrorPopover.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/ErrorPopover.service.js b/modules/web-console/frontend/app/services/ErrorPopover.service.js index 5132d50..bddf436 100644 --- a/modules/web-console/frontend/app/services/ErrorPopover.service.js +++ b/modules/web-console/frontend/app/services/ErrorPopover.service.js @@ -19,19 +19,17 @@ * Service to show/hide error popover. */ export default class ErrorPopover { - static $inject = ['$popover', '$anchorScroll', '$location', '$timeout', 'IgniteFormUtils']; + static $inject = ['$popover', '$anchorScroll', '$timeout', 'IgniteFormUtils']; /** * @param $popover * @param $anchorScroll - * @param $location * @param $timeout * @param FormUtils */ - constructor($popover, $anchorScroll, $location, $timeout, FormUtils) { + constructor($popover, $anchorScroll, $timeout, FormUtils) { this.$popover = $popover; this.$anchorScroll = $anchorScroll; - this.$location = $location; this.$timeout = $timeout; this.FormUtils = FormUtils; @@ -73,11 +71,9 @@ export default class ErrorPopover { el = body.find('[name="' + id + '"]'); if (el && el.length > 0) { - if (!ErrorPopover._isElementInViewport(el[0])) { - this.$location.hash(el[0].id); + if (!ErrorPopover._isElementInViewport(el[0])) + el[0].scrollIntoView(); - this.$anchorScroll(); - } const newPopover = this.$popover(el, {content: message}); http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/FormUtils.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/FormUtils.service.js b/modules/web-console/frontend/app/services/FormUtils.service.js index 6ccc3c6..f22d4bc 100644 --- a/modules/web-console/frontend/app/services/FormUtils.service.js +++ b/modules/web-console/frontend/app/services/FormUtils.service.js @@ -18,7 +18,7 @@ export default ['IgniteFormUtils', ['$window', 'IgniteFocus', ($window, Focus) => { function ensureActivePanel(ui, pnl, focusId) { if (ui && ui.loadPanel) { - const collapses = $('div.panel-collapse'); + const collapses = $('[bs-collapse-target]'); ui.loadPanel(pnl); @@ -324,6 +324,22 @@ export default ['IgniteFormUtils', ['$window', 'IgniteFocus', ($window, Focus) = return width | 0; } + // TODO: move somewhere else + function triggerValidation(form, $scope) { + const fe = (m) => Object.keys(m.$error)[0]; + const em = (e) => (m) => { + if (!e) return; + const walk = (m) => { + if (!m.$error[e]) return; + if (m.$error[e] === true) return m; + return walk(m.$error[e][0]); + }; + return walk(m); + }; + + $scope.$broadcast('$showValidationError', em(fe(form))(form)); + } + return { /** * Cut class name by width in pixel or width in symbol count. @@ -434,6 +450,7 @@ export default ['IgniteFormUtils', ['$window', 'IgniteFocus', ($window, Focus) = markPristineInvalidAsDirty(ngModelCtrl) { if (ngModelCtrl && ngModelCtrl.$invalid && ngModelCtrl.$pristine) ngModelCtrl.$setDirty(); - } + }, + triggerValidation }; }]]; http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/IGFSs.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/IGFSs.js b/modules/web-console/frontend/app/services/IGFSs.js new file mode 100644 index 0000000..87dfd17 --- /dev/null +++ b/modules/web-console/frontend/app/services/IGFSs.js @@ -0,0 +1,77 @@ +/* + * 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 ObjectID from 'bson-objectid'; +import omit from 'lodash/fp/omit'; +import get from 'lodash/get'; + +export default class IGFSs { + static $inject = ['$http']; + + igfsModes = [ + {value: 'PRIMARY', label: 'PRIMARY'}, + {value: 'PROXY', label: 'PROXY'}, + {value: 'DUAL_SYNC', label: 'DUAL_SYNC'}, + {value: 'DUAL_ASYNC', label: 'DUAL_ASYNC'} + ]; + + constructor($http) { + Object.assign(this, {$http}); + } + + getIGFS(igfsID) { + return this.$http.get(`/api/v1/configuration/igfs/${igfsID}`); + } + + getBlankIGFS() { + return { + _id: ObjectID.generate(), + ipcEndpointEnabled: true, + fragmentizerEnabled: true, + colocateMetadata: true, + relaxedConsistency: true + }; + } + + affinnityGroupSize = { + default: 512, + min: 1 + }; + + defaultMode = { + values: [ + {value: 'PRIMARY', label: 'PRIMARY'}, + {value: 'PROXY', label: 'PROXY'}, + {value: 'DUAL_SYNC', label: 'DUAL_SYNC'}, + {value: 'DUAL_ASYNC', label: 'DUAL_ASYNC'} + ], + default: 'DUAL_ASYNC' + }; + + secondaryFileSystemEnabled = { + requiredWhenIGFSProxyMode: (igfs) => { + if (get(igfs, 'defaultMode') === 'PROXY') return get(igfs, 'secondaryFileSystemEnabled') === true; + return true; + }, + requiredWhenPathModeProxyMode: (igfs) => { + if (get(igfs, 'pathModes', []).some((pm) => pm.mode === 'PROXY')) return get(igfs, 'secondaryFileSystemEnabled') === true; + return true; + } + }; + + normalize = omit(['__v', 'space', 'clusters']); +} http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/JavaTypes.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/JavaTypes.service.js b/modules/web-console/frontend/app/services/JavaTypes.service.js index dff73a4..0e58b8d 100644 --- a/modules/web-console/frontend/app/services/JavaTypes.service.js +++ b/modules/web-console/frontend/app/services/JavaTypes.service.js @@ -15,6 +15,15 @@ * limitations under the License. */ +import merge from 'lodash/merge'; +import uniq from 'lodash/uniq'; +import map from 'lodash/map'; +import reduce from 'lodash/reduce'; +import isObject from 'lodash/isObject'; +import includes from 'lodash/includes'; +import isNil from 'lodash/isNil'; +import find from 'lodash/find'; + // Java built-in class names. import JAVA_CLASSES from '../data/java-classes.json'; @@ -46,8 +55,8 @@ export default class JavaTypes { static $inject = ['IgniteClusterDefaults', 'IgniteCacheDefaults', 'IgniteIGFSDefaults']; constructor(clusterDflts, cacheDflts, igfsDflts) { - this.enumClasses = _.uniq(this._enumClassesAcc(_.merge(clusterDflts, cacheDflts, igfsDflts), [])); - this.shortEnumClasses = _.map(this.enumClasses, (cls) => this.shortClassName(cls)); + this.enumClasses = uniq(this._enumClassesAcc(merge(clusterDflts, cacheDflts, igfsDflts), [])); + this.shortEnumClasses = map(this.enumClasses, (cls) => this.shortClassName(cls)); JAVA_CLASS_STRINGS.push({short: 'byte[]', full: 'byte[]', stringValue: '[B'}); } @@ -61,10 +70,10 @@ export default class JavaTypes { * @private */ _enumClassesAcc(root, classes) { - return _.reduce(root, (acc, val, key) => { + return reduce(root, (acc, val, key) => { if (key === 'clsName') acc.push(val); - else if (_.isObject(val)) + else if (isObject(val)) this._enumClassesAcc(val, acc); return acc; @@ -78,7 +87,7 @@ export default class JavaTypes { * @return {boolean} */ nonEnum(clsName) { - return !_.includes(this.shortEnumClasses, clsName) && !_.includes(this.enumClasses, clsName); + return !includes(this.shortEnumClasses, clsName) && !includes(this.enumClasses, clsName); } /** @@ -86,7 +95,7 @@ export default class JavaTypes { * @returns {boolean} 'true' if provided class name is a not Java built in class. */ nonBuiltInClass(clsName) { - return _.isNil(_.find(JAVA_CLASSES, (clazz) => clsName === clazz.short || clsName === clazz.full)); + return isNil(find(JAVA_CLASSES, (clazz) => clsName === clazz.short || clsName === clazz.full)); } /** @@ -94,7 +103,7 @@ export default class JavaTypes { * @returns {String} Full class name for java build-in types or source class otherwise. */ fullClassName(clsName) { - const type = _.find(JAVA_CLASSES, (clazz) => clsName === clazz.short); + const type = find(JAVA_CLASSES, (clazz) => clsName === clazz.short); return type ? type.full : clsName; } @@ -166,7 +175,7 @@ export default class JavaTypes { * @returns {boolean} 'true' if given value is one of Java reserved keywords. */ isKeyword(value) { - return !!(value && _.includes(JAVA_KEYWORDS, value.toLowerCase())); + return !!(value && includes(JAVA_KEYWORDS, value.toLowerCase())); } /** @@ -174,7 +183,7 @@ export default class JavaTypes { * @returns {boolean} 'true' if given class name is java primitive. */ isPrimitive(clsName) { - return _.includes(JAVA_PRIMITIVES, clsName); + return includes(JAVA_PRIMITIVES, clsName); } /** http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/LegacyUtils.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/LegacyUtils.service.js b/modules/web-console/frontend/app/services/LegacyUtils.service.js index b19bde3..8f283c0 100644 --- a/modules/web-console/frontend/app/services/LegacyUtils.service.js +++ b/modules/web-console/frontend/app/services/LegacyUtils.service.js @@ -295,6 +295,8 @@ export default ['IgniteLegacyUtils', ['IgniteErrorPopover', (ErrorPopover) => { } return { + VALID_JAVA_IDENTIFIER, + JAVA_KEYWORDS, mkOptions(options) { return _.map(options, (option) => { return {value: option, label: isDefined(option) ? option : 'Not set'}; http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/Messages.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/Messages.service.js b/modules/web-console/frontend/app/services/Messages.service.js index 39ffd3c..620d372 100644 --- a/modules/web-console/frontend/app/services/Messages.service.js +++ b/modules/web-console/frontend/app/services/Messages.service.js @@ -16,6 +16,8 @@ */ import {CancellationError} from 'app/errors/CancellationError'; +import isEmpty from 'lodash/isEmpty'; +import {nonEmpty} from 'app/utils/lodashMixins'; // Service to show various information and error messages. export default ['IgniteMessages', ['$alert', ($alert) => { @@ -37,8 +39,8 @@ export default ['IgniteMessages', ['$alert', ($alert) => { return prefix + (errIndex >= 0 ? msg.substring(errIndex + 5, msg.length - 1) : msg); } - if (_.nonEmpty(err.className)) { - if (_.isEmpty(prefix)) + if (nonEmpty(err.className)) { + if (isEmpty(prefix)) prefix = 'Internal cluster error: '; return prefix + err.className; http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/Models.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/Models.js b/modules/web-console/frontend/app/services/Models.js new file mode 100644 index 0000000..3b714c4 --- /dev/null +++ b/modules/web-console/frontend/app/services/Models.js @@ -0,0 +1,181 @@ +/* + * 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 ObjectID from 'bson-objectid'; +import omit from 'lodash/fp/omit'; + +export default class Models { + static $inject = ['$http']; + + /** + * @param {ng.IHttpService} $http + */ + constructor($http) { + this.$http = $http; + } + + /** + * @param {string} modelID + * @returns {ng.IPromise<ng.IHttpResponse<{data: ig.config.model.DomainModel}>>} + */ + getModel(modelID) { + return this.$http.get(`/api/v1/configuration/domains/${modelID}`); + } + + /** + * @returns {ig.config.model.DomainModel} + */ + getBlankModel() { + return { + _id: ObjectID.generate(), + generatePojo: true, + caches: [], + queryKeyFields: [], + queryMetadata: 'Configuration' + }; + } + + queryMetadata = { + values: [ + {label: 'Annotations', value: 'Annotations'}, + {label: 'Configuration', value: 'Configuration'} + ] + }; + + indexType = { + values: [ + {label: 'SORTED', value: 'SORTED'}, + {label: 'FULLTEXT', value: 'FULLTEXT'}, + {label: 'GEOSPATIAL', value: 'GEOSPATIAL'} + ] + }; + + indexSortDirection = { + values: [ + {value: true, label: 'ASC'}, + {value: false, label: 'DESC'} + ], + default: true + }; + + normalize = omit(['__v', 'space']); + + /** + * @param {Array<ig.config.model.IndexField>} fields + */ + addIndexField(fields) { + return fields[fields.push({_id: ObjectID.generate(), direction: true}) - 1]; + } + + /** + * @param {ig.config.model.DomainModel} model + */ + addIndex(model) { + if (!model) return; + if (!model.indexes) model.indexes = []; + model.indexes.push({ + _id: ObjectID.generate(), + name: '', + indexType: 'SORTED', + fields: [] + }); + return model.indexes[model.indexes.length - 1]; + } + + /** + * @param {ig.config.model.DomainModel} model + */ + hasIndex(model) { + return model.queryMetadata === 'Configuration' + ? !!(model.keyFields && model.keyFields.length) + : (!model.generatePojo || !model.databaseSchema && !model.databaseTable); + } + + /** + * @param {ig.config.model.DomainModel} model + * @returns {ig.config.model.ShortDomainModel} + */ + toShortModel(model) { + return { + _id: model._id, + keyType: model.keyType, + valueType: model.valueType, + hasIndex: this.hasIndex(model) + }; + } + + queryIndexes = { + /** + * Validates query indexes for completeness + * @param {Array<ig.config.model.Index>} $value + */ + complete: ($value = []) => $value.every((index) => ( + index.name && index.indexType && + index.fields && index.fields.length && index.fields.every((field) => !!field.name)) + ), + /** + * Checks if field names used in indexes exist + * @param {Array<ig.config.model.Index>} $value + * @param {Array<ig.config.model.Field>} fields + */ + fieldsExist: ($value = [], fields = []) => { + const names = new Set(fields.map((field) => field.name)); + return $value.every((index) => index.fields && index.fields.every((field) => names.has(field.name))); + }, + /** + * Check if fields of query indexes have unique names + * @param {Array<ig.config.model.Index>} $value + */ + indexFieldsHaveUniqueNames: ($value = []) => { + return $value.every((index) => { + if (!index.fields) return true; + const uniqueNames = new Set(index.fields.map((ec) => ec.name)); + return uniqueNames.size === index.fields.length; + }); + } + }; + + /** + * Removes instances of removed fields from queryKeyFields and index fields + * + * @param {ig.config.model.DomainModel} model + * @returns {ig.config.model.DomainModel} + */ + removeInvalidFields(model) { + if (!model) return model; + const fieldNames = new Set((model.fields || []).map((f) => f.name)); + return { + ...model, + queryKeyFields: (model.queryKeyFields || []).filter((queryKeyField) => fieldNames.has(queryKeyField)), + indexes: (model.indexes || []).map((index) => ({ + ...index, + fields: (index.fields || []).filter((indexField) => fieldNames.has(indexField.name)) + })) + }; + } + + /** + * Checks that collection of DB fields has unique DB and Java field names + * @param {Array<ig.config.model.KeyField|ig.config.model.ValueField>} DBFields + */ + storeKeyDBFieldsUnique(DBFields = []) { + return ['databaseFieldName', 'javaFieldName'].every((key) => { + const items = new Set(DBFields.map((field) => field[key])); + return items.size === DBFields.length; + }); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/Version.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/Version.service.js b/modules/web-console/frontend/app/services/Version.service.js index 6daf3aa..33de64d 100644 --- a/modules/web-console/frontend/app/services/Version.service.js +++ b/modules/web-console/frontend/app/services/Version.service.js @@ -16,6 +16,7 @@ */ import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import _ from 'lodash'; /** * Utility service for version parsing and comparing http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/services/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/index.js b/modules/web-console/frontend/app/services/index.js index 55f8d3d..77884df 100644 --- a/modules/web-console/frontend/app/services/index.js +++ b/modules/web-console/frontend/app/services/index.js @@ -16,10 +16,12 @@ */ import angular from 'angular'; +import Clusters from './Clusters'; import IgniteVersion from './Version.service'; import {default as DefaultState} from './DefaultState'; export default angular .module('ignite-console.services', []) + .service('Clusters', Clusters) .provider('DefaultState', DefaultState) .service('IgniteVersion', IgniteVersion); http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/utils/lodashMixins.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/utils/lodashMixins.js b/modules/web-console/frontend/app/utils/lodashMixins.js new file mode 100644 index 0000000..ff50ee0 --- /dev/null +++ b/modules/web-console/frontend/app/utils/lodashMixins.js @@ -0,0 +1,23 @@ +/* + * 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 negate from 'lodash/negate'; +import isNil from 'lodash/isNil'; +import isEmpty from 'lodash/isEmpty'; + +export const nonNil = negate(isNil); +export const nonEmpty = negate(isEmpty); http://git-wip-us.apache.org/repos/asf/ignite/blob/7ee1683e/modules/web-console/frontend/app/utils/uniqueName.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/utils/uniqueName.js b/modules/web-console/frontend/app/utils/uniqueName.js new file mode 100644 index 0000000..bebe2c3 --- /dev/null +++ b/modules/web-console/frontend/app/utils/uniqueName.js @@ -0,0 +1,27 @@ +/* + * 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 const uniqueName = (name, items, fn = ({name, i}) => `${name}${i}`) => { + let i = 0; + let newName = name; + const isUnique = (item) => item.name === newName; + while (items.some(isUnique)) { + i += 1; + newName = fn({name, i}); + } + return newName; +};
