http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/generator/generator-xml.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/generator/generator-xml.js b/modules/control-center-web/src/main/js/routes/generator/generator-xml.js new file mode 100644 index 0000000..569c6dd --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/generator/generator-xml.js @@ -0,0 +1,1202 @@ +/* + * + * * 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. + * + */ + +// For server side we should load required libraries. +if (typeof window === 'undefined') { + _ = require('lodash'); + + $commonUtils = require('../../helpers/common-utils'); + $dataStructures = require('../../helpers/data-structures'); + $generatorCommon = require('./generator-common'); +} + +// XML generation entry point. +$generatorXml = {}; + +// Do XML escape. +$generatorXml.escape = function (s) { + if (typeof(s) != 'string') + return s; + + return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); +}; + +// Add XML element. +$generatorXml.element = function (res, tag, attr1, val1, attr2, val2) { + var elem = '<' + tag; + + if (attr1) + elem += ' ' + attr1 + '="' + val1 + '"'; + + if (attr2) + elem += ' ' + attr2 + '="' + val2 + '"'; + + elem += '/>'; + + res.emptyLineIfNeeded(); + res.line(elem); +}; + +// Add property. +$generatorXml.property = function (res, obj, propName, setterName, dflt) { + if ($commonUtils.isDefined(obj)) { + var val = obj[propName]; + + if ($commonUtils.isDefinedAndNotEmpty(val)) { + var hasDflt = $commonUtils.isDefined(dflt); + + // Add to result if no default provided or value not equals to default. + if (!hasDflt || (hasDflt && val != dflt)) { + $generatorXml.element(res, 'property', 'name', setterName ? setterName : propName, 'value', $generatorXml.escape(val)); + + return true; + } + } + } + + return false; +}; + +// Add property for class name. +$generatorXml.classNameProperty = function (res, obj, propName) { + var val = obj[propName]; + + if ($commonUtils.isDefined(val)) + $generatorXml.element(res, 'property', 'name', propName, 'value', $dataStructures.fullClassName(val)); +}; + +// Add list property. +$generatorXml.listProperty = function (res, obj, propName, listType, rowFactory) { + var val = obj[propName]; + + if (val && val.length > 0) { + res.emptyLineIfNeeded(); + + if (!listType) + listType = 'list'; + + if (!rowFactory) + rowFactory = function (val) { + return '<value>' + $generatorXml.escape(val) + '</value>' + }; + + res.startBlock('<property name="' + propName + '">'); + res.startBlock('<' + listType + '>'); + + for (var i = 0; i < val.length; i++) + res.line(rowFactory(val[i])); + + res.endBlock('</' + listType + '>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } +}; + +// Add array property +$generatorXml.arrayProperty = function (res, obj, propName, descr, rowFactory) { + var val = obj[propName]; + + if (val && val.length > 0) { + res.emptyLineIfNeeded(); + + if (!rowFactory) + rowFactory = function (val) { + return '<bean class="' + val + '"/>'; + }; + + res.startBlock('<property name="' + propName + '">'); + res.startBlock('<list>'); + + _.forEach(val, function (v) { + res.append(rowFactory(v)) + }); + + res.endBlock('</list>'); + res.endBlock('</property>'); + } +} + +// Add bean property. +$generatorXml.beanProperty = function (res, bean, beanPropName, desc, createBeanAlthoughNoProps) { + var props = desc.fields; + + if (bean && $commonUtils.hasProperty(bean, props)) { + res.startSafeBlock(); + + res.emptyLineIfNeeded(); + res.startBlock('<property name="' + beanPropName + '">'); + res.startBlock('<bean class="' + desc.className + '">'); + + var hasData = false; + + for (var propName in props) { + if (props.hasOwnProperty(propName)) { + var descr = props[propName]; + + if (descr) { + if (descr.type == 'list') + $generatorXml.listProperty(res, bean, propName, descr.setterName); + else if (descr.type == 'array') + $generatorXml.arrayProperty(res, bean, propName, descr); + else if (descr.type == 'jdbcDialect') { + if (bean[propName]) { + res.startBlock('<property name="' + propName + '">'); + res.line('<bean class="' + $generatorCommon.jdbcDialectClassName(bean[propName]) + '"/>'); + res.endBlock('</property>'); + + hasData = true; + } + } + else if (descr.type == 'propertiesAsList') { + var val = bean[propName]; + + if (val && val.length > 0) { + res.startBlock('<property name="' + propName + '">'); + res.startBlock('<props>'); + + for (var i = 0; i < val.length; i++) { + var nameAndValue = val[i]; + + var eqIndex = nameAndValue.indexOf('='); + if (eqIndex >= 0) { + res.line('<prop key="' + $generatorXml.escape(nameAndValue.substring(0, eqIndex)) + '">' + + $generatorXml.escape(nameAndValue.substr(eqIndex + 1)) + '</prop>'); + } + } + + res.endBlock('</props>'); + res.endBlock('</property>'); + + hasData = true; + } + } + else { + if ($generatorXml.property(res, bean, propName, descr.setterName, descr.dflt)) + hasData = true; + } + } + else + if ($generatorXml.property(res, bean, propName)) + hasData = true; + } + } + + res.endBlock('</bean>'); + res.endBlock('</property>'); + + if (!hasData) + res.rollbackSafeBlock(); + } + else if (createBeanAlthoughNoProps) { + res.emptyLineIfNeeded(); + res.line('<property name="' + beanPropName + '">'); + res.line(' <bean class="' + desc.className + '"/>'); + res.line('</property>'); + } +}; + +// Generate eviction policy. +$generatorXml.evictionPolicy = function (res, evtPlc, propName) { + if (evtPlc && evtPlc.kind) { + $generatorXml.beanProperty(res, evtPlc[evtPlc.kind.toUpperCase()], propName, + $generatorCommon.EVICTION_POLICIES[evtPlc.kind], true); + } +}; + +// Generate discovery. +$generatorXml.clusterGeneral = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cluster, 'name', 'gridName'); + + if (cluster.discovery) { + res.startBlock('<property name="discoverySpi">'); + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">'); + res.startBlock('<property name="ipFinder">'); + + var d = cluster.discovery; + + switch (d.kind) { + case 'Multicast': + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">'); + + if (d.Multicast) { + $generatorXml.property(res, d.Multicast, 'multicastGroup'); + $generatorXml.property(res, d.Multicast, 'multicastPort'); + $generatorXml.property(res, d.Multicast, 'responseWaitTime'); + $generatorXml.property(res, d.Multicast, 'addressRequestAttempts'); + $generatorXml.property(res, d.Multicast, 'localAddress'); + $generatorXml.listProperty(res, d.Multicast, 'addresses'); + } + + res.endBlock('</bean>'); + + break; + + case 'Vm': + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">'); + + if (d.Vm) { + $generatorXml.listProperty(res, d.Vm, 'addresses'); + } + + res.endBlock('</bean>'); + + break; + + case 'S3': + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.s3.TcpDiscoveryS3IpFinder">'); + + if (d.S3) { + if (d.S3.bucketName) + res.line('<property name="bucketName" value="' + $generatorXml.escape(d.S3.bucketName) + '" />'); + } + + res.endBlock('</bean>'); + + break; + + case 'Cloud': + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.cloud.TcpDiscoveryCloudIpFinder">'); + + if (d.Cloud) { + $generatorXml.property(res, d.Cloud, 'credential'); + $generatorXml.property(res, d.Cloud, 'credentialPath'); + $generatorXml.property(res, d.Cloud, 'identity'); + $generatorXml.property(res, d.Cloud, 'provider'); + $generatorXml.listProperty(res, d.Cloud, 'regions'); + $generatorXml.listProperty(res, d.Cloud, 'zones'); + } + + res.endBlock('</bean>'); + + break; + + case 'GoogleStorage': + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.gce.TcpDiscoveryGoogleStorageIpFinder">'); + + if (d.GoogleStorage) { + $generatorXml.property(res, d.GoogleStorage, 'projectName'); + $generatorXml.property(res, d.GoogleStorage, 'bucketName'); + $generatorXml.property(res, d.GoogleStorage, 'serviceAccountP12FilePath'); + $generatorXml.property(res, d.GoogleStorage, 'serviceAccountId'); + } + + res.endBlock('</bean>'); + + break; + + case 'Jdbc': + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinder">'); + + if (d.Jdbc) { + res.line('<property name="initSchema" value="' + ($commonUtils.isDefined(d.Jdbc.initSchema) && d.Jdbc.initSchema) + '"/>'); + } + + res.endBlock('</bean>'); + + break; + + case 'SharedFs': + res.startBlock('<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinder">'); + + if (d.SharedFs) { + $generatorXml.property(res, d.SharedFs, 'path'); + } + + res.endBlock('</bean>'); + + break; + + default: + throw "Unknown discovery kind: " + d.kind; + } + + res.endBlock('</property>'); + + $generatorXml.clusterDiscovery(d, res); + + res.endBlock('</bean>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } + + return res; +}; + +// Generate atomics group. +$generatorXml.clusterAtomics = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + var atomics = cluster.atomicConfiguration; + + if ($commonUtils.hasAtLeastOneProperty(atomics, ['cacheMode', 'atomicSequenceReserveSize', 'backups'])) { + res.startSafeBlock(); + + res.emptyLineIfNeeded(); + + res.startBlock('<property name="atomicConfiguration">'); + res.startBlock('<bean class="org.apache.ignite.configuration.AtomicConfiguration">'); + + var cacheMode = atomics.cacheMode ? atomics.cacheMode : 'PARTITIONED'; + + var hasData = cacheMode != 'PARTITIONED'; + + $generatorXml.property(res, atomics, 'cacheMode'); + + hasData = $generatorXml.property(res, atomics, 'atomicSequenceReserveSize') || hasData; + + if (cacheMode == 'PARTITIONED') + hasData = $generatorXml.property(res, atomics, 'backups') || hasData; + + res.endBlock('</bean>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + + if (!hasData) + res.rollbackSafeBlock(); + } + + return res; +}; + +// Generate communication group. +$generatorXml.clusterCommunication = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cluster, 'networkTimeout'); + $generatorXml.property(res, cluster, 'networkSendRetryDelay'); + $generatorXml.property(res, cluster, 'networkSendRetryCount'); + $generatorXml.property(res, cluster, 'segmentCheckFrequency'); + $generatorXml.property(res, cluster, 'waitForSegmentOnStart', null, false); + $generatorXml.property(res, cluster, 'discoveryStartupDelay'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate deployment group. +$generatorXml.clusterDeployment = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + if ($generatorXml.property(res, cluster, 'deploymentMode', null, 'SHARED')) + res.needEmptyLine = true; + + return res; +}; + +// Generate discovery group. +$generatorXml.clusterDiscovery = function (disco, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, disco, 'localAddress'); + $generatorXml.property(res, disco, 'localPort', undefined, 47500); + $generatorXml.property(res, disco, 'localPortRange', undefined, 100); + $generatorXml.beanProperty(res, disco, 'addressResolver', {className: disco.addressResolver}, true); + $generatorXml.property(res, disco, 'socketTimeout'); + $generatorXml.property(res, disco, 'ackTimeout'); + $generatorXml.property(res, disco, 'maxAckTimeout', undefined, 600000); + $generatorXml.property(res, disco, 'discoNetworkTimeout', 'setNetworkTimeout', 5000); + $generatorXml.property(res, disco, 'joinTimeout', undefined, 0); + $generatorXml.property(res, disco, 'threadPriority', undefined, 10); + $generatorXml.property(res, disco, 'heartbeatFrequency', undefined, 2000); + $generatorXml.property(res, disco, 'maxMissedHeartbeats', undefined, 1); + $generatorXml.property(res, disco, 'maxMissedClientHeartbeats', undefined, 5); + $generatorXml.property(res, disco, 'topHistorySize', undefined, 100); + $generatorXml.beanProperty(res, disco, 'listener', {className: disco.listener}, true); + $generatorXml.beanProperty(res, disco, 'dataExchange', {className: disco.dataExchange}, true); + $generatorXml.beanProperty(res, disco, 'metricsProvider', {className: disco.metricsProvider}, true); + $generatorXml.property(res, disco, 'reconnectCount', undefined, 10); + $generatorXml.property(res, disco, 'statisticsPrintFrequency', undefined, 0); + $generatorXml.property(res, disco, 'ipFinderCleanFrequency', undefined, 60000); + $generatorXml.beanProperty(res, disco, 'authenticator', {className: disco.authenticator}, true); + $generatorXml.property(res, disco, 'forceServerMode', undefined, false); + $generatorXml.property(res, disco, 'clientReconnectDisabled', undefined, false); + + res.needEmptyLine = true; + + return res; +}; + +// Generate events group. +$generatorXml.clusterEvents = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + if (cluster.includeEventTypes && cluster.includeEventTypes.length > 0) { + res.emptyLineIfNeeded(); + + res.startBlock('<property name="includeEventTypes">'); + + if (cluster.includeEventTypes.length == 1) + res.line('<util:constant static-field="org.apache.ignite.events.EventType.' + cluster.includeEventTypes[0] + '"/>'); + else { + res.startBlock('<list>'); + + for (i = 0; i < cluster.includeEventTypes.length; i++) { + if (i > 0) + res.line(); + + var eventGroup = cluster.includeEventTypes[i]; + + res.line('<!-- EventType.' + eventGroup + ' -->'); + + var eventList = $dataStructures.EVENT_GROUPS[eventGroup]; + + for (var k = 0; k < eventList.length; k++) { + res.line('<util:constant static-field="org.apache.ignite.events.EventType.' + eventList[k] + '"/>') + } + } + + res.endBlock('</list>'); + } + + res.endBlock('</property>'); + + res.needEmptyLine = true; + } + + return res; +}; + +// Generate marshaller group. +$generatorXml.clusterMarshaller = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + var marshaller = cluster.marshaller; + + if (marshaller && marshaller.kind) { + $generatorXml.beanProperty(res, marshaller[marshaller.kind], 'marshaller', $generatorCommon.MARSHALLERS[marshaller.kind], true); + + res.needEmptyLine = true; + } + + $generatorXml.property(res, cluster, 'marshalLocalJobs', null, false); + $generatorXml.property(res, cluster, 'marshallerCacheKeepAliveTime'); + $generatorXml.property(res, cluster, 'marshallerCacheThreadPoolSize'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate metrics group. +$generatorXml.clusterMetrics = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cluster, 'metricsExpireTime'); + $generatorXml.property(res, cluster, 'metricsHistorySize'); + $generatorXml.property(res, cluster, 'metricsLogFrequency'); + $generatorXml.property(res, cluster, 'metricsUpdateFrequency'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate PeerClassLoading group. +$generatorXml.clusterP2p = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + var p2pEnabled = cluster.peerClassLoadingEnabled; + + if ($commonUtils.isDefined(p2pEnabled)) { + $generatorXml.property(res, cluster, 'peerClassLoadingEnabled', null, false); + + if (p2pEnabled) { + $generatorXml.property(res, cluster, 'peerClassLoadingMissedResourcesCacheSize'); + $generatorXml.property(res, cluster, 'peerClassLoadingThreadPoolSize'); + $generatorXml.listProperty(res, cluster, 'peerClassLoadingLocalClassPathExclude'); + } + + res.needEmptyLine = true; + } + + return res; +}; + +// Generate swap group. +$generatorXml.clusterSwap = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + if (cluster.swapSpaceSpi && cluster.swapSpaceSpi.kind == 'FileSwapSpaceSpi') { + $generatorXml.beanProperty(res, cluster.swapSpaceSpi.FileSwapSpaceSpi, 'swapSpaceSpi', + $generatorCommon.SWAP_SPACE_SPI, true); + + res.needEmptyLine = true; + } + + return res; +}; + +// Generate time group. +$generatorXml.clusterTime = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cluster, 'clockSyncSamples'); + $generatorXml.property(res, cluster, 'clockSyncFrequency'); + $generatorXml.property(res, cluster, 'timeServerPortBase'); + $generatorXml.property(res, cluster, 'timeServerPortRange'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate thread pools group. +$generatorXml.clusterPools = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cluster, 'publicThreadPoolSize'); + $generatorXml.property(res, cluster, 'systemThreadPoolSize'); + $generatorXml.property(res, cluster, 'managementThreadPoolSize'); + $generatorXml.property(res, cluster, 'igfsThreadPoolSize'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate transactions group. +$generatorXml.clusterTransactions = function (cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.beanProperty(res, cluster.transactionConfiguration, 'transactionConfiguration', $generatorCommon.TRANSACTION_CONFIGURATION); + + res.needEmptyLine = true; + + return res; +}; + +/** + * XML generator for cluster's SSL configuration. + * + * @param cluster Cluster to get SSL configuration. + * @param res Optional configuration presentation builder object. + * @returns Configuration presentation builder object + */ +$generatorXml.clusterSsl = function(cluster, res) { + if (!res) + res = $generatorCommon.builder(); + + if (cluster.sslEnabled && $commonUtils.isDefined(cluster.sslContextFactory)) { + cluster.sslContextFactory.keyStorePassword = + ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.keyStoreFilePath)) ? '${ssl.key.storage.password}' : undefined; + + cluster.sslContextFactory.trustStorePassword = + ($commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustStoreFilePath)) ? '${ssl.trust.storage.password}' : undefined; + + var propsDesc = $commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustManagers) ? + $generatorCommon.SSL_CONFIGURATION_TRUST_MANAGER_FACTORY : + $generatorCommon.SSL_CONFIGURATION_TRUST_FILE_FACTORY; + + $generatorXml.beanProperty(res, cluster.sslContextFactory, 'sslContextFactory', propsDesc, false); + } + + return res; +}; + +// Generate cache general group. +$generatorXml.cacheGeneral = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cache, 'name'); + + $generatorXml.property(res, cache, 'cacheMode'); + $generatorXml.property(res, cache, 'atomicityMode'); + + if (cache.cacheMode == 'PARTITIONED') + $generatorXml.property(res, cache, 'backups'); + + $generatorXml.property(res, cache, 'readFromBackup'); + $generatorXml.property(res, cache, 'copyOnRead'); + $generatorXml.property(res, cache, 'invalidate'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache memory group. +$generatorXml.cacheMemory = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cache, 'memoryMode'); + $generatorXml.property(res, cache, 'offHeapMaxMemory'); + + res.needEmptyLine = true; + + $generatorXml.evictionPolicy(res, cache.evictionPolicy, 'evictionPolicy'); + + res.needEmptyLine = true; + + $generatorXml.property(res, cache, 'swapEnabled'); + $generatorXml.property(res, cache, 'startSize'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache query & indexing group. +$generatorXml.cacheQuery = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cache, 'sqlOnheapRowCacheSize'); + $generatorXml.property(res, cache, 'longQueryWarningTimeout'); + + if (cache.indexedTypes && cache.indexedTypes.length > 0) { + res.startBlock('<property name="indexedTypes">'); + res.startBlock('<list>'); + + for (var i = 0; i < cache.indexedTypes.length; i++) { + var pair = cache.indexedTypes[i]; + + res.line('<value>' + $dataStructures.fullClassName(pair.keyClass) + '</value>'); + res.line('<value>' + $dataStructures.fullClassName(pair.valueClass) + '</value>'); + } + + res.endBlock('</list>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } + + $generatorXml.listProperty(res, cache, 'sqlFunctionClasses'); + + $generatorXml.property(res, cache, 'sqlEscapeAll'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache store group. +$generatorXml.cacheStore = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + if (cache.cacheStoreFactory && cache.cacheStoreFactory.kind) { + var storeFactory = cache.cacheStoreFactory[cache.cacheStoreFactory.kind]; + + if (storeFactory) { + $generatorXml.beanProperty(res, storeFactory, 'cacheStoreFactory', $generatorCommon.STORE_FACTORIES[cache.cacheStoreFactory.kind], true); + + if (storeFactory.dialect) { + if (_.findIndex(res.datasources, function (ds) { + return ds.dataSourceBean == storeFactory.dataSourceBean; + }) < 0) { + res.datasources.push({ + dataSourceBean: storeFactory.dataSourceBean, + className: $generatorCommon.DATA_SOURCES[storeFactory.dialect], + dialect: storeFactory.dialect + }); + } + } + + res.needEmptyLine = true; + } + } + + $generatorXml.property(res, cache, 'loadPreviousValue'); + $generatorXml.property(res, cache, 'readThrough'); + $generatorXml.property(res, cache, 'writeThrough'); + + res.needEmptyLine = true; + + $generatorXml.property(res, cache, 'writeBehindEnabled'); + $generatorXml.property(res, cache, 'writeBehindBatchSize'); + $generatorXml.property(res, cache, 'writeBehindFlushSize'); + $generatorXml.property(res, cache, 'writeBehindFlushFrequency'); + $generatorXml.property(res, cache, 'writeBehindFlushThreadCount'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache concurrency group. +$generatorXml.cacheConcurrency = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cache, 'maxConcurrentAsyncOperations'); + $generatorXml.property(res, cache, 'defaultLockTimeout'); + $generatorXml.property(res, cache, 'atomicWriteOrderMode'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache rebalance group. +$generatorXml.cacheRebalance = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + if (cache.cacheMode != 'LOCAL') { + $generatorXml.property(res, cache, 'rebalanceMode'); + $generatorXml.property(res, cache, 'rebalanceThreadPoolSize'); + $generatorXml.property(res, cache, 'rebalanceBatchSize'); + $generatorXml.property(res, cache, 'rebalanceOrder'); + $generatorXml.property(res, cache, 'rebalanceDelay'); + $generatorXml.property(res, cache, 'rebalanceTimeout'); + $generatorXml.property(res, cache, 'rebalanceThrottle'); + + res.needEmptyLine = true; + } + + return res; +}; + +// Generate cache server near cache group. +$generatorXml.cacheServerNearCache = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + if (cache.cacheMode == 'PARTITIONED' && cache.nearCacheEnabled) { + res.emptyLineIfNeeded(); + + res.startBlock('<property name="nearConfiguration">'); + res.startBlock('<bean class="org.apache.ignite.configuration.NearCacheConfiguration">'); + + if (cache.nearConfiguration) { + if (cache.nearConfiguration.nearStartSize) + $generatorXml.property(res, cache.nearConfiguration, 'nearStartSize'); + + + $generatorXml.evictionPolicy(res, cache.nearConfiguration.nearEvictionPolicy, 'nearEvictionPolicy'); + } + + + + res.endBlock('</bean>'); + res.endBlock('</property>'); + } + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache statistics group. +$generatorXml.cacheStatistics = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, cache, 'statisticsEnabled'); + $generatorXml.property(res, cache, 'managementEnabled'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate metadata query fields. +$generatorXml.metadataQueryFields = function (res, meta, fieldProp) { + var fields = meta[fieldProp]; + + if (fields && fields.length > 0) { + res.emptyLineIfNeeded(); + + res.startBlock('<property name="' + fieldProp + '">'); + res.startBlock('<map>'); + + _.forEach(fields, function (field) { + $generatorXml.element(res, 'entry', 'key', field.name.toUpperCase(), 'value', $dataStructures.fullClassName(field.className)); + }); + + res.endBlock('</map>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } +}; + +// Generate metadata groups. +$generatorXml.metadataGroups = function (res, meta) { + var groups = meta.groups; + + if (groups && groups.length > 0) { + res.emptyLineIfNeeded(); + + res.startBlock('<property name="groups">'); + res.startBlock('<map>'); + + _.forEach(groups, function (group) { + var fields = group.fields; + + if (fields && fields.length > 0) { + res.startBlock('<entry key="' + group.name + '">'); + res.startBlock('<map>'); + + _.forEach(fields, function (field) { + res.startBlock('<entry key="' + field.name + '">'); + + res.startBlock('<bean class="org.apache.ignite.lang.IgniteBiTuple">'); + res.line('<constructor-arg value="' + $dataStructures.fullClassName(field.className) + '"/>'); + res.line('<constructor-arg value="' + field.direction + '"/>'); + res.endBlock('</bean>'); + + res.endBlock('</entry>'); + }); + + res.endBlock('</map>'); + res.endBlock('</entry>'); + } + }); + + res.endBlock('</map>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } +}; + +// Generate metadata db fields. +$generatorXml.metadataDatabaseFields = function (res, meta, fieldProp) { + var fields = meta[fieldProp]; + + if (fields && fields.length > 0) { + res.emptyLineIfNeeded(); + + res.startBlock('<property name="' + fieldProp + '">'); + + res.startBlock('<list>'); + + _.forEach(fields, function (field) { + res.startBlock('<bean class="org.apache.ignite.cache.CacheTypeFieldMetadata">'); + + $generatorXml.property(res, field, 'databaseName'); + + res.startBlock('<property name="databaseType">'); + res.line('<util:constant static-field="java.sql.Types.' + field.databaseType + '"/>'); + res.endBlock('</property>'); + + $generatorXml.property(res, field, 'javaName'); + + $generatorXml.classNameProperty(res, field, 'javaType'); + + res.endBlock('</bean>'); + }); + + res.endBlock('</list>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } +}; + +// Generate metadata general group. +$generatorXml.metadataGeneral = function(meta, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.classNameProperty(res, meta, 'keyType'); + $generatorXml.property(res, meta, 'valueType'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate metadata for query group. +$generatorXml.metadataQuery = function(meta, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.metadataQueryFields(res, meta, 'queryFields'); + $generatorXml.metadataQueryFields(res, meta, 'ascendingFields'); + $generatorXml.metadataQueryFields(res, meta, 'descendingFields'); + + $generatorXml.listProperty(res, meta, 'textFields'); + + $generatorXml.metadataGroups(res, meta); + + res.needEmptyLine = true; + + return res; +}; + +// Generate metadata for store group. +$generatorXml.metadataStore = function(meta, res) { + if (!res) + res = $generatorCommon.builder(); + + $generatorXml.property(res, meta, 'databaseSchema'); + $generatorXml.property(res, meta, 'databaseTable'); + + res.needEmptyLine = true; + + if (!$dataStructures.isJavaBuildInClass(meta.keyType)) + $generatorXml.metadataDatabaseFields(res, meta, 'keyFields'); + + $generatorXml.metadataDatabaseFields(res, meta, 'valueFields'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache type metadata config. +$generatorXml.cacheMetadata = function(meta, res) { + if (!res) + res = $generatorCommon.builder(); + + res.emptyLineIfNeeded(); + + res.startBlock('<bean class="org.apache.ignite.cache.CacheTypeMetadata">'); + + $generatorXml.metadataGeneral(meta, res); + $generatorXml.metadataQuery(meta, res); + $generatorXml.metadataStore(meta, res); + + res.endBlock('</bean>'); + + res.needEmptyLine = true; + + return res; +}; + +// Generate cache type metadata configs. +$generatorXml.cacheMetadatas = function(metadatas, res) { + if (!res) + res = $generatorCommon.builder(); + + if (metadatas && metadatas.length > 0) { + res.emptyLineIfNeeded(); + + res.startBlock('<property name="typeMetadata">'); + res.startBlock('<list>'); + + _.forEach(metadatas, function (meta) { + $generatorXml.cacheMetadata(meta, res); + }); + + res.endBlock('</list>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } + + return res; +}; + +// Generate cache configs. +$generatorXml.cache = function(cache, res) { + if (!res) + res = $generatorCommon.builder(); + + res.startBlock('<bean class="org.apache.ignite.configuration.CacheConfiguration">'); + + $generatorXml.cacheGeneral(cache, res); + + $generatorXml.cacheMemory(cache, res); + + $generatorXml.cacheQuery(cache, res); + + $generatorXml.cacheStore(cache, res); + + $generatorXml.cacheConcurrency(cache, res); + + $generatorXml.cacheRebalance(cache, res); + + $generatorXml.cacheServerNearCache(cache, res); + + $generatorXml.cacheStatistics(cache, res); + + $generatorXml.cacheMetadatas(cache.metadatas, res); + + res.endBlock('</bean>'); + + return res; +}; + +// Generate caches configs. +$generatorXml.clusterCaches = function(caches, res) { + if (!res) + res = $generatorCommon.builder(); + + if (caches && caches.length > 0) { + res.emptyLineIfNeeded(); + + res.startBlock('<property name="cacheConfiguration">'); + res.startBlock('<list>'); + + for (var i = 0; i < caches.length; i++) { + if (i > 0) + res.line(); + + $generatorXml.cache(caches[i], res); + } + + res.endBlock('</list>'); + res.endBlock('</property>'); + + res.needEmptyLine = true; + } + + return res; +}; + +// Generate cluster config. +$generatorXml.cluster = function (cluster, clientNearCfg) { + if (cluster) { + var res = $generatorCommon.builder(); + + res.deep = 1; + + if (clientNearCfg) { + res.startBlock('<bean id="nearCacheBean" class="org.apache.ignite.configuration.NearCacheConfiguration">'); + + if (clientNearCfg.nearStartSize) + $generatorXml.property(res, clientNearCfg, 'nearStartSize'); + + if (clientNearCfg.nearEvictionPolicy && clientNearCfg.nearEvictionPolicy.kind) + $generatorXml.evictionPolicy(res, clientNearCfg.nearEvictionPolicy, 'nearEvictionPolicy'); + + res.endBlock('</bean>'); + + res.line(); + } + + // Generate Ignite Configuration. + res.startBlock('<bean class="org.apache.ignite.configuration.IgniteConfiguration">'); + + if (clientNearCfg) { + res.line('<property name="clientMode" value="true" />'); + + res.line(); + } + + $generatorXml.clusterGeneral(cluster, res); + + $generatorXml.clusterAtomics(cluster, res); + + $generatorXml.clusterCommunication(cluster, res); + + $generatorXml.clusterDeployment(cluster, res); + + $generatorXml.clusterEvents(cluster, res); + + $generatorXml.clusterMarshaller(cluster, res); + + $generatorXml.clusterMetrics(cluster, res); + + $generatorXml.clusterP2p(cluster, res); + + $generatorXml.clusterSwap(cluster, res); + + $generatorXml.clusterTime(cluster, res); + + $generatorXml.clusterPools(cluster, res); + + $generatorXml.clusterTransactions(cluster, res); + + $generatorXml.clusterCaches(cluster.caches, res); + + $generatorXml.clusterSsl(cluster, res); + + res.endBlock('</bean>'); + + // Build final XML: + // 1. Add header. + var xml = '<?xml version="1.0" encoding="UTF-8"?>\n\n'; + + xml += '<!-- ' + $generatorCommon.mainComment() + ' -->\n'; + xml += '<beans xmlns="http://www.springframework.org/schema/beans"\n'; + xml += ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n'; + xml += ' xmlns:util="http://www.springframework.org/schema/util"\n'; + xml += ' xsi:schemaLocation="http://www.springframework.org/schema/beans\n'; + xml += ' http://www.springframework.org/schema/beans/spring-beans.xsd\n'; + xml += ' http://www.springframework.org/schema/util\n'; + xml += ' http://www.springframework.org/schema/util/spring-util.xsd">\n'; + + // 2. Add external property file + if (res.datasources.length > 0 + || (cluster.sslEnabled && ( + $commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.keyStoreFilePath) || + $commonUtils.isDefinedAndNotEmpty(cluster.sslContextFactory.trustStoreFilePath)))) { + xml += ' <!-- Load external properties file. -->\n'; + xml += ' <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">\n'; + xml += ' <property name="location" value="classpath:secret.properties"/>\n'; + xml += ' </bean>\n\n'; + } + + // 3. Add data sources. + if (res.datasources.length > 0) { + xml += ' <!-- Data source beans will be initialized from external properties file. -->\n'; + + _.forEach(res.datasources, function (item) { + var beanId = item.dataSourceBean; + + xml += ' <bean id="' + beanId + '" class="' + item.className + '">\n'; + switch (item.dialect) { + case 'DB2': + xml += ' <property name="serverName" value="${' + beanId + '.jdbc.server_name}" />\n'; + xml += ' <property name="portNumber" value="${' + beanId + '.jdbc.port_number}" />\n'; + xml += ' <property name="databaseName" value="${' + beanId + '.jdbc.database_name}" />\n'; + xml += ' <property name="driverType" value="${' + beanId + '.jdbc.driver_type}" />\n'; + break; + + default: + xml += ' <property name="URL" value="${' + beanId + '.jdbc.url}" />\n'; + } + + xml += ' <property name="user" value="${' + beanId + '.jdbc.username}" />\n'; + xml += ' <property name="password" value="${' + beanId + '.jdbc.password}" />\n'; + xml += ' </bean>\n\n'; + }); + } + + // 3. Add main content. + xml += res.asString(); + + // 4. Add footer. + xml += '\n</beans>'; + + return xml; + } + + return ''; +}; + +// For server side we should export XML generation entry point. +if (typeof window === 'undefined') { + module.exports = $generatorXml; +}
http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/metadata.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/metadata.js b/modules/control-center-web/src/main/js/routes/metadata.js new file mode 100644 index 0000000..b9d9445 --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/metadata.js @@ -0,0 +1,192 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var async = require('async'); +var router = require('express').Router(); +var db = require('../db'); + +/* GET metadata page. */ +router.get('/', function (req, res) { + res.render('configuration/metadata'); +}); + +/* GET metadata load dialog. */ +router.get('/metadata-load', function (req, res) { + res.render('configuration/metadata-load'); +}); + +/** + * Get spaces and metadata accessed for user account. + * + * @param req Request. + * @param res Response. + */ +router.post('/list', function (req, res) { + var user_id = req.currentUserId(); + + // Get owned space and all accessed space. + db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { + if (db.processed(err, res)) { + var space_ids = spaces.map(function (value) { + return value._id; + }); + + // Get all caches for spaces. + db.Cache.find({space: {$in: space_ids}}).sort('name').exec(function (err, caches) { + if (db.processed(err, res)) { + // Get all metadata for spaces. + db.CacheTypeMetadata.find({space: {$in: space_ids}}).sort('valueType').exec(function (err, metadatas) { + if (db.processed(err, res)) { + // Remove deleted caches. + _.forEach(metadatas, function (meta) { + meta.caches = _.filter(meta.caches, function (cacheId) { + return _.findIndex(caches, function (cache) { + return cache._id.equals(cacheId); + }) >= 0; + }); + }); + + res.json({ + spaces: spaces, + caches: caches.map(function (cache) { + return {value: cache._id, label: cache.name}; + }), + metadatas: metadatas + }); + } + }); + } + }); + } + }); +}); + +function _save(metas, res) { + var savedMetas = []; + + if (metas && metas.length > 0) + async.forEachOf(metas, function(meta, idx, callback) { + var metaId = meta._id; + var caches = meta.caches; + + if (metaId) + db.CacheTypeMetadata.update({_id: meta._id}, meta, {upsert: true}, function (err) { + if (err) + callback(err); + else + db.Cache.update({_id: {$in: caches}}, {$addToSet: {metadatas: metaId}}, {multi: true}, function (err) { + if (err) + callback(err); + else + db.Cache.update({_id: {$nin: caches}}, {$pull: {metadatas: metaId}}, {multi: true}, function (err) { + if (err) + callback(err); + else { + savedMetas.push(meta); + + callback(); + } + }); + }); + }); + else { + db.CacheTypeMetadata.findOne({space: meta.space, valueType: meta.valueType}, function (err, metadata) { + if (err) + callback(err); + else + if (metadata) + return callback('Cache type metadata with value type: "' + metadata.valueType + '" already exist.'); + + (new db.CacheTypeMetadata(meta)).save(function (err, metadata) { + if (err) + callback(err); + else { + metaId = metadata._id; + + db.Cache.update({_id: {$in: caches}}, {$addToSet: {metadatas: metaId}}, {multi: true}, function (err) { + if (err) + callback(err); + else { + savedMetas.push(metadata); + + callback(); + } + }); + } + }); + }); + } + }, function (err) { + if (err) + res.status(500).send(err); + else + res.send(savedMetas); + }); + else + res.status(500).send('Nothing to save!'); +} + +/** + * Save metadata. + */ +router.post('/save', function (req, res) { + _save([req.body], res); +}); + +/** + * Batch save metadata . + */ +router.post('/save/batch', function (req, res) { + _save(req.body, res); +}); + +/** + * Remove metadata by ._id. + */ +router.post('/remove', function (req, res) { + db.CacheTypeMetadata.remove(req.body, function (err) { + if (db.processed(err, res)) + res.sendStatus(200); + }) +}); + +/** + * Remove all metadata. + */ +router.post('/remove/all', function (req, res) { + var user_id = req.currentUserId(); + + // Get owned space and all accessed space. + db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { + if (db.processed(err, res)) { + var space_ids = spaces.map(function (value) { + return value._id; + }); + + db.CacheTypeMetadata.remove({space: {$in: space_ids}}, function (err) { + if (err) + return res.status(500).send(err.message); + + res.sendStatus(200); + }) + } + }); +}); + +module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/notebooks.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/notebooks.js b/modules/control-center-web/src/main/js/routes/notebooks.js new file mode 100644 index 0000000..70ccbcd --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/notebooks.js @@ -0,0 +1,157 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var router = require('express').Router(); + +var db = require('../db'); +var utils = require('./../helpers/common-utils'); + +router.get('/new', function (req, res) { + res.render('sql/notebook-new', {}); +}); + +/** + * Get notebooks names accessed for user account. + * + * @param req Request. + * @param res Response. + */ +router.post('/list', function (req, res) { + var user_id = req.currentUserId(); + + // Get owned space and all accessed space. + db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { + if (err) + return res.status(500).send(err.message); + + var space_ids = spaces.map(function (value) { + return value._id; + }); + + // Get all metadata for spaces. + db.Notebook.find({space: {$in: space_ids}}).select('_id name').sort('name').exec(function (err, notebooks) { + if (err) + return res.status(500).send(err.message); + + res.json(notebooks); + }); + }); +}); + +/** + * Get notebook accessed for user account. + * + * @param req Request. + * @param res Response. + */ +router.post('/get', function (req, res) { + var user_id = req.currentUserId(); + + // Get owned space and all accessed space. + db.Space.find({$or: [{owner: user_id}, {usedBy: {$elemMatch: {account: user_id}}}]}, function (err, spaces) { + if (err) + return res.status(500).send(err.message); + + var space_ids = spaces.map(function (value) { + return value._id; + }); + + // Get all metadata for spaces. + db.Notebook.findOne({space: {$in: space_ids}, _id: req.body.noteId}).exec(function (err, notebook) { + if (err) + return res.status(500).send(err.message); + + res.json(notebook); + }); + }); +}); + +/** + * Save notebook accessed for user account. + * + * @param req Request. + * @param res Response. + */ +router.post('/save', function (req, res) { + var note = req.body; + var noteId = note._id; + + if (noteId) + db.Notebook.update({_id: noteId}, note, {upsert: true}, function (err) { + if (err) + return res.status(500).send(err.message); + + res.send(noteId); + }); + else + db.Notebook.findOne({space: note.space, name: note.name}, function (err, note) { + if (err) + return res.status(500).send(err.message); + + if (note) + return res.status(500).send('Notebook with name: "' + note.name + '" already exist.'); + + (new db.Notebook(req.body)).save(function (err, note) { + if (err) + return res.status(500).send(err.message); + + res.send(note._id); + }); + }); +}); + +/** + * Remove notebook by ._id. + * + * @param req Request. + * @param res Response. + */ +router.post('/remove', function (req, res) { + db.Notebook.remove(req.body, function (err) { + if (err) + return res.status(500).send(err.message); + + res.sendStatus(200); + }); +}); + +/** + * Create new notebook for user account. + * + * @param req Request. + * @param res Response. + */ +router.post('/new', function (req, res) { + var user_id = req.currentUserId(); + + // Get owned space and all accessed space. + db.Space.findOne({owner: user_id}, function (err, space) { + if (err) + return res.status(500).send(err.message); + + (new db.Notebook({space: space.id, name: req.body.name, paragraphs: []})).save(function (err, note) { + if (err) + return res.status(500).send(err.message); + + return res.send(note._id); + }); + }); +}); + +module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/presets.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/presets.js b/modules/control-center-web/src/main/js/routes/presets.js new file mode 100644 index 0000000..76eb5dd --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/presets.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. + * + */ + +var router = require('express').Router(); +var db = require('../db'); + +/** + * Get database presets. + * + * @param req Request. + * @param res Response. + */ +router.post('/list', function (req, res) { + var userId = req.currentUserId(); + + // Get owned space and all accessed space. + db.Space.find({$or: [{owner: userId}, {usedBy: {$elemMatch: {account: userId}}}]}, function (err, spaces) { + if (db.processed(err, res)) { + var spaceIds = spaces.map(function (value) { + return value._id; + }); + + // Get all presets for spaces. + db.DatabasePreset.find({space: {$in: spaceIds}}).exec(function (err, presets) { + if (db.processed(err, res)) + res.json({spaces: spaces, presets: presets}); + }); + } + }); +}); + +/** + * Save database preset. + */ +router.post('/save', function (req, res) { + var params = req.body; + + db.DatabasePreset.findOne({space: params.space, jdbcDriverJar: params.jdbcDriverJar}, function (err, preset) { + if (db.processed(err, res)) { + if (preset) + db.DatabasePreset.update({space: params.space, jdbcDriverJar: params.jdbcDriverJar}, params, {upsert: true}, function (err) { + if (db.processed(err, res)) + return res.sendStatus(200); + }); + else + (new db.DatabasePreset(params)).save(function (err) { + if (db.processed(err, res)) + return res.sendStatus(200); + }); + } + }); +}); + +module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/profile.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/profile.js b/modules/control-center-web/src/main/js/routes/profile.js new file mode 100644 index 0000000..8f9d324 --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/profile.js @@ -0,0 +1,105 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var router = require('express').Router(); +var db = require('../db'); + +/** + * Get user profile page. + */ +router.get('/', function (req, res) { + var user_id = req.currentUserId(); + + db.Account.findById(user_id, function (err) { + if (err) + return res.status(500).send(err.message); + + res.render('settings/profile'); + }); +}); + +function _updateUser(res, user, params) { + if (params.userName) + user.username = params.userName; + + if (params.email) + user.email = params.email; + + if (params.token) + user.token = params.token; + + if (params.userName || params.email || params.token || params.newPassword) + user.save(function (err) { + if (err) + // TODO IGNITE-843 Send error to admin. + return res.status(500).send('Failed to update profile!'); + + res.json(user); + }); + else + res.status(200); +} + +function _checkUserEmailAndUpdate(res, user, params) { + if (params.email && user.email != params.email) { + db.Account.findOne({email: params.email}, function(err, userForEmail) { + // TODO send error to admin + if (err) + return res.status(500).send('Failed to check e-mail!'); + + if (userForEmail && userForEmail._id != user._id) + return res.status(500).send('User with this e-mail already registered!'); + + _updateUser(res, user, params); + }); + } + else + _updateUser(res, user, params); +} + +/** + * Save user profile. + */ +router.post('/save', function (req, res) { + var params = req.body; + + db.Account.findById(params._id, function (err, user) { + if (err) + // TODO IGNITE-843 Send error to admin + return res.status(500).send('Failed to find user!'); + + if (params.newPassword) { + var newPassword = params.newPassword; + + if (!newPassword || newPassword.length == 0) + return res.status(500).send('Wrong value for new password!'); + + user.setPassword(newPassword, function (err, user) { + if (err) + return res.status(500).send(err.message); + + _checkUserEmailAndUpdate(res, user, params); + }); + } + else + _checkUserEmailAndUpdate(res, user, params); + }); +}); + +module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/public.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/public.js b/modules/control-center-web/src/main/js/routes/public.js new file mode 100644 index 0000000..47b5d56 --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/public.js @@ -0,0 +1,266 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var router = require('express').Router(); +var passport = require('passport'); +var nodemailer = require('nodemailer'); + +var db = require('../db'); +var config = require('../helpers/configuration-loader.js'); +var $commonUtils = require('./../helpers/common-utils'); + +// GET dropdown-menu template. +router.get('/select', function (req, res) { + res.render('templates/select', {}); +}); + +// GET dropdown-menu template. +router.get('/validation-error', function (req, res) { + res.render('templates/validation-error', {}); +}); + +// GET confirmation dialog. +router.get('/message', function (req, res) { + res.render('templates/message', {}); +}); + +// GET confirmation dialog. +router.get('/confirm', function (req, res) { + res.render('templates/confirm', {}); +}); + +// GET batch confirmation dialog. +router.get('/confirm/batch', function (req, res) { + res.render('templates/batch-confirm', {}); +}); + +// GET copy dialog. +router.get('/clone', function (req, res) { + res.render('templates/clone', {}); +}); + +/* GET login dialog. */ +router.get('/login', function (req, res) { + res.render('login'); +}); + +/** + * Register new account. + */ +router.post('/register', function (req, res) { + db.Account.count(function (err, cnt) { + if (err) + return res.status(401).send(err.message); + + req.body.admin = cnt == 0; + + var account = new db.Account(req.body); + + account.token = $commonUtils.randomString(20); + + db.Account.register(account, req.body.password, function (err, account) { + if (err) + return res.status(401).send(err.message); + + if (!account) + return res.status(500).send('Failed to create account.'); + + new db.Space({name: 'Personal space', owner: account._id}).save(); + + req.logIn(account, {}, function (err) { + if (err) + return res.status(401).send(err.message); + + return res.redirect('/configuration/clusters'); + }); + }); + }); +}); + +/** + * Login in exist account. + */ +router.post('/login', function (req, res, next) { + passport.authenticate('local', function (err, user) { + if (err) + return res.status(401).send(err.message); + + if (!user) + return res.status(401).send('Invalid email or password'); + + req.logIn(user, {}, function (err) { + if (err) + return res.status(401).send(err.message); + + res.redirect('/configuration/clusters'); + }); + })(req, res, next); +}); + +/** + * Logout. + */ +router.get('/logout', function (req, res) { + req.logout(); + + res.redirect('/'); +}); + +/** + * Send e-mail to user with reset token. + */ +router.post('/password/forgot', function(req, res) { + var transporter = { + service: config.get('smtp:service'), + auth: { + user:config.get('smtp:username'), + pass: config.get('smtp:password') + } + }; + + if (transporter.service == '' || transporter.auth.user == '' || transporter.auth.pass == '') + return res.status(401).send('Can\'t send e-mail with instructions to reset password.<br />' + + 'Please ask webmaster to setup smtp server!'); + + var token = $commonUtils.randomString(20); + + db.Account.findOne({ email: req.body.email }, function(err, user) { + if (!user) + return res.status(401).send('No account with that email address exists!'); + + if (err) + // TODO IGNITE-843 Send email to admin + return res.status(401).send('Failed to reset password!'); + + user.resetPasswordToken = token; + + user.save(function(err) { + if (err) + // TODO IGNITE-843 Send email to admin + return res.status(401).send('Failed to reset password!'); + + var mailer = nodemailer.createTransport(transporter); + + var mailOptions = { + from: transporter.auth.user, + to: user.email, + subject: 'Password Reset', + text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' + + 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + + 'http://' + req.headers.host + '/password/reset/' + token + '\n\n' + + 'If you did not request this, please ignore this email and your password will remain unchanged.\n\n' + + '--------------\n' + + 'Apache Ignite Web Console\n' + }; + + mailer.sendMail(mailOptions, function(err){ + if (err) + return res.status(401).send('Failed to send e-mail with reset link!<br />' + err); + + return res.status(403).send('An e-mail has been sent with further instructions.'); + }); + }); + }); +}); + +/** + * Change password with given token. + */ +router.post('/password/reset', function(req, res) { + db.Account.findOne({ resetPasswordToken: req.body.token }, function(err, user) { + if (!user) + return res.status(500).send('Invalid token for password reset!'); + + if (err) + // TODO IGNITE-843 Send email to admin + return res.status(500).send('Failed to reset password!'); + + user.setPassword(req.body.password, function (err, updatedUser) { + if (err) + return res.status(500).send(err.message); + + updatedUser.resetPasswordToken = undefined; + + updatedUser.save(function (err) { + if (err) + return res.status(500).send(err.message); + + var transporter = { + service: config.get('smtp:service'), + auth: { + user: config.get('smtp:username'), + pass: config.get('smtp:password') + } + }; + + var mailer = nodemailer.createTransport(transporter); + + var mailOptions = { + from: transporter.auth.user, + to: user.email, + subject: 'Your password has been changed', + text: 'Hello,\n\n' + + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n\n' + + 'Now you can login: http://' + req.headers.host + '\n\n' + + '--------------\n' + + 'Apache Ignite Web Console\n' + }; + + mailer.sendMail(mailOptions, function (err) { + if (err) + return res.status(503).send('Password was changed, but failed to send confirmation e-mail!<br />' + err); + + return res.status(200).send(user.email); + }); + }); + }); + }); +}); + +router.get('/password/reset', function (req, res) { + res.render('reset'); +}); + +/* GET reset password page. */ +router.get('/password/reset/:token', function (req, res) { + var token = req.params.token; + + var data = {token: token}; + + db.Account.findOne({resetPasswordToken: token}, function (err, user) { + if (!user) + data.error = 'Invalid token for password reset!'; + else if (err) + data.error = err; + else + data.email = user.email; + + res.render('reset', data); + }); +}); + +/* GET home page. */ +router.get('/', function (req, res) { + if (req.isAuthenticated()) + res.redirect('/configuration/clusters'); + else + res.render('index'); +}); + +module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/sql.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/sql.js b/modules/control-center-web/src/main/js/routes/sql.js new file mode 100644 index 0000000..306539b --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/sql.js @@ -0,0 +1,39 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var router = require('express').Router(); +var db = require('../db'); + +router.get('/rate', function (req, res) { + res.render('sql/paragraph-rate', {}); +}); + +router.get('/chart-settings', function (req, res) { + res.render('sql/chart-settings', {}); +}); + +router.get('/cache-metadata', function (req, res) { + res.render('sql/cache-metadata', {}); +}); + +router.get('/:noteId', function (req, res) { + res.render('sql/sql', {noteId: req.params.noteId}); +}); + +module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/routes/summary.js ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/routes/summary.js b/modules/control-center-web/src/main/js/routes/summary.js new file mode 100644 index 0000000..911b495 --- /dev/null +++ b/modules/control-center-web/src/main/js/routes/summary.js @@ -0,0 +1,104 @@ +/* + * + * * Licensed to the Apache Software Foundation (ASF) under one or more + * * contributor license agreements. See the NOTICE file distributed with + * * this work for additional information regarding copyright ownership. + * * The ASF licenses this file to You under the Apache License, Version 2.0 + * * (the "License"); you may not use this file except in compliance with + * * the License. You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +var db = require('../db'); + +var router = require('express').Router(); + +var $generatorXml = require('./generator/generator-xml'); +var $generatorJava = require('./generator/generator-java'); +var $generatorDocker = require('./generator/generator-docker'); +var $generatorProperties = require('./generator/generator-properties'); + +// GET template for summary tabs. +router.get('/summary-tabs', function (req, res) { + res.render('configuration/summary-tabs', {}); +}); + +/* GET summary page. */ +router.get('/', function (req, res) { + res.render('configuration/summary'); +}); + +router.post('/download', function (req, res) { + // Get cluster with all inner objects (caches, metadata). + db.Cluster.findById(req.body._id).deepPopulate('caches caches.metadatas').exec(function (err, cluster) { + if (err) + return res.status(500).send(err.message); + + if (!cluster) + return res.sendStatus(404); + + var clientNearConfiguration = req.body.clientNearConfiguration; + + var archiver = require('archiver'); + + // Creating archive. + var zip = archiver('zip'); + + zip.on('error', function (err) { + res.status(500).send({error: err.message}); + }); + + // On stream closed we can end the request. + res.on('close', function () { + return res.status(200).send('OK').end(); + }); + + // Set the archive name. + res.attachment(cluster.name + (clientNearConfiguration ? '-client' : '-server') + '-configuration.zip'); + + // Send the file to the page output. + zip.pipe(res); + + var builder = $generatorProperties.sslProperties(cluster); + + if (!clientNearConfiguration) { + zip.append($generatorDocker.clusterDocker(cluster, req.body.os), {name: 'Dockerfile'}); + + builder = $generatorProperties.dataSourcesProperties(cluster, builder); + } + + if (builder) + zip.append(builder.asString(), {name: 'secret.properties'}); + + zip.append($generatorXml.cluster(cluster, clientNearConfiguration), {name: cluster.name + '.xml'}) + .append($generatorJava.cluster(cluster, false, clientNearConfiguration), + {name: cluster.name + '.snippet.java'}) + .append($generatorJava.cluster(cluster, true, clientNearConfiguration), + {name: 'ConfigurationFactory.java'}); + + $generatorJava.pojos(cluster.caches, req.body.useConstructor, req.body.includeKeyFields); + + var metadatas = $generatorJava.metadatas; + + for (var metaIx = 0; metaIx < metadatas.length; metaIx ++) { + var meta = metadatas[metaIx]; + + if (meta.keyClass) + zip.append(meta.keyClass, {name: meta.keyType.replace(/\./g, '/') + '.java'}); + + zip.append(meta.valueClass, {name: meta.valueType.replace(/\./g, '/') + '.java'}); + } + + zip.finalize(); + }); +}); + +module.exports = router; http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/configuration/caches.jade ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/views/configuration/caches.jade b/modules/control-center-web/src/main/js/views/configuration/caches.jade new file mode 100644 index 0000000..3a8dbfc --- /dev/null +++ b/modules/control-center-web/src/main/js/views/configuration/caches.jade @@ -0,0 +1,46 @@ +//- + 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. + +extends sidebar + +append scripts + script(src='/caches-controller.js') + +include ../includes/controls + +block content + .docs-header + h1 Create and Configure Ignite Caches + .docs-body(ng-controller='cachesController') + div(dw-loading='loadingCachesScreen' dw-loading-options='{text: "Loading caches screen...", className: "page-loading-overlay"}') + div(ng-show='ui.ready') + +block-callout('{{screenTip.workflowTitle}}', 'screenTip.workflowContent', '{{screenTip.whatsNextTitle}}', 'screenTip.whatsNextContent') + hr + +main-table('Caches:', 'caches', 'cacheName', 'selectItem(row)', '{{$index + 1}}) {{row.name}}, {{row.cacheMode | displayValue:cacheModes:"Cache mode not set"}}, {{row.atomicityMode | displayValue:atomicities:"Cache atomicity not set"}}') + .padding-top-dflt(bs-affix) + .panel-tip-container(data-placement='bottom' bs-tooltip data-title='Create new caches') + button.btn.btn-primary(id='new-item' ng-click='createItem()') Add cache + +save-remove-buttons('cache') + hr + form.form-horizontal(name='ui.inputForm' ng-if='backupItem' novalidate unsaved-warning-form) + .panel-group(bs-collapse ng-model='panels.activePanels' data-allow-multiple='true') + +groups('general', 'backupItem') + div(ng-show='ui.expanded') + +advanced-options + +groups('advanced', 'backupItem') + +advanced-options + .section(ng-if='ui.expanded') + +save-remove-buttons('cache') http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/configuration/clusters.jade ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/views/configuration/clusters.jade b/modules/control-center-web/src/main/js/views/configuration/clusters.jade new file mode 100644 index 0000000..3ce51aa --- /dev/null +++ b/modules/control-center-web/src/main/js/views/configuration/clusters.jade @@ -0,0 +1,46 @@ +//- + 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. + +extends sidebar + +append scripts + script(src='/clusters-controller.js') + +include ../includes/controls + +block content + .docs-header + h1 Create and Configure Ignite Clusters + .docs-body(ng-controller='clustersController') + div(dw-loading='loadingClustersScreen' dw-loading-options='{text: "Loading clusters screen...", className: "page-loading-overlay"}') + div(ng-show='ui.ready') + +block-callout('{{screenTip.workflowTitle}}', 'screenTip.workflowContent', '{{screenTip.whatsNextTitle}}', 'screenTip.whatsNextContent') + hr + +main-table('Clusters:', 'clusters', 'clusterName', 'selectItem(row)', '{{$index + 1}}) {{row.name}}, {{row.discovery.kind | displayValue:discoveries:"Discovery not set"}}') + .padding-top-dflt(bs-affix) + .panel-tip-container(data-placement='bottom' bs-tooltip data-title='Create new cluster') + button.btn.btn-primary(id='new-item' ng-click='createItem()') Add cluster + +save-remove-buttons('cluster') + hr + form.form-horizontal(name='ui.inputForm' ng-if='backupItem' novalidate unsaved-warning-form) + .panel-group(bs-collapse ng-model='panels.activePanels' data-allow-multiple='true' ng-click='triggerDigest = true') + +groups('general', 'backupItem') + div(ng-show='ui.expanded') + +advanced-options + +groups('advanced', 'backupItem') + +advanced-options + .section(ng-show='ui.expanded') + +save-remove-buttons('cluster') http://git-wip-us.apache.org/repos/asf/ignite/blob/bce0deb7/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade ---------------------------------------------------------------------- diff --git a/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade b/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade new file mode 100644 index 0000000..42e7798 --- /dev/null +++ b/modules/control-center-web/src/main/js/views/configuration/metadata-load.jade @@ -0,0 +1,89 @@ +//- + 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. + +include ../includes/controls + +mixin chk(mdl, change, tip) + input(type='checkbox' ng-model=mdl ng-change=change bs-tooltip='' data-title=tip data-placement='bottom') + +.modal.center(role='dialog') + .modal-dialog + .modal-content(dw-loading='loadingMetadataFromDb' dw-loading-options='{text: ""}') + #errors-container.modal-header.header + button.close(ng-click='$hide()' aria-hidden='true') × + h4.modal-title Load metadata from database + .metadata-content(ng-show='loadMeta.action == "connect"' style='margin-bottom: 60px') + form.form-horizontal(name='loadForm' novalidate) + .settings-row(ng-repeat='field in metadataDb') + +form-row-custom(['col-xs-4 col-sm-3 col-md-3'], ['col-xs-8 col-sm-9 col-md-9'], 'preset') + .metadata-content(ng-show='loadMeta.action == "schemas"') + table.table.metadata(st-table='loadMeta.displayedSchemas' st-safe-src='loadMeta.schemas') + thead + tr + th.header(colspan='2') + .col-sm-4.pull-right + input.form-control(type='text' st-search='' placeholder='Filter schemas...' ng-model='loadMeta.displayedSchemasFilter' ng-change='selectSchema()') + tr + th(width='50px') + +chk('loadMeta.allSchemasSelected', 'selectAllSchemas()', 'Select all schemas') + th + label Schemas + tbody + tr + td(colspan='2') + .scrollable-y(style='height: 184px') + table.table-modal-striped(id='metadataSchemaData') + tbody + tr(ng-repeat='schema in loadMeta.displayedSchemas') + td(width='50px') + input(type='checkbox' ng-model='schema.use' ng-change='selectSchema()') + td + label {{::schema.name}} + .metadata-content(ng-show='loadMeta.action == "tables"') + .metadata-package-name + label.required Package: + span + input.form-control(id='metadataLoadPackage' type="text" ng-model='ui.packageName' placeholder='Package for POJOs generation' bs-tooltip='' data-title='Package that will be used for POJOs generation' data-placement='top' data-trigger='hover') + table.table.metadata(st-table='loadMeta.displayedTables' st-safe-src='loadMeta.tables') + thead + tr + th.header(colspan='3') + .col-sm-4.pull-right + input.form-control(type='text' st-search='' placeholder='Filter tables...' ng-model='loadMeta.displayedTablesFilter' ng-change='selectTable()') + tr + th(width='50px') + +chk('loadMeta.allTablesSelected', 'selectAllTables()', 'Select all tables') + th(width='200px') + label Schemas + th + label Tables + tbody + tr + td(colspan='3') + .scrollable-y(style='height: 146px') + table.table-modal-striped(id='metadataTableData') + tbody + tr(ng-repeat='table in loadMeta.displayedTables') + td(width='50px') + input(type='checkbox' ng-model='table.use' ng-change='selectTable()') + td(width='200px') + label {{::table.schema}} + td + label {{::table.tbl}} + .modal-footer + label.labelField {{loadMeta.info}} + button.btn.btn-primary(ng-show='loadMeta.action != "connect"' ng-click='loadMetadataPrev()') Prev + a.btn.btn-primary(ng-click='loadMetadataNext()' ng-disabled='!nextAvailable()' bs-tooltip data-title='{{nextTooltipText()}}' data-placement='bottom') {{loadMeta.button}}
