You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2016/09/13 09:53:23 UTC
[31/69] [abbrv] ignite git commit: Web Console beta-3.
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/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
new file mode 100644
index 0000000..5e7943a
--- /dev/null
+++ b/modules/web-console/frontend/app/services/FormUtils.service.js
@@ -0,0 +1,435 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default ['IgniteFormUtils', ['$window', 'IgniteFocus', ($window, Focus) => {
+ function ensureActivePanel(ui, pnl, focusId) {
+ if (ui) {
+ const collapses = $('div.panel-collapse');
+
+ ui.loadPanel(pnl);
+
+ const idx = _.findIndex(collapses, function(collapse) {
+ return collapse.id === pnl;
+ });
+
+ if (idx >= 0) {
+ const activePanels = ui.activePanels;
+
+ if (!_.includes(ui.topPanels, idx)) {
+ ui.expanded = true;
+
+ const customExpanded = ui[pnl];
+
+ if (customExpanded)
+ ui[customExpanded] = true;
+ }
+
+ if (!activePanels || activePanels.length < 1)
+ ui.activePanels = [idx];
+ else if (!_.includes(activePanels, idx)) {
+ const newActivePanels = angular.copy(activePanels);
+
+ newActivePanels.push(idx);
+
+ ui.activePanels = newActivePanels;
+ }
+ }
+
+ if (!_.isNil(focusId))
+ Focus.move(focusId);
+ }
+ }
+
+ let context = null;
+
+ /**
+ * Calculate width of specified text in body's font.
+ *
+ * @param text Text to calculate width.
+ * @returns {Number} Width of text in pixels.
+ */
+ function measureText(text) {
+ if (!context) {
+ const canvas = document.createElement('canvas');
+
+ context = canvas.getContext('2d');
+
+ const style = window.getComputedStyle(document.getElementsByTagName('body')[0]);
+
+ context.font = style.fontSize + ' ' + style.fontFamily;
+ }
+
+ return context.measureText(text).width;
+ }
+
+ /**
+ * Compact java full class name by max number of characters.
+ *
+ * @param names Array of class names to compact.
+ * @param nameLength Max available width in characters for simple name.
+ * @returns {*} Array of compacted class names.
+ */
+ function compactByMaxCharts(names, nameLength) {
+ for (let nameIx = 0; nameIx < names.length; nameIx++) {
+ const s = names[nameIx];
+
+ if (s.length > nameLength) {
+ let totalLength = s.length;
+
+ const packages = s.split('.');
+
+ const packageCnt = packages.length - 1;
+
+ for (let i = 0; i < packageCnt && totalLength > nameLength; i++) {
+ if (packages[i].length > 0) {
+ totalLength -= packages[i].length - 1;
+
+ packages[i] = packages[i][0];
+ }
+ }
+
+ if (totalLength > nameLength) {
+ const className = packages[packageCnt];
+
+ const classNameLen = className.length;
+
+ let remains = Math.min(nameLength - totalLength + classNameLen, classNameLen);
+
+ if (remains < 3)
+ remains = Math.min(3, classNameLen);
+
+ packages[packageCnt] = className.substring(0, remains) + '...';
+ }
+
+ let result = packages[0];
+
+ for (let i = 1; i < packages.length; i++)
+ result += '.' + packages[i];
+
+ names[nameIx] = result;
+ }
+ }
+
+ return names;
+ }
+
+ /**
+ * Compact java full class name by max number of pixels.
+ *
+ * @param names Array of class names to compact.
+ * @param nameLength Max available width in characters for simple name. Used for calculation optimization.
+ * @param nameWidth Maximum available width in pixels for simple name.
+ * @returns {*} Array of compacted class names.
+ */
+ function compactByMaxPixels(names, nameLength, nameWidth) {
+ if (nameWidth <= 0)
+ return names;
+
+ const fitted = [];
+
+ const widthByName = [];
+
+ const len = names.length;
+
+ let divideTo = len;
+
+ for (let nameIx = 0; nameIx < len; nameIx++) {
+ fitted[nameIx] = false;
+
+ widthByName[nameIx] = nameWidth;
+ }
+
+ // Try to distribute space from short class names to long class names.
+ let remains = 0;
+
+ do {
+ for (let nameIx = 0; nameIx < len; nameIx++) {
+ if (!fitted[nameIx]) {
+ const curNameWidth = measureText(names[nameIx]);
+
+ if (widthByName[nameIx] > curNameWidth) {
+ fitted[nameIx] = true;
+
+ remains += widthByName[nameIx] - curNameWidth;
+
+ divideTo -= 1;
+
+ widthByName[nameIx] = curNameWidth;
+ }
+ }
+ }
+
+ const remainsByName = remains / divideTo;
+
+ for (let nameIx = 0; nameIx < len; nameIx++) {
+ if (!fitted[nameIx])
+ widthByName[nameIx] += remainsByName;
+ }
+ }
+ while (remains > 0);
+
+ // Compact class names to available for each space.
+ for (let nameIx = 0; nameIx < len; nameIx++) {
+ const s = names[nameIx];
+
+ if (s.length > (nameLength / 2 | 0)) {
+ let totalWidth = measureText(s);
+
+ if (totalWidth > widthByName[nameIx]) {
+ const packages = s.split('.');
+
+ const packageCnt = packages.length - 1;
+
+ for (let i = 0; i < packageCnt && totalWidth > widthByName[nameIx]; i++) {
+ if (packages[i].length > 1) {
+ totalWidth -= measureText(packages[i].substring(1, packages[i].length));
+
+ packages[i] = packages[i][0];
+ }
+ }
+
+ let shortPackage = '';
+
+ for (let i = 0; i < packageCnt; i++)
+ shortPackage += packages[i] + '.';
+
+ const className = packages[packageCnt];
+
+ const classLen = className.length;
+
+ let minLen = Math.min(classLen, 3);
+
+ totalWidth = measureText(shortPackage + className);
+
+ // Compact class name if shorten package path is very long.
+ if (totalWidth > widthByName[nameIx]) {
+ let maxLen = classLen;
+ let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+
+ while (middleLen !== minLen && middleLen !== maxLen) {
+ const middleLenPx = measureText(shortPackage + className.substr(0, middleLen) + '...');
+
+ if (middleLenPx > widthByName[nameIx])
+ maxLen = middleLen;
+ else
+ minLen = middleLen;
+
+ middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+ }
+
+ names[nameIx] = shortPackage + className.substring(0, middleLen) + '...';
+ }
+ else
+ names[nameIx] = shortPackage + className;
+ }
+ }
+ }
+
+ return names;
+ }
+
+ /**
+ * Compact any string by max number of pixels.
+ *
+ * @param label String to compact.
+ * @param nameWidth Maximum available width in pixels for simple name.
+ * @returns {*} Compacted string.
+ */
+ function compactLabelByPixels(label, nameWidth) {
+ if (nameWidth <= 0)
+ return label;
+
+ const totalWidth = measureText(label);
+
+ if (totalWidth > nameWidth) {
+ let maxLen = label.length;
+ let minLen = Math.min(maxLen, 3);
+ let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+
+ while (middleLen !== minLen && middleLen !== maxLen) {
+ const middleLenPx = measureText(label.substr(0, middleLen) + '...');
+
+ if (middleLenPx > nameWidth)
+ maxLen = middleLen;
+ else
+ minLen = middleLen;
+
+ middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
+ }
+
+ return label.substring(0, middleLen) + '...';
+ }
+
+ return label;
+ }
+
+ /**
+ * Calculate available width for text in link to edit element.
+ *
+ * @param index Showed index of element for calculation of maximum width in pixels.
+ * @param id Id of contains link table.
+ * @returns {*[]} First element is length of class for single value, second element is length for pair vlaue.
+ */
+ function availableWidth(index, id) {
+ const idElem = $('#' + id);
+
+ let width = 0;
+
+ switch (idElem.prop('tagName')) {
+ // Detection of available width in presentation table row.
+ case 'TABLE':
+ const cont = $(idElem.find('tr')[index - 1]).find('td')[0];
+
+ width = cont.clientWidth;
+
+ if (width > 0) {
+ const children = $(cont).children(':not("a")');
+
+ _.forEach(children, function(child) {
+ if ('offsetWidth' in child)
+ width -= $(child).outerWidth(true);
+ });
+ }
+
+ break;
+
+ // Detection of available width in dropdown row.
+ case 'A':
+ width = idElem.width();
+
+ $(idElem).children(':not("span")').each(function(ix, child) {
+ if ('offsetWidth' in child)
+ width -= child.offsetWidth;
+ });
+
+ break;
+
+ default:
+ }
+
+ return width | 0;
+ }
+
+ return {
+ /**
+ * Cut class name by width in pixel or width in symbol count.
+ *
+ * @param id Id of parent table.
+ * @param index Row number in table.
+ * @param maxLength Maximum length in symbols for all names.
+ * @param names Array of class names to compact.
+ * @param divider String to visualy divide items.
+ * @returns {*} Array of compacted class names.
+ */
+ compactJavaName(id, index, maxLength, names, divider) {
+ divider = ' ' + divider + ' ';
+
+ const prefix = index + ') ';
+
+ const nameCnt = names.length;
+
+ const nameLength = ((maxLength - 3 * (nameCnt - 1)) / nameCnt) | 0;
+
+ try {
+ const nameWidth = (availableWidth(index, id) - measureText(prefix) - (nameCnt - 1) * measureText(divider)) /
+ nameCnt | 0;
+
+ // HTML5 calculation of showed message width.
+ names = compactByMaxPixels(names, nameLength, nameWidth);
+ }
+ catch (err) {
+ names = compactByMaxCharts(names, nameLength);
+ }
+
+ let result = prefix + names[0];
+
+ for (let nameIx = 1; nameIx < names.length; nameIx++)
+ result += divider + names[nameIx];
+
+ return result;
+ },
+ /**
+ * Compact text by width in pixels or symbols count.
+ *
+ * @param id Id of parent table.
+ * @param index Row number in table.
+ * @param maxLength Maximum length in symbols for all names.
+ * @param label Text to compact.
+ * @returns Compacted label text.
+ */
+ compactTableLabel(id, index, maxLength, label) {
+ label = index + ') ' + label;
+
+ try {
+ const nameWidth = availableWidth(index, id) | 0;
+
+ // HTML5 calculation of showed message width.
+ label = compactLabelByPixels(label, nameWidth);
+ }
+ catch (err) {
+ const nameLength = maxLength - 3 | 0;
+
+ label = label.length > maxLength ? label.substr(0, nameLength) + '...' : label;
+ }
+
+ return label;
+ },
+ widthIsSufficient(id, index, text) {
+ try {
+ const available = availableWidth(index, id);
+
+ const required = measureText(text);
+
+ return !available || available >= Math.floor(required);
+ }
+ catch (err) {
+ return true;
+ }
+ },
+ ensureActivePanel(panels, id, focusId) {
+ ensureActivePanel(panels, id, focusId);
+ },
+ confirmUnsavedChanges(dirty, selectFunc) {
+ if (dirty) {
+ if ($window.confirm('You have unsaved changes.\n\nAre you sure you want to discard them?'))
+ selectFunc();
+ }
+ else
+ selectFunc();
+ },
+ saveBtnTipText(dirty, objectName) {
+ if (dirty)
+ return 'Save ' + objectName;
+
+ return 'Nothing to save';
+ },
+ formUI() {
+ return {
+ ready: false,
+ expanded: false,
+ loadedPanels: [],
+ loadPanel(pnl) {
+ if (!_.includes(this.loadedPanels, pnl))
+ this.loadedPanels.push(pnl);
+ },
+ isPanelLoaded(pnl) {
+ return _.includes(this.loadedPanels, pnl);
+ }
+ };
+ }
+ };
+}]];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/frontend/app/services/InetAddress.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/services/InetAddress.service.js b/modules/web-console/frontend/app/services/InetAddress.service.js
new file mode 100644
index 0000000..abdd8a3
--- /dev/null
+++ b/modules/web-console/frontend/app/services/InetAddress.service.js
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default ['IgniteInetAddress', function() {
+ return {
+ /**
+ * @param {String} ip IP address to check.
+ * @returns {boolean} 'true' if given ip address is valid.
+ */
+ validIp(ip) {
+ const regexp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
+
+ return regexp.test(ip);
+ },
+ /**
+ * @param {String} hostNameOrIp host name or ip address to check.
+ * @returns {boolean} 'true' if given is host name or ip.
+ */
+ validHost(hostNameOrIp) {
+ const regexp = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
+
+ return regexp.test(hostNameOrIp) || this.validIp(hostNameOrIp);
+ },
+ /**
+ * @param {int} port Port value to check.
+ * @returns boolean 'true' if given port is valid tcp/udp port range.
+ */
+ validPort(port) {
+ return _.isInteger(port) && port > 0 && port <= 65535;
+ },
+ /**
+ * @param {int} port Port value to check.
+ * @returns {boolean} 'true' if given port in non system port range(user+dynamic).
+ */
+ validNonSystemPort(port) {
+ return _.isInteger(port) && port >= 1024 && port <= 65535;
+ }
+ };
+}];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/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
new file mode 100644
index 0000000..e8d4903
--- /dev/null
+++ b/modules/web-console/frontend/app/services/JavaTypes.service.js
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Java built-in class names.
+import JAVA_CLASSES from '../data/java-classes.json';
+
+// Java build-in primitive.
+import JAVA_PRIMITIVES from '../data/java-primitives.json';
+
+import JAVA_KEYWORDS from '../data/java-keywords.json';
+
+export default ['JavaTypes', function() {
+ return {
+ /**
+ * @param {String} clsName Class name to check.
+ * @returns boolean 'true' if given class name non a Java built-in type.
+ */
+ nonBuiltInClass(clsName) {
+ return _.isNil(_.find(JAVA_CLASSES, (clazz) => clsName === clazz.short || clsName === clazz.full));
+ },
+ /**
+ * @param clsName Class name to check.
+ * @returns Full class name for java build-in types or source class otherwise.
+ */
+ fullClassName(clsName) {
+ const type = _.find(JAVA_CLASSES, (clazz) => clsName === clazz.short);
+
+ return type ? type.full : clsName;
+ },
+ /**
+ * @param {String} value text to check.
+ * @returns boolean 'true' if given text is valid Java identifier.
+ */
+ validIdentifier(value) {
+ const regexp = /^(([a-zA-Z_$][a-zA-Z0-9_$]*)\.)*([a-zA-Z_$][a-zA-Z0-9_$]*)$/igm;
+
+ return value === '' || regexp.test(value);
+ },
+ /**
+ * @param {String} value text to check.
+ * @returns boolean 'true' if given text is valid Java package.
+ */
+ validPackage(value) {
+ const regexp = /^(([a-zA-Z_$][a-zA-Z0-9_$]*)\.)*([a-zA-Z_$][a-zA-Z0-9_$]*(\.?\*)?)$/igm;
+
+ return value === '' || regexp.test(value);
+ },
+ /**
+ * @param {String} value text to check.
+ * @returns boolean 'true' if given text is valid Java UUID value.
+ */
+ validUUID(value) {
+ const regexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/igm;
+
+ return value === '' || regexp.test(value);
+ },
+ /**
+ * @param {String} value text to check.
+ * @returns boolean 'true' if given text is a Java type with package.
+ */
+ packageSpecified(value) {
+ return value.split('.').length >= 2;
+ },
+ /**
+ * @param {String} value text to check.
+ * @returns boolean 'true' if given text non Java keyword.
+ */
+ isKeywords(value) {
+ return _.includes(JAVA_KEYWORDS, value);
+ },
+ /**
+ * @param {String} clsName Class name to check.
+ * @returns {boolean} 'true' if givent class name is java primitive.
+ */
+ isJavaPrimitive(clsName) {
+ return _.includes(JAVA_PRIMITIVES, clsName);
+ }
+ };
+}];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/frontend/app/services/LegacyTable.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/services/LegacyTable.service.js b/modules/web-console/frontend/app/services/LegacyTable.service.js
new file mode 100644
index 0000000..5d9ec9d
--- /dev/null
+++ b/modules/web-console/frontend/app/services/LegacyTable.service.js
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Refactor this service for legacy tables with more than one input field.
+export default ['IgniteLegacyTable',
+ ['IgniteLegacyUtils', 'IgniteFocus', 'IgniteErrorPopover', (LegacyUtils, Focus, ErrorPopover) => {
+ function _model(item, field) {
+ return LegacyUtils.getModel(item, field);
+ }
+
+ const table = {name: 'none', editIndex: -1};
+
+ function _tableReset() {
+ delete table.field;
+ table.name = 'none';
+ table.editIndex = -1;
+
+ ErrorPopover.hide();
+ }
+
+ function _tableSaveAndReset() {
+ const field = table.field;
+
+ const save = LegacyUtils.isDefined(field) && LegacyUtils.isDefined(field.save);
+
+ if (!save || !LegacyUtils.isDefined(field) || field.save(field, table.editIndex, true)) {
+ _tableReset();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function _tableState(field, editIndex, specName) {
+ table.field = field;
+ table.name = specName || field.model;
+ table.editIndex = editIndex;
+ }
+
+ function _tableUI(field) {
+ const ui = field.ui;
+
+ return ui ? ui : field.type;
+ }
+
+ function _tableFocus(focusId, index) {
+ Focus.move((index < 0 ? 'new' : 'cur') + focusId + (index >= 0 ? index : ''));
+ }
+
+ function _tablePairValue(filed, index) {
+ return index < 0 ? {key: filed.newKey, value: filed.newValue} : {
+ key: filed.curKey,
+ value: filed.curValue
+ };
+ }
+
+ function _tableStartEdit(item, tbl, index, save) {
+ _tableState(tbl, index);
+
+ const val = _.get(_model(item, tbl), tbl.model)[index];
+
+ const ui = _tableUI(tbl);
+
+ tbl.save = save;
+
+ if (ui === 'table-pair') {
+ tbl.curKey = val[tbl.keyName];
+ tbl.curValue = val[tbl.valueName];
+
+ _tableFocus('Key' + tbl.focusId, index);
+ }
+ else if (ui === 'table-db-fields') {
+ tbl.curDatabaseFieldName = val.databaseFieldName;
+ tbl.curDatabaseFieldType = val.databaseFieldType;
+ tbl.curJavaFieldName = val.javaFieldName;
+ tbl.curJavaFieldType = val.javaFieldType;
+
+ _tableFocus('DatabaseFieldName' + tbl.focusId, index);
+ }
+ else if (ui === 'table-indexes') {
+ tbl.curIndexName = val.name;
+ tbl.curIndexType = val.indexType;
+ tbl.curIndexFields = val.fields;
+
+ _tableFocus(tbl.focusId, index);
+ }
+ }
+
+ function _tableNewItem(tbl) {
+ _tableState(tbl, -1);
+
+ const ui = _tableUI(tbl);
+
+ if (ui === 'table-pair') {
+ tbl.newKey = null;
+ tbl.newValue = null;
+
+ _tableFocus('Key' + tbl.focusId, -1);
+ }
+ else if (ui === 'table-db-fields') {
+ tbl.newDatabaseFieldName = null;
+ tbl.newDatabaseFieldType = null;
+ tbl.newJavaFieldName = null;
+ tbl.newJavaFieldType = null;
+
+ _tableFocus('DatabaseFieldName' + tbl.focusId, -1);
+ }
+ else if (ui === 'table-indexes') {
+ tbl.newIndexName = null;
+ tbl.newIndexType = 'SORTED';
+ tbl.newIndexFields = null;
+
+ _tableFocus(tbl.focusId, -1);
+ }
+ }
+
+ return {
+ tableState: _tableState,
+ tableReset: _tableReset,
+ tableSaveAndReset: _tableSaveAndReset,
+ tableNewItem: _tableNewItem,
+ tableNewItemActive(tbl) {
+ return table.name === tbl.model && table.editIndex < 0;
+ },
+ tableEditing(tbl, index) {
+ return table.name === tbl.model && table.editIndex === index;
+ },
+ tableEditedRowIndex() {
+ return table.editIndex;
+ },
+ tableField() {
+ return table.field;
+ },
+ tableStartEdit: _tableStartEdit,
+ tableRemove(item, field, index) {
+ _tableReset();
+
+ _.get(_model(item, field), field.model).splice(index, 1);
+ },
+ tablePairValue: _tablePairValue,
+ tablePairSave(pairValid, item, field, index, stopEdit) {
+ const valid = pairValid(item, field, index, stopEdit);
+
+ if (valid) {
+ const pairValue = _tablePairValue(field, index);
+
+ let pairModel = {};
+
+ const container = _.get(item, field.model);
+
+ if (index < 0) {
+ pairModel[field.keyName] = pairValue.key;
+ pairModel[field.valueName] = pairValue.value;
+
+ if (container)
+ container.push(pairModel);
+ else
+ _.set(item, field.model, [pairModel]);
+
+ if (!stopEdit)
+ _tableNewItem(field);
+ }
+ else {
+ pairModel = container[index];
+
+ pairModel[field.keyName] = pairValue.key;
+ pairModel[field.valueName] = pairValue.value;
+
+ if (!stopEdit) {
+ if (index < container.length - 1)
+ _tableStartEdit(item, field, index + 1);
+ else
+ _tableNewItem(field);
+ }
+ }
+ }
+
+ return valid;
+ },
+ tablePairSaveVisible(field, index) {
+ const pairValue = _tablePairValue(field, index);
+
+ return !LegacyUtils.isEmptyString(pairValue.key) && !LegacyUtils.isEmptyString(pairValue.value);
+ },
+ tableFocusInvalidField(index, id) {
+ _tableFocus(id, index);
+
+ return false;
+ },
+ tableFieldId(index, id) {
+ return (index < 0 ? 'new' : 'cur') + id + (index >= 0 ? index : '');
+ }
+ };
+ }]];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/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
new file mode 100644
index 0000000..ed555a1
--- /dev/null
+++ b/modules/web-console/frontend/app/services/LegacyUtils.service.js
@@ -0,0 +1,572 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO: Refactor this service for legacy tables with more than one input field.
+export default ['IgniteLegacyUtils', ['IgniteErrorPopover', (ErrorPopover) => {
+ function isDefined(v) {
+ return !_.isNil(v);
+ }
+
+ function isEmptyString(s) {
+ if (isDefined(s))
+ return s.trim().length === 0;
+
+ return true;
+ }
+
+ const javaBuiltInClasses = [
+ 'BigDecimal',
+ 'Boolean',
+ 'Byte',
+ 'Date',
+ 'Double',
+ 'Float',
+ 'Integer',
+ 'Long',
+ 'Object',
+ 'Short',
+ 'String',
+ 'Time',
+ 'Timestamp',
+ 'UUID'
+ ];
+
+ const javaBuiltInTypes = [
+ 'BigDecimal',
+ 'boolean',
+ 'Boolean',
+ 'byte',
+ 'Byte',
+ 'Date',
+ 'double',
+ 'Double',
+ 'float',
+ 'Float',
+ 'int',
+ 'Integer',
+ 'long',
+ 'Long',
+ 'Object',
+ 'short',
+ 'Short',
+ 'String',
+ 'Time',
+ 'Timestamp',
+ 'UUID'
+ ];
+
+ const javaBuiltInFullNameClasses = [
+ 'java.math.BigDecimal',
+ 'java.lang.Boolean',
+ 'java.lang.Byte',
+ 'java.sql.Date',
+ 'java.lang.Double',
+ 'java.lang.Float',
+ 'java.lang.Integer',
+ 'java.lang.Long',
+ 'java.lang.Object',
+ 'java.lang.Short',
+ 'java.lang.String',
+ 'java.sql.Time',
+ 'java.sql.Timestamp',
+ 'java.util.UUID'
+ ];
+
+ /**
+ * @param clsName Class name to check.
+ * @returns {Boolean} 'true' if given class name is a java build-in type.
+ */
+ function isJavaBuiltInClass(clsName) {
+ if (isEmptyString(clsName))
+ return false;
+
+ return _.includes(javaBuiltInClasses, clsName) || _.includes(javaBuiltInFullNameClasses, clsName);
+ }
+
+ const SUPPORTED_JDBC_TYPES = [
+ 'BIGINT',
+ 'BIT',
+ 'BOOLEAN',
+ 'BLOB',
+ 'CHAR',
+ 'CLOB',
+ 'DATE',
+ 'DECIMAL',
+ 'DOUBLE',
+ 'FLOAT',
+ 'INTEGER',
+ 'LONGNVARCHAR',
+ 'LONGVARCHAR',
+ 'NCHAR',
+ 'NUMERIC',
+ 'NVARCHAR',
+ 'REAL',
+ 'SMALLINT',
+ 'TIME',
+ 'TIMESTAMP',
+ 'TINYINT',
+ 'VARCHAR'
+ ];
+
+ const ALL_JDBC_TYPES = [
+ {dbName: 'BIT', dbType: -7, javaType: 'Boolean', primitiveType: 'boolean'},
+ {dbName: 'TINYINT', dbType: -6, javaType: 'Byte', primitiveType: 'byte'},
+ {dbName: 'SMALLINT', dbType: 5, javaType: 'Short', primitiveType: 'short'},
+ {dbName: 'INTEGER', dbType: 4, javaType: 'Integer', primitiveType: 'int'},
+ {dbName: 'BIGINT', dbType: -5, javaType: 'Long', primitiveType: 'long'},
+ {dbName: 'FLOAT', dbType: 6, javaType: 'Float', primitiveType: 'float'},
+ {dbName: 'REAL', dbType: 7, javaType: 'Double', primitiveType: 'double'},
+ {dbName: 'DOUBLE', dbType: 8, javaType: 'Double', primitiveType: 'double'},
+ {dbName: 'NUMERIC', dbType: 2, javaType: 'BigDecimal'},
+ {dbName: 'DECIMAL', dbType: 3, javaType: 'BigDecimal'},
+ {dbName: 'CHAR', dbType: 1, javaType: 'String'},
+ {dbName: 'VARCHAR', dbType: 12, javaType: 'String'},
+ {dbName: 'LONGVARCHAR', dbType: -1, javaType: 'String'},
+ {dbName: 'DATE', dbType: 91, javaType: 'Date'},
+ {dbName: 'TIME', dbType: 92, javaType: 'Time'},
+ {dbName: 'TIMESTAMP', dbType: 93, javaType: 'Timestamp'},
+ {dbName: 'BINARY', dbType: -2, javaType: 'Object'},
+ {dbName: 'VARBINARY', dbType: -3, javaType: 'Object'},
+ {dbName: 'LONGVARBINARY', dbType: -4, javaType: 'Object'},
+ {dbName: 'NULL', dbType: 0, javaType: 'Object'},
+ {dbName: 'OTHER', dbType: 1111, javaType: 'Object'},
+ {dbName: 'JAVA_OBJECT', dbType: 2000, javaType: 'Object'},
+ {dbName: 'DISTINCT', dbType: 2001, javaType: 'Object'},
+ {dbName: 'STRUCT', dbType: 2002, javaType: 'Object'},
+ {dbName: 'ARRAY', dbType: 2003, javaType: 'Object'},
+ {dbName: 'BLOB', dbType: 2004, javaType: 'Object'},
+ {dbName: 'CLOB', dbType: 2005, javaType: 'String'},
+ {dbName: 'REF', dbType: 2006, javaType: 'Object'},
+ {dbName: 'DATALINK', dbType: 70, javaType: 'Object'},
+ {dbName: 'BOOLEAN', dbType: 16, javaType: 'Boolean', primitiveType: 'boolean'},
+ {dbName: 'ROWID', dbType: -8, javaType: 'Object'},
+ {dbName: 'NCHAR', dbType: -15, javaType: 'String'},
+ {dbName: 'NVARCHAR', dbType: -9, javaType: 'String'},
+ {dbName: 'LONGNVARCHAR', dbType: -16, javaType: 'String'},
+ {dbName: 'NCLOB', dbType: 2011, javaType: 'String'},
+ {dbName: 'SQLXML', dbType: 2009, javaType: 'Object'}
+ ];
+
+ /*eslint-disable */
+ const JAVA_KEYWORDS = [
+ 'abstract',
+ 'assert',
+ 'boolean',
+ 'break',
+ 'byte',
+ 'case',
+ 'catch',
+ 'char',
+ 'class',
+ 'const',
+ 'continue',
+ 'default',
+ 'do',
+ 'double',
+ 'else',
+ 'enum',
+ 'extends',
+ 'false',
+ 'final',
+ 'finally',
+ 'float',
+ 'for',
+ 'goto',
+ 'if',
+ 'implements',
+ 'import',
+ 'instanceof',
+ 'int',
+ 'interface',
+ 'long',
+ 'native',
+ 'new',
+ 'null',
+ 'package',
+ 'private',
+ 'protected',
+ 'public',
+ 'return',
+ 'short',
+ 'static',
+ 'strictfp',
+ 'super',
+ 'switch',
+ 'synchronized',
+ 'this',
+ 'throw',
+ 'throws',
+ 'transient',
+ 'true',
+ 'try',
+ 'void',
+ 'volatile',
+ 'while'
+ ];
+ /*eslint-enable */
+
+ const VALID_JAVA_IDENTIFIER = new RegExp('^[a-zA-Z_$][a-zA-Z\\d_$]*$');
+
+ function isValidJavaIdentifier(msg, ident, elemId, panels, panelId) {
+ if (isEmptyString(ident))
+ return ErrorPopover.show(elemId, msg + ' is invalid!', panels, panelId);
+
+ if (_.includes(JAVA_KEYWORDS, ident))
+ return ErrorPopover.show(elemId, msg + ' could not contains reserved java keyword: "' + ident + '"!', panels, panelId);
+
+ if (!VALID_JAVA_IDENTIFIER.test(ident))
+ return ErrorPopover.show(elemId, msg + ' contains invalid identifier: "' + ident + '"!', panels, panelId);
+
+ return true;
+ }
+
+ function getModel(obj, field) {
+ let path = field.path;
+
+ if (!isDefined(path) || !isDefined(obj))
+ return obj;
+
+ path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
+ path = path.replace(/^\./, ''); // strip a leading dot
+
+ const segs = path.split('.');
+ let root = obj;
+
+ while (segs.length > 0) {
+ const pathStep = segs.shift();
+
+ if (typeof root[pathStep] === 'undefined')
+ root[pathStep] = {};
+
+ root = root[pathStep];
+ }
+
+ return root;
+ }
+
+ /**
+ * Extract datasource from cache or cluster.
+ *
+ * @param object Cache or cluster to extract datasource.
+ * @returns {*} Datasource object or null if not set.
+ */
+ function extractDataSource(object) {
+ // Extract from cluster object
+ if (_.get(object, 'discovery.kind') === 'Jdbc') {
+ const datasource = object.discovery.Jdbc;
+
+ if (datasource.dataSourceBean && datasource.dialect)
+ return datasource;
+ } // Extract from cache object
+ else if (_.get(object, 'cacheStoreFactory.kind')) {
+ const storeFactory = object.cacheStoreFactory[object.cacheStoreFactory.kind];
+
+ if (storeFactory.dialect || (storeFactory.connectVia === 'DataSource'))
+ return storeFactory;
+ }
+
+ return null;
+ }
+
+ const cacheStoreJdbcDialects = [
+ {value: 'Generic', label: 'Generic JDBC'},
+ {value: 'Oracle', label: 'Oracle'},
+ {value: 'DB2', label: 'IBM DB2'},
+ {value: 'SQLServer', label: 'Microsoft SQL Server'},
+ {value: 'MySQL', label: 'MySQL'},
+ {value: 'PostgreSQL', label: 'PostgreSQL'},
+ {value: 'H2', label: 'H2 database'}
+ ];
+
+ function domainForStoreConfigured(domain) {
+ const isEmpty = !isDefined(domain) || (isEmptyString(domain.databaseSchema) &&
+ isEmptyString(domain.databaseTable) &&
+ _.isEmpty(domain.keyFields) &&
+ _.isEmpty(domain.valueFields));
+
+ return !isEmpty;
+ }
+
+ const DS_CHECK_SUCCESS = {checked: true};
+
+ /**
+ * Compare datasources of caches or clusters.
+ *
+ * @param firstObj First cache or cluster.
+ * @param secondObj Second cache or cluster.
+ * @returns {*} Check result object.
+ */
+ function compareDataSources(firstObj, secondObj) {
+ const firstDs = extractDataSource(firstObj);
+ const secondDs = extractDataSource(secondObj);
+
+ if (firstDs && secondDs) {
+ const firstDB = firstDs.dialect;
+ const secondDB = secondDs.dialect;
+
+ if (firstDs.dataSourceBean === secondDs.dataSourceBean && firstDB !== secondDB)
+ return {checked: false, firstObj, firstDB, secondObj, secondDB};
+ }
+
+ return DS_CHECK_SUCCESS;
+ }
+
+ function compareSQLSchemaNames(firstCache, secondCache) {
+ const firstName = firstCache.sqlSchema;
+ const secondName = secondCache.sqlSchema;
+
+ if (firstName && secondName && (firstName === secondName))
+ return {checked: false, firstCache, secondCache};
+
+ return DS_CHECK_SUCCESS;
+ }
+
+ function toJavaName(prefix, name) {
+ const javaName = name ? name.replace(/[^A-Za-z_0-9]+/g, '_') : 'dflt';
+
+ return prefix + javaName.charAt(0).toLocaleUpperCase() + javaName.slice(1);
+ }
+
+ return {
+ getModel,
+ mkOptions(options) {
+ return _.map(options, (option) => {
+ return {value: option, label: isDefined(option) ? option : 'Not set'};
+ });
+ },
+ isDefined,
+ hasProperty(obj, props) {
+ for (const propName in props) {
+ if (props.hasOwnProperty(propName)) {
+ if (obj[propName])
+ return true;
+ }
+ }
+
+ return false;
+ },
+ isEmptyString,
+ SUPPORTED_JDBC_TYPES,
+ findJdbcType(jdbcType) {
+ const res = _.find(ALL_JDBC_TYPES, (item) => item.dbType === jdbcType);
+
+ return res ? res : {dbName: 'Unknown', javaType: 'Unknown'};
+ },
+ javaBuiltInClasses,
+ javaBuiltInTypes,
+ isJavaBuiltInClass,
+ isValidJavaIdentifier,
+ isValidJavaClass(msg, ident, allowBuiltInClass, elemId, packageOnly, panels, panelId) {
+ if (isEmptyString(ident))
+ return ErrorPopover.show(elemId, msg + ' could not be empty!', panels, panelId);
+
+ const parts = ident.split('.');
+
+ const len = parts.length;
+
+ if (!allowBuiltInClass && isJavaBuiltInClass(ident))
+ return ErrorPopover.show(elemId, msg + ' should not be the Java build-in class!', panels, panelId);
+
+ if (len < 2 && !isJavaBuiltInClass(ident) && !packageOnly)
+ return ErrorPopover.show(elemId, msg + ' does not have package specified!', panels, panelId);
+
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i];
+
+ if (!isValidJavaIdentifier(msg, part, elemId, panels, panelId))
+ return false;
+ }
+
+ return true;
+ },
+ domainForQueryConfigured(domain) {
+ const isEmpty = !isDefined(domain) || (_.isEmpty(domain.fields) &&
+ _.isEmpty(domain.aliases) &&
+ _.isEmpty(domain.indexes));
+
+ return !isEmpty;
+ },
+ domainForStoreConfigured,
+ download(type, name, data) {
+ const file = document.createElement('a');
+
+ file.setAttribute('href', 'data:' + type + ';charset=utf-8,' + data);
+ file.setAttribute('download', name);
+ file.setAttribute('target', '_self');
+
+ file.style.display = 'none';
+
+ document.body.appendChild(file);
+
+ file.click();
+
+ document.body.removeChild(file);
+ },
+ getQueryVariable(name) {
+ const attrs = window.location.search.substring(1).split('&');
+ const attr = _.find(attrs, (a) => a === name || (a.indexOf('=') >= 0 && a.substr(0, a.indexOf('=')) === name));
+
+ if (!isDefined(attr))
+ return null;
+
+ if (attr === name)
+ return true;
+
+ return attr.substr(attr.indexOf('=') + 1);
+ },
+ cacheStoreJdbcDialects,
+ cacheStoreJdbcDialectsLabel(dialect) {
+ const found = _.find(cacheStoreJdbcDialects, (dialectVal) => dialectVal.value === dialect);
+
+ return found ? found.label : null;
+ },
+ checkDataSources(cluster, caches, checkCacheExt) {
+ let res = DS_CHECK_SUCCESS;
+
+ _.find(caches, (curCache, curIx) => {
+ res = compareDataSources(curCache, cluster);
+
+ if (!res.checked)
+ return true;
+
+ if (isDefined(checkCacheExt)) {
+ if (checkCacheExt._id !== curCache._id) {
+ res = compareDataSources(checkCacheExt, curCache);
+
+ return !res.checked;
+ }
+
+ return false;
+ }
+
+ return _.find(caches, (checkCache, checkIx) => {
+ if (checkIx < curIx) {
+ res = compareDataSources(checkCache, curCache);
+
+ return !res.checked;
+ }
+
+ return false;
+ });
+ });
+
+ return res;
+ },
+ checkCacheSQLSchemas(caches, checkCacheExt) {
+ let res = DS_CHECK_SUCCESS;
+
+ _.find(caches, (curCache, curIx) => {
+ if (isDefined(checkCacheExt)) {
+ if (checkCacheExt._id !== curCache._id) {
+ res = compareSQLSchemaNames(checkCacheExt, curCache);
+
+ return !res.checked;
+ }
+
+ return false;
+ }
+
+ return _.find(caches, (checkCache, checkIx) => {
+ if (checkIx < curIx) {
+ res = compareSQLSchemaNames(checkCache, curCache);
+
+ return !res.checked;
+ }
+
+ return false;
+ });
+ });
+
+ return res;
+ },
+ autoCacheStoreConfiguration(cache, domains) {
+ const cacheStoreFactory = isDefined(cache.cacheStoreFactory) &&
+ isDefined(cache.cacheStoreFactory.kind);
+
+ if (!cacheStoreFactory && _.findIndex(domains, domainForStoreConfigured) >= 0) {
+ const dflt = !cache.readThrough && !cache.writeThrough;
+
+ return {
+ cacheStoreFactory: {
+ kind: 'CacheJdbcPojoStoreFactory',
+ CacheJdbcPojoStoreFactory: {
+ dataSourceBean: toJavaName('ds', cache.name),
+ dialect: 'Generic'
+ },
+ CacheJdbcBlobStoreFactory: {connectVia: 'DataSource'}
+ },
+ readThrough: dflt || cache.readThrough,
+ writeThrough: dflt || cache.writeThrough
+ };
+ }
+ },
+ autoClusterSwapSpiConfiguration(cluster, caches) {
+ const swapConfigured = cluster.swapSpaceSpi && cluster.swapSpaceSpi.kind;
+
+ if (!swapConfigured && _.find(caches, (cache) => cache.swapEnabled))
+ return {swapSpaceSpi: {kind: 'FileSwapSpaceSpi'}};
+
+ return null;
+ },
+ randomString(len) {
+ const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ const possibleLen = possible.length;
+
+ let res = '';
+
+ for (let i = 0; i < len; i++)
+ res += possible.charAt(Math.floor(Math.random() * possibleLen));
+
+ return res;
+ },
+ checkFieldValidators(ui) {
+ const form = ui.inputForm;
+ const errors = form.$error;
+ const errKeys = Object.keys(errors);
+
+ if (errKeys && errKeys.length > 0) {
+ const firstErrorKey = errKeys[0];
+
+ const firstError = errors[firstErrorKey][0];
+ const actualError = firstError.$error[firstErrorKey][0];
+
+ const errNameFull = actualError.$name;
+ const errNameShort = errNameFull.endsWith('TextInput') ? errNameFull.substring(0, errNameFull.length - 9) : errNameFull;
+
+ const extractErrorMessage = (errName) => {
+ try {
+ return errors[firstErrorKey][0].$errorMessages[errName][firstErrorKey];
+ }
+ catch (ignored) {
+ try {
+ return form[firstError.$name].$errorMessages[errName][firstErrorKey];
+ }
+ catch (ignited) {
+ return false;
+ }
+ }
+ };
+
+ const msg = extractErrorMessage(errNameFull) || extractErrorMessage(errNameShort) || 'Invalid value!';
+
+ return ErrorPopover.show(errNameFull, msg, ui, firstError.$name);
+ }
+
+ return true;
+ }
+ };
+}]];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/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
new file mode 100644
index 0000000..e679488
--- /dev/null
+++ b/modules/web-console/frontend/app/services/Messages.service.js
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Service to show various information and error messages.
+export default ['IgniteMessages', ['$alert', ($alert) => {
+ // Common instance of alert modal.
+ let msgModal;
+
+ const errorMessage = (prefix, err) => {
+ prefix = prefix || '';
+
+ if (err) {
+ if (err.hasOwnProperty('message'))
+ return prefix + err.message;
+
+ return prefix + err;
+ }
+
+ return prefix + 'Internal error.';
+ };
+
+ const hideAlert = () => {
+ if (msgModal)
+ msgModal.hide();
+ };
+
+ const _showMessage = (err, type, duration, icon) => {
+ hideAlert();
+
+ const title = errorMessage(null, err);
+
+ msgModal = $alert({type, title, duration});
+
+ msgModal.$scope.icon = icon;
+ };
+
+ return {
+ errorMessage,
+ hideAlert,
+ showError(err) {
+ _showMessage(err, 'danger', 10, 'fa-exclamation-triangle');
+
+ return false;
+ },
+ showInfo(err) {
+ _showMessage(err, 'success', 3, 'fa-check-circle-o');
+ }
+ };
+}]];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/frontend/app/services/ModelNormalizer.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/services/ModelNormalizer.service.js b/modules/web-console/frontend/app/services/ModelNormalizer.service.js
new file mode 100644
index 0000000..4c7052b
--- /dev/null
+++ b/modules/web-console/frontend/app/services/ModelNormalizer.service.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Service to normalize objects for dirty checks.
+export default ['IgniteModelNormalizer', () => {
+ /**
+ * Normalize object for dirty checks.
+ *
+ * @param original
+ * @param dest
+ * @returns {*}
+ */
+ const normalize = (original, dest) => {
+ if (_.isUndefined(original))
+ return dest;
+
+ if (_.isObject(original)) {
+ _.forOwn(original, (value, key) => {
+ if (/\$\$hashKey/.test(key))
+ return;
+
+ const attr = normalize(value);
+
+ if (!_.isNil(attr)) {
+ dest = dest || {};
+ dest[key] = attr;
+ }
+ });
+ } else if (_.isBoolean(original) && original === true)
+ dest = original;
+ else if ((_.isString(original) && original.length) || _.isNumber(original))
+ dest = original;
+ else if (_.isArray(original) && original.length)
+ dest = _.map(original, (value) => normalize(value, {}));
+
+ return dest;
+ };
+
+ return {
+ normalize,
+ isEqual(prev, cur) {
+ return _.isEqual(prev, normalize(cur));
+ }
+ };
+}];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/frontend/app/services/UnsavedChangesGuard.service.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/services/UnsavedChangesGuard.service.js b/modules/web-console/frontend/app/services/UnsavedChangesGuard.service.js
new file mode 100644
index 0000000..91244b0
--- /dev/null
+++ b/modules/web-console/frontend/app/services/UnsavedChangesGuard.service.js
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const MSG = 'You have unsaved changes.\n\nAre you sure you want to discard them?';
+
+// Service that show confirmation about unsaved changes on user change location.
+export default ['IgniteUnsavedChangesGuard', ['$rootScope', ($root) => {
+ return {
+ install(scope, customDirtyCheck = () => scope.ui.inputForm.$dirty) {
+ scope.$on('$destroy', () => window.onbeforeunload = null);
+
+ const unbind = $root.$on('$stateChangeStart', (event) => {
+ if (_.get(scope, 'ui.inputForm', false) && customDirtyCheck()) {
+ if (!confirm(MSG)) // eslint-disable-line no-alert
+ event.preventDefault();
+ else
+ unbind();
+ }
+ });
+
+ window.onbeforeunload = () => _.get(scope, 'ui.inputForm.$dirty', false) ? MSG : null;
+ }
+ };
+}]];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/frontend/app/vendor.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/vendor.js b/modules/web-console/frontend/app/vendor.js
new file mode 100644
index 0000000..0322887
--- /dev/null
+++ b/modules/web-console/frontend/app/vendor.js
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'jquery';
+import 'angular';
+import 'angular-acl';
+import 'angular-animate';
+import 'angular-sanitize';
+import 'angular-strap';
+import 'angular-strap/dist/angular-strap.tpl';
+import 'angular-socket-io';
+import 'angular-retina';
+import 'angular-ui-router';
+import 'ui-router-metatags/dist/ui-router-metatags';
+import 'angular-smart-table';
+import 'angular-ui-grid/ui-grid';
+import 'angular-drag-and-drop-lists';
+import 'angular-nvd3';
+import 'angular-tree-control';
+import 'angular-gridster';
+import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
+import 'bootstrap-sass/assets/javascripts/bootstrap/carousel';
+import 'brace';
+import 'brace/mode/xml';
+import 'brace/mode/sql';
+import 'brace/mode/java';
+import 'brace/mode/dockerfile';
+import 'brace/mode/snippets';
+import 'brace/theme/chrome';
+import 'brace/ext/language_tools';
+import 'brace/ext/searchbox';
+import 'file-saver';
+import 'jszip';
+import 'nvd3';
+import 'query-command-supported';
+import 'angular-gridster/dist/angular-gridster.min.css';
+import 'angular-tree-control/css/tree-control-attribute.css';
+import 'angular-tree-control/css/tree-control.css';
+import 'angular-ui-grid/ui-grid.css';
+import 'angular-motion/dist/angular-motion.css';
+import 'nvd3/build/nv.d3.css';
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/frontend/controllers/admin-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/admin-controller.js b/modules/web-console/frontend/controllers/admin-controller.js
new file mode 100644
index 0000000..57a39b2
--- /dev/null
+++ b/modules/web-console/frontend/controllers/admin-controller.js
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Controller for Admin screen.
+export default ['adminController', [
+ '$rootScope', '$scope', '$http', '$q', '$state', 'IgniteMessages', 'IgniteConfirm', 'User', 'IgniteCountries',
+ ($rootScope, $scope, $http, $q, $state, Messages, Confirm, User, Countries) => {
+ $scope.users = null;
+
+ const _reloadUsers = () => {
+ $http.post('/api/v1/admin/list')
+ .success((users) => {
+ $scope.users = users;
+
+ _.forEach($scope.users, (user) => {
+ user.userName = user.firstName + ' ' + user.lastName;
+ user.countryCode = Countries.getByName(user.country).code;
+ user.label = user.userName + ' ' + user.email + ' ' +
+ (user.company || '') + ' ' + (user.countryCode || '');
+ });
+ })
+ .error(Messages.showError);
+ };
+
+ _reloadUsers();
+
+ $scope.becomeUser = function(user) {
+ $http.get('/api/v1/admin/become', { params: {viewedUserId: user._id}})
+ .catch(({data}) => Promise.reject(data))
+ .then(User.load)
+ .then((becomeUser) => {
+ $rootScope.$broadcast('user', becomeUser);
+
+ $state.go('base.configuration.clusters');
+ })
+ .catch(Messages.showError);
+ };
+
+ $scope.removeUser = (user) => {
+ Confirm.confirm('Are you sure you want to remove user: "' + user.userName + '"?')
+ .then(() => {
+ $http.post('/api/v1/admin/remove', {userId: user._id})
+ .success(() => {
+ const i = _.findIndex($scope.users, (u) => u._id === user._id);
+
+ if (i >= 0)
+ $scope.users.splice(i, 1);
+
+ Messages.showInfo('User has been removed: "' + user.userName + '"');
+ })
+ .error((err, status) => {
+ if (status === 503)
+ Messages.showInfo(err);
+ else
+ Messages.showError(Messages.errorMessage('Failed to remove user: ', err));
+ });
+ });
+ };
+
+ $scope.toggleAdmin = (user) => {
+ if (user.adminChanging)
+ return;
+
+ user.adminChanging = true;
+
+ $http.post('/api/v1/admin/save', {userId: user._id, adminFlag: !user.admin})
+ .success(() => {
+ user.admin = !user.admin;
+
+ Messages.showInfo('Admin right was successfully toggled for user: "' + user.userName + '"');
+ })
+ .error((err) => {
+ Messages.showError(Messages.errorMessage('Failed to toggle admin right for user: ', err));
+ })
+ .finally(() => user.adminChanging = false);
+ };
+ }
+]];
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/frontend/controllers/caches-controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/controllers/caches-controller.js b/modules/web-console/frontend/controllers/caches-controller.js
new file mode 100644
index 0000000..9873051
--- /dev/null
+++ b/modules/web-console/frontend/controllers/caches-controller.js
@@ -0,0 +1,524 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Controller for Caches screen.
+export default ['cachesController', [
+ '$scope', '$http', '$state', '$filter', '$timeout', 'IgniteLegacyUtils', 'IgniteMessages', 'IgniteConfirm', 'IgniteClone', 'IgniteLoading', 'IgniteModelNormalizer', 'IgniteUnsavedChangesGuard', 'igniteConfigurationResource', 'IgniteErrorPopover', 'IgniteFormUtils',
+ function($scope, $http, $state, $filter, $timeout, LegacyUtils, Messages, Confirm, Clone, Loading, ModelNormalizer, UnsavedChangesGuard, Resource, ErrorPopover, FormUtils) {
+ UnsavedChangesGuard.install($scope);
+
+ const emptyCache = {empty: true};
+
+ let __original_value;
+
+ const blank = {
+ evictionPolicy: {},
+ cacheStoreFactory: {
+ CacheHibernateBlobStoreFactory: {
+ hibernateProperties: []
+ }
+ },
+ nearConfiguration: {},
+ sqlFunctionClasses: []
+ };
+
+ // We need to initialize backupItem with empty object in order to properly used from angular directives.
+ $scope.backupItem = emptyCache;
+
+ $scope.ui = FormUtils.formUI();
+ $scope.ui.activePanels = [0];
+ $scope.ui.topPanels = [0, 1, 2, 3];
+
+ $scope.saveBtnTipText = FormUtils.saveBtnTipText;
+ $scope.widthIsSufficient = FormUtils.widthIsSufficient;
+ $scope.offHeapMode = 'DISABLED';
+
+ $scope.contentVisible = function() {
+ const item = $scope.backupItem;
+
+ return !item.empty && (!item._id || _.find($scope.displayedRows, {_id: item._id}));
+ };
+
+ $scope.toggleExpanded = function() {
+ $scope.ui.expanded = !$scope.ui.expanded;
+
+ ErrorPopover.hide();
+ };
+
+ $scope.caches = [];
+ $scope.domains = [];
+
+ function _cacheLbl(cache) {
+ return cache.name + ', ' + cache.cacheMode + ', ' + cache.atomicityMode;
+ }
+
+ function selectFirstItem() {
+ if ($scope.caches.length > 0)
+ $scope.selectItem($scope.caches[0]);
+ }
+
+ function cacheDomains(item) {
+ return _.reduce($scope.domains, function(memo, domain) {
+ if (item && _.includes(item.domains, domain.value))
+ memo.push(domain.meta);
+
+ return memo;
+ }, []);
+ }
+
+ const setOffHeapMode = (item) => {
+ if (_.isNil(item.offHeapMaxMemory))
+ return;
+
+ return item.offHeapMode = Math.sign(item.offHeapMaxMemory);
+ };
+
+ const setOffHeapMaxMemory = (value) => {
+ const item = $scope.backupItem;
+
+ if (_.isNil(value) || value <= 0)
+ return item.offHeapMaxMemory = value;
+
+ item.offHeapMaxMemory = item.offHeapMaxMemory > 0 ? item.offHeapMaxMemory : null;
+ };
+
+ Loading.start('loadingCachesScreen');
+
+ // When landing on the page, get caches and show them.
+ Resource.read()
+ .then(({spaces, clusters, caches, domains, igfss}) => {
+ const validFilter = $filter('domainsValidation');
+
+ $scope.spaces = spaces;
+ $scope.caches = caches;
+ $scope.igfss = _.map(igfss, (igfs) => ({
+ label: igfs.name,
+ value: igfs._id,
+ igfs
+ }));
+
+ _.forEach($scope.caches, (cache) => cache.label = _cacheLbl(cache));
+
+ $scope.clusters = _.map(clusters, (cluster) => ({
+ value: cluster._id,
+ label: cluster.name,
+ discovery: cluster.discovery,
+ caches: cluster.caches
+ }));
+
+ $scope.domains = _.sortBy(_.map(validFilter(domains, true, false), (domain) => ({
+ label: domain.valueType,
+ value: domain._id,
+ kind: domain.kind,
+ meta: domain
+ })), 'label');
+
+ if ($state.params.linkId)
+ $scope.createItem($state.params.linkId);
+ else {
+ const lastSelectedCache = angular.fromJson(sessionStorage.lastSelectedCache);
+
+ if (lastSelectedCache) {
+ const idx = _.findIndex($scope.caches, function(cache) {
+ return cache._id === lastSelectedCache;
+ });
+
+ if (idx >= 0)
+ $scope.selectItem($scope.caches[idx]);
+ else {
+ sessionStorage.removeItem('lastSelectedCache');
+
+ selectFirstItem();
+ }
+ }
+ else
+ selectFirstItem();
+ }
+
+ $scope.$watch('ui.inputForm.$valid', function(valid) {
+ if (valid && ModelNormalizer.isEqual(__original_value, $scope.backupItem))
+ $scope.ui.inputForm.$dirty = false;
+ });
+
+ $scope.$watch('backupItem', function(val) {
+ if (!$scope.ui.inputForm)
+ return;
+
+ const form = $scope.ui.inputForm;
+
+ if (form.$valid && ModelNormalizer.isEqual(__original_value, val))
+ form.$setPristine();
+ else
+ form.$setDirty();
+ }, true);
+
+ $scope.$watch('backupItem.offHeapMode', setOffHeapMaxMemory);
+
+ $scope.$watch('ui.activePanels.length', () => {
+ ErrorPopover.hide();
+ });
+ })
+ .catch(Messages.showError)
+ .then(() => {
+ $scope.ui.ready = true;
+ $scope.ui.inputForm && $scope.ui.inputForm.$setPristine();
+
+ Loading.finish('loadingCachesScreen');
+ });
+
+ $scope.selectItem = function(item, backup) {
+ function selectItem() {
+ $scope.selectedItem = item;
+
+ if (item && !_.get(item.cacheStoreFactory.CacheJdbcBlobStoreFactory, 'connectVia'))
+ _.set(item.cacheStoreFactory, 'CacheJdbcBlobStoreFactory.connectVia', 'DataSource');
+
+ try {
+ if (item && item._id)
+ sessionStorage.lastSelectedCache = angular.toJson(item._id);
+ else
+ sessionStorage.removeItem('lastSelectedCache');
+ }
+ catch (ignored) {
+ // No-op.
+ }
+
+ if (backup)
+ $scope.backupItem = backup;
+ else if (item)
+ $scope.backupItem = angular.copy(item);
+ else
+ $scope.backupItem = emptyCache;
+
+ $scope.backupItem = angular.merge({}, blank, $scope.backupItem);
+
+ if ($scope.ui.inputForm) {
+ $scope.ui.inputForm.$error = {};
+ $scope.ui.inputForm.$setPristine();
+ }
+
+ setOffHeapMode($scope.backupItem);
+
+ __original_value = ModelNormalizer.normalize($scope.backupItem);
+
+ if (LegacyUtils.getQueryVariable('new'))
+ $state.go('base.configuration.caches');
+ }
+
+ FormUtils.confirmUnsavedChanges($scope.backupItem && $scope.ui.inputForm && $scope.ui.inputForm.$dirty, selectItem);
+ };
+
+ $scope.linkId = () => $scope.backupItem._id ? $scope.backupItem._id : 'create';
+
+ function prepareNewItem(linkId) {
+ return {
+ space: $scope.spaces[0]._id,
+ cacheMode: 'PARTITIONED',
+ atomicityMode: 'ATOMIC',
+ readFromBackup: true,
+ copyOnRead: true,
+ clusters: linkId && _.find($scope.clusters, {value: linkId})
+ ? [linkId] : _.map($scope.clusters, function(cluster) { return cluster.value; }),
+ domains: linkId && _.find($scope.domains, { value: linkId }) ? [linkId] : [],
+ cacheStoreFactory: {CacheJdbcBlobStoreFactory: {connectVia: 'DataSource'}}
+ };
+ }
+
+ // Add new cache.
+ $scope.createItem = function(linkId) {
+ $timeout(() => FormUtils.ensureActivePanel($scope.ui, 'general', 'cacheNameInput'));
+
+ $scope.selectItem(null, prepareNewItem(linkId));
+ };
+
+ function cacheClusters() {
+ return _.filter($scope.clusters, (cluster) => _.includes($scope.backupItem.clusters, cluster.value));
+ }
+
+ function clusterCaches(cluster) {
+ const caches = _.filter($scope.caches,
+ (cache) => cache._id !== $scope.backupItem._id && _.includes(cluster.caches, cache._id));
+
+ caches.push($scope.backupItem);
+
+ return caches;
+ }
+
+ function checkDataSources() {
+ const clusters = cacheClusters();
+
+ let checkRes = {checked: true};
+
+ const failCluster = _.find(clusters, (cluster) => {
+ const caches = clusterCaches(cluster);
+
+ checkRes = LegacyUtils.checkDataSources(cluster, caches, $scope.backupItem);
+
+ return !checkRes.checked;
+ });
+
+ if (!checkRes.checked) {
+ if (_.get(checkRes.secondObj, 'discovery.kind') === 'Jdbc') {
+ return ErrorPopover.show(checkRes.firstObj.cacheStoreFactory.kind === 'CacheJdbcPojoStoreFactory' ? 'pojoDialectInput' : 'blobDialectInput',
+ 'Found cluster "' + failCluster.label + '" with the same data source bean name "' +
+ checkRes.secondObj.discovery.Jdbc.dataSourceBean + '" and different database: "' +
+ LegacyUtils.cacheStoreJdbcDialectsLabel(checkRes.firstDB) + '" in current cache and "' +
+ LegacyUtils.cacheStoreJdbcDialectsLabel(checkRes.secondDB) + '" in"' + checkRes.secondObj.label + '" cluster',
+ $scope.ui, 'store', 10000);
+ }
+
+ return ErrorPopover.show(checkRes.firstObj.cacheStoreFactory.kind === 'CacheJdbcPojoStoreFactory' ? 'pojoDialectInput' : 'blobDialectInput',
+ 'Found cache "' + checkRes.secondObj.name + '" in cluster "' + failCluster.label + '" ' +
+ 'with the same data source bean name "' + checkRes.firstObj.cacheStoreFactory[checkRes.firstObj.cacheStoreFactory.kind].dataSourceBean +
+ '" and different database: "' + LegacyUtils.cacheStoreJdbcDialectsLabel(checkRes.firstDB) + '" in current cache and "' +
+ LegacyUtils.cacheStoreJdbcDialectsLabel(checkRes.secondDB) + '" in "' + checkRes.secondObj.name + '" cache',
+ $scope.ui, 'store', 10000);
+ }
+
+ return true;
+ }
+
+ function checkSQLSchemas() {
+ const clusters = cacheClusters();
+
+ let checkRes = {checked: true};
+
+ const failCluster = _.find(clusters, (cluster) => {
+ const caches = clusterCaches(cluster);
+
+ checkRes = LegacyUtils.checkCacheSQLSchemas(caches, $scope.backupItem);
+
+ return !checkRes.checked;
+ });
+
+ if (!checkRes.checked) {
+ return ErrorPopover.show('sqlSchemaInput',
+ 'Found cache "' + checkRes.secondCache.name + '" in cluster "' + failCluster.label + '" ' +
+ 'with the same SQL schema name "' + checkRes.firstCache.sqlSchema + '"',
+ $scope.ui, 'query', 10000);
+ }
+
+ return true;
+ }
+
+ function checkStoreFactoryBean(storeFactory, beanFieldId) {
+ if (!LegacyUtils.isValidJavaIdentifier('Data source bean', storeFactory.dataSourceBean, beanFieldId, $scope.ui, 'store'))
+ return false;
+
+ return checkDataSources();
+ }
+
+ function checkStoreFactory(item) {
+ const cacheStoreFactorySelected = item.cacheStoreFactory && item.cacheStoreFactory.kind;
+
+ if (cacheStoreFactorySelected) {
+ const storeFactory = item.cacheStoreFactory[item.cacheStoreFactory.kind];
+
+ if (item.cacheStoreFactory.kind === 'CacheJdbcPojoStoreFactory' && !checkStoreFactoryBean(storeFactory, 'pojoDataSourceBean'))
+ return false;
+
+ if (item.cacheStoreFactory.kind === 'CacheJdbcBlobStoreFactory' && storeFactory.connectVia !== 'URL'
+ && !checkStoreFactoryBean(storeFactory, 'blobDataSourceBean'))
+ return false;
+ }
+
+ if ((item.readThrough || item.writeThrough) && !cacheStoreFactorySelected)
+ return ErrorPopover.show('cacheStoreFactoryInput', (item.readThrough ? 'Read' : 'Write') + ' through are enabled but store is not configured!', $scope.ui, 'store');
+
+ if (item.writeBehindEnabled && !cacheStoreFactorySelected)
+ return ErrorPopover.show('cacheStoreFactoryInput', 'Write behind enabled but store is not configured!', $scope.ui, 'store');
+
+ if (cacheStoreFactorySelected && !item.readThrough && !item.writeThrough)
+ return ErrorPopover.show('readThroughLabel', 'Store is configured but read/write through are not enabled!', $scope.ui, 'store');
+
+ return true;
+ }
+
+ // Check cache logical consistency.
+ function validate(item) {
+ ErrorPopover.hide();
+
+ if (LegacyUtils.isEmptyString(item.name))
+ return ErrorPopover.show('cacheNameInput', 'Cache name should not be empty!', $scope.ui, 'general');
+
+ if (item.memoryMode === 'ONHEAP_TIERED' && item.offHeapMaxMemory > 0 && !LegacyUtils.isDefined(item.evictionPolicy.kind))
+ return ErrorPopover.show('evictionPolicyKindInput', 'Eviction policy should be configured!', $scope.ui, 'memory');
+
+ if (!LegacyUtils.checkFieldValidators($scope.ui))
+ return false;
+
+ if (item.memoryMode === 'OFFHEAP_VALUES' && !_.isEmpty(item.domains))
+ return ErrorPopover.show('memoryModeInput', 'Query indexing could not be enabled while values are stored off-heap!', $scope.ui, 'memory');
+
+ if (item.memoryMode === 'OFFHEAP_TIERED' && item.offHeapMaxMemory === -1)
+ return ErrorPopover.show('offHeapModeInput', 'Invalid value!', $scope.ui, 'memory');
+
+ if (!checkSQLSchemas())
+ return false;
+
+ if (!checkStoreFactory(item))
+ return false;
+
+ if (item.writeBehindFlushSize === 0 && item.writeBehindFlushFrequency === 0)
+ return ErrorPopover.show('writeBehindFlushSizeInput', 'Both "Flush frequency" and "Flush size" are not allowed as 0!', $scope.ui, 'store');
+
+ if (item.nodeFilter && item.nodeFilter.kind === 'OnNodes' && _.isEmpty(item.nodeFilter.OnNodes.nodeIds))
+ return ErrorPopover.show('nodeFilter-title', 'At least one node ID should be specified!', $scope.ui, 'nodeFilter');
+
+ return true;
+ }
+
+ // Save cache in database.
+ function save(item) {
+ $http.post('/api/v1/configuration/caches/save', item)
+ .success(function(_id) {
+ item.label = _cacheLbl(item);
+
+ $scope.ui.inputForm.$setPristine();
+
+ const idx = _.findIndex($scope.caches, function(cache) {
+ return cache._id === _id;
+ });
+
+ if (idx >= 0)
+ angular.merge($scope.caches[idx], item);
+ else {
+ item._id = _id;
+ $scope.caches.push(item);
+ }
+
+ _.forEach($scope.clusters, (cluster) => {
+ if (_.includes(item.clusters, cluster.value))
+ cluster.caches = _.union(cluster.caches, [_id]);
+ else
+ _.remove(cluster.caches, (id) => id === _id);
+ });
+
+ _.forEach($scope.domains, (domain) => {
+ if (_.includes(item.domains, domain.value))
+ domain.meta.caches = _.union(domain.meta.caches, [_id]);
+ else
+ _.remove(domain.meta.caches, (id) => id === _id);
+ });
+
+ $scope.selectItem(item);
+
+ Messages.showInfo('Cache "' + item.name + '" saved.');
+ })
+ .error(Messages.showError);
+ }
+
+ // Save cache.
+ $scope.saveItem = function() {
+ const item = $scope.backupItem;
+
+ angular.extend(item, LegacyUtils.autoCacheStoreConfiguration(item, cacheDomains(item)));
+
+ if (validate(item))
+ save(item);
+ };
+
+ function _cacheNames() {
+ return _.map($scope.caches, function(cache) {
+ return cache.name;
+ });
+ }
+
+ // Clone cache with new name.
+ $scope.cloneItem = function() {
+ if (validate($scope.backupItem)) {
+ Clone.confirm($scope.backupItem.name, _cacheNames()).then(function(newName) {
+ const item = angular.copy($scope.backupItem);
+
+ delete item._id;
+
+ item.name = newName;
+
+ delete item.sqlSchema;
+
+ save(item);
+ });
+ }
+ };
+
+ // Remove cache from db.
+ $scope.removeItem = function() {
+ const selectedItem = $scope.selectedItem;
+
+ Confirm.confirm('Are you sure you want to remove cache: "' + selectedItem.name + '"?')
+ .then(function() {
+ const _id = selectedItem._id;
+
+ $http.post('/api/v1/configuration/caches/remove', {_id})
+ .success(function() {
+ Messages.showInfo('Cache has been removed: ' + selectedItem.name);
+
+ const caches = $scope.caches;
+
+ const idx = _.findIndex(caches, function(cache) {
+ return cache._id === _id;
+ });
+
+ if (idx >= 0) {
+ caches.splice(idx, 1);
+
+ $scope.ui.inputForm.$setPristine();
+
+ if (caches.length > 0)
+ $scope.selectItem(caches[0]);
+ else
+ $scope.backupItem = emptyCache;
+
+ _.forEach($scope.clusters, (cluster) => _.remove(cluster.caches, (id) => id === _id));
+ _.forEach($scope.domains, (domain) => _.remove(domain.meta.caches, (id) => id === _id));
+ }
+ })
+ .error(Messages.showError);
+ });
+ };
+
+ // Remove all caches from db.
+ $scope.removeAllItems = function() {
+ Confirm.confirm('Are you sure you want to remove all caches?')
+ .then(function() {
+ $http.post('/api/v1/configuration/caches/remove/all')
+ .success(function() {
+ Messages.showInfo('All caches have been removed');
+
+ $scope.caches = [];
+
+ _.forEach($scope.clusters, (cluster) => cluster.caches = []);
+ _.forEach($scope.domains, (domain) => domain.meta.caches = []);
+
+ $scope.backupItem = emptyCache;
+ $scope.ui.inputForm.$error = {};
+ $scope.ui.inputForm.$setPristine();
+ })
+ .error(Messages.showError);
+ });
+ };
+
+ $scope.resetAll = function() {
+ Confirm.confirm('Are you sure you want to undo all changes for current cache?')
+ .then(function() {
+ $scope.backupItem = $scope.selectedItem ? angular.copy($scope.selectedItem) : prepareNewItem();
+ $scope.ui.inputForm.$error = {};
+ $scope.ui.inputForm.$setPristine();
+ });
+ };
+ }
+]];