// Licensed to Cloudera, Inc. under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. Cloudera, Inc. 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 searchRenderers = { 'rowkey': { //class to tag selection select: /[^\,\{\[]+(([\{\[][^\}\]]+[\}\]])+|)([^\,]+|)/g, //select the substring to process, useful as JS has no lookbehinds old: ([^,]+\[([^,]+(,|)+)+\]|[^,]+) tag: /.+/g, //select the matches to wrap with tags nested: { 'scan': { select: /(([^\\]|\b)\+[0-9]+)/g, tag: /\+[0-9]+/g}, 'columns': { select: /\[.+\]/g, tag: /[^:,\[\]]+:([^,\[\]]+|)/g}, //forced to do this select due to lack of lookbehinds /[\[\]]/g 'prefix': { select: /[^\*\\]+\*/g, tag: /\*/g}, 'filter': { select: /\{[^\{\}]+\}/, tag:/[^\{\}]+/g, nested: { 'linker': { select: /\ (AND|OR|SKIP|WHILE)\ /g, tag: /.+/g }/*, 'compare_op': { select: /[\<\=\!\>]{1,2}/g, tag: /.+/g }*/ //will be added eventually after html bug is figured out } } } } }; var DataTableViewModel = function(options) { var self = this, _defaults = { name: '', columns: [], items: [], reload: function() { }, el:'' }; options = ko.utils.extend(_defaults,options); ListViewModel.apply(this, [options]); self.name = ko.observable(options.name); self.searchQuery.subscribe(function(value) { self._table.fnFilter(value); }); self.columns = ko.observableArray(options.columns); self._el = $('table[data-datasource="' + options.el + '"]'); self._table = null; self._initTable = function() { if(!self._table) { self._table = self._el.dataTable({ "aoColumnDefs": [ { "bSortable": false, "aTargets": [ 0 ] } ], "sDom": 'tr',//this has to be in, change to sDom so you can call filter() 'bAutoWidth':false, "iDisplayLength": -1}); return self._table; } }; self.sort = function(viewModel, event) { var el = $(event.currentTarget); }; var _reload = self.reload; self.reload = function(callback) { logGA('view_cluster'); if(self._table) { self._table.fnClearTable(); self._table.fnDestroy(); self._table = null; } _reload(function() { self._initTable(); if(callback!=null) callback(); }); }; self.canDrop = ko.computed(function() { var selected = self.selected(); if(selected.length <= 0) return false; for(var i=0; i b) return 1; if(a < b) return -1; return 0; }, 'Row Key Length': function(a, b) { a = a.row.length; b = b.row.length; if(a > b) return 1; if(a < b) return -1; return 0; } } }, options); ListViewModel.apply(this, [options]); //items: [ListView.items[],ListView.items[]] self.columnFamilies = ko.observableArray(); self.name = ko.observable(options.name); self.name.subscribe(function(){ self.columnFamilies([]); self._reloadcfs(); if(app.station() == 'table') return; self.querySet.removeAll(); self.evaluateQuery(); }); //fix and decouple self.lastReloadTime = ko.observable(1); self.searchQuery.subscribe(function goToRow(value) //make this as nice as the renderfucnction and split into two, also fire not down on keyup events { if(app.station() != 'table') return; if(value.replace(/\s/g, "") == '' || value == null) routie(app.cluster() + '/' + app.views.tabledata.name()); var inputs = value.match(searchRenderers['rowkey']['select']); self.querySet.removeAll(); if(inputs) { for(var i=0; i 1 ? parseInt(p[1].trim()) : 0; //pull out filters var filter = inputs[i].match(searchRenderers['rowkey']['nested']['filter']['select']) || ""; filter = filter != null && filter.length > 0 ? escape(filter[0].trim().slice(1, -1)) : ""; //pull out column filters function filterPostfix(postfix) { return (filter != null && filter.length > 0 ? ' AND ' : ' ' ) + postfix; } var cfs = []; $(columns).each(function(i, o) { var colscan = pullFromRenderer(o, searchRenderers['rowkey']['nested']['scan']); if(colscan) { colscan = parseInt(colscan.split('+')[1]) + 1; filter += filterPostfix("ColumnPaginationFilter(" + colscan + ", 0)"); } var fc = o.replace(pullFromRenderer(o, searchRenderers['rowkey']['nested']['prefix']), ''); if(fc != o) { filter += filterPostfix("ColumnPrefixFilter('" + fc.slice(fc.indexOf(':') + 1).match(/[0-9]+/g)[0] + "')"); columns[i] = fc.slice(0, fc.indexOf(':')); } }); self.querySet.push(new QuerySetPiece({ 'row_key': inputs[i].replace(/\\(\+|\*|\,)/g, '$1').replace(/[\[\{].+[\]\}]|\*/g,'').trim(), //clean up with column regex selectors instead 'scan_length': scan ? scan + 1 : 1, 'columns': columns, 'prefix': inputs[i].match(searchRenderers['rowkey']['nested']['prefix']['select']) != null, 'filter': filter.length > 0 ? filter : null })); } } } self.evaluateQuery(); }); self._reloadcfs = function() { return API.queryTable("getColumnDescriptors").done(function(data) { self.columnFamilies.removeAll(); var keys = Object.keys(data); for(var i=0;i b) return 1; if(a < b) return -1; return 0; }, 'Cell Value': function(a, b) { return a.value().localeCompare(b.value()); }, 'Timestamp': function(a, b) { a = parseInt(a.timestamp); b = parseInt(b.timestamp); if(a > b) return 1; if(a < b) return -1; return 0; }, 'Column Name Length': function(a, b) { a = a.name.split(':')[1].length; b = b.name.split(':')[1].length; if(a > b) return 1; if(a < b) return -1; return 0; } } }, options); DataRow.apply(self,[options]); ListViewModel.apply(self,[options]); self.displayedItems = ko.observableArray(); self.displayRangeStart = 0; self.displayRangeLength = 20; self.items.subscribe(function() { self.displayedItems([]); self.updateDisplayedItems(); }); self.searchQuery.subscribe(function(searchValue) { self.scrollLoadSource = ko.computed(function(){ return self.items().filter(function(column) { return column.name.toLowerCase().indexOf(searchValue.toLowerCase()) != -1; }); }); self.displayRangeLength = 20; self.updateDisplayedItems(); }); self.scrollLoadSource = self.items; self.updateDisplayedItems = function() { var x = self.displayRangeStart; self.displayedItems(self.scrollLoadSource().slice(x, x + self.displayRangeLength)); }; self.resetScrollLoad = function() { self.scrollLoadSource = self.items; self.updateDisplayedItems(); }; self.toggleSelectedCollapse = function() { if(self.displayedItems().length == self.displayRangeStart + self.displayRangeLength) { self.displayedItems(self.displayedItems().filter(function(item) { return item.isSelected(); })); self.scrollLoadSource = self.displayedItems; } else { self.resetScrollLoad(); } }; self.onScroll = function(target, ev) { var displayRangeDelta = 15; if($(ev.target).scrollLeft() == ev.target.scrollWidth - ev.target.clientWidth) { if(self.displayedItems().length < self.scrollLoadSource().length) { self.displayRangeLength += displayRangeDelta; self.updateDisplayedItems(); } else { self.displayRangeLength = self.items().length + displayRangeDelta; var validate = self.items().length; API.queryTable('getRowPartial' , self.row, self.items().length, 50).done(function(data) { if(self.items().length != validate) return false; var cols = data[0].columns; var keys = Object.keys(cols); var temp = []; for(var i=0; i 0) self.parent.reload(); //change later self.parent.isLoading(false); }); } (cont === true) ? doDrop() : confirm(i18n("Confirm Delete"), i18n("Are you sure you want to drop this column?"), doDrop); }; self.reload = function(callback, skipPut) { self.isLoading(true); API.queryTable('get', self.parent.row, self.name, 'null').done(function(data) { if(data.length > 0 && !skipPut) self.value(data[0].value); if(typeof callback !== "undefined" && callback != null) callback(); self.isLoading(false); }); }; self.value.subscribe(function(value) { //change transport prep to object wrapper logGA('put_column'); API.queryTable('putColumn', self.parent.row, self.name, "hbase-post-key-" + JSON.stringify(value)).done(function(data) { self.reload(function(){}, true); }); self.editing(false); }); self.editing = ko.observable(false); self.isLoading = ko.observable(false); //move to baseclass }; var SortDropDownView = function(options) { var self = this; options = ko.utils.extend({ sortFields: {}, target: null }, options); BaseModel.apply(self,[options]); self.target = options.target; self.sortAsc = ko.observable(true); self.sortAsc.subscribe(function(){self.sort()}); self.sortField = ko.observable(""); self.sortField.subscribe(function(){self.sort()}); self.sortFields = ko.observableArray(Object.keys(options.sortFields)); // change to ko.computed? self.sortFunctionHash = ko.observable(options.sortFields); self.toggleSortMode = function() { self.sortAsc(!self.sortAsc()); }; self.sort = function() { if (!self.target || !(self.sortFields().length > 0)) return; self.target.sort(function(a, b) { return (self.sortAsc() ? 1 : -1) * self.sortFunctionHash()[self.sortField() ? self.sortField() : self.sortFields()[0]](a,b); //all sort functions must sort by ASC for default }); }; }; var TableDataRow = function(options) { var self = this; options = ko.utils.extend({ name:"", enabled:true }, options); DataRow.apply(self,[options]); self.name = options['name']; self.enabled = ko.observable(options['enabled']); self.toggle = function(viewModel,event){ var action = [i18n('enable'), i18n('disable')][self.enabled() << 0], el = $(event.currentTarget); confirm(i18n("Confirm") + " " + action, i18n("Are you sure you want to") + " " + action + " " + i18n("this table?"), function() { el.showIndicator(); return self[action](el).complete(function() { el.hideIndicator(); }); }); }; self.enable = function(el) { return API.queryCluster('enableTable',self.name).complete(function() { self.enabled(true); }); }; self.disable = function(callback) { return API.queryCluster('disableTable',self.name).complete(function() { self.enabled(false); if($.isFunction(callback)) callback(); }); }; self.drop = function(el) { return API.queryCluster('deleteTable',self.name); }; }; var QuerySetPiece = function(options) { var self = this; options = ko.utils.extend({ row_key: "null", scan_length: 1, prefix: false, columns: [], filter: null, onValidate: function(){} }, options); BaseModel.apply(self,[options]); self.row_key = ko.observable(options.row_key); self.scan_length = ko.observable(options.scan_length); self.columns = ko.observableArray(options.columns); self.prefix = ko.observable(options.prefix); self.filter = ko.observable(options.filter); self.editing = ko.observable(true); self.validate = function() { if(self.scan_length() <= 0 || self.row_key() == "") return app.views.tabledata.querySet.remove(self); //change later return options.onValidate(); }; self.row_key.subscribe(self.validate.bind()); self.scan_length.subscribe(self.validate.bind()); }; var ColumnFamily = function(options) { this.name = options.name; this.enabled = ko.observable(options.enabled); this.toggle = function() { this.enabled(!this.enabled()); app.views.tabledata.reload(); }; } //tagsearch var tagsearch = function() { var self = this; self.tags = ko.observableArray(); self.mode = ko.observable('idle'); self.cur_input = ko.observable(''); self.submitted = ko.observable(false); self.filters = ["KeyOnlyFilter ()", "FirstKeyOnlyFilter ()", "PrefixFilter (‘row_prefix’)", "ColumnPrefixFilter(‘column_prefix’)", "MultipleColumnPrefixFilter(‘column_prefix’, ‘column_prefix’, …, ‘column_prefix’)", "ColumnCountGetFilter (‘limit’)", "PageFilter (‘page_size’)", "ColumnPaginationFilter(‘limit’, ‘offest')", "InclusiveStopFilter(‘stop_row_key’)", "TimeStampsFilter (timestamp, timestamp, ... ,timestamp)", "RowFilter (compareOp, ‘row_comparator’)", "QualifierFilter (compareOp, ‘qualifier_comparator’)", "QualifierFilter (compareOp,‘qualifier_comparator’)", "ValueFilter (compareOp,‘value_comparator’)", "DependentColumnFilter (‘family’, ‘qualifier’, boolean, compare operator, ‘value comparator’)", "DependentColumnFilter (‘family’, ‘qualifier’, boolean)", "DependentColumnFilter (‘family’, ‘qualifier’)", "SingleColumnValueFilter(‘family’, ‘qualifier’, compare operator, ‘comparator’, filterIfColumnMissing_boolean, latest_version_boolean)", "SingleColumnValueFilter(‘family’, ‘qualifier, compare operator, ‘comparator’)", "SingleColumnValueExcludeFilter('family', 'qualifier', compare operator, 'comparator', latest_version_boolean, filterIfColumnMissing_boolean)", "SingleColumnValueExcludeFilter('family', 'qualifier', compare operator, 'comparator')", "ColumnRangeFilter (‘minColumn’, minColumnInclusive_bool, ‘maxColumn’, maxColumnInclusive_bool)"]; self.hints = ko.observableArray([ { hint: i18n('End Query'), shortcut: ',', mode: ['rowkey', 'prefix', 'scan'], selected: false }, { hint: i18n('Mark Row/Column Prefix'), shortcut: '*', mode: ['rowkey'], selected: false }, { hint: i18n('Start Row/Column Scan'), shortcut: '+', mode: ['rowkey', 'prefix'], selected: false }, { hint: i18n('Start Select Columns'), shortcut: '[', mode: ['rowkey', 'prefix'], selected: false }, { hint: i18n('End Column/Family'), shortcut: ',', mode: ['columns'], selected: false }, { hint: i18n('End Select Columns'), shortcut: ']', mode: ['columns'], selected: false }, { hint: i18n('Start FilterString'), shortcut: '{', mode: ['rowkey'], selected: false }, { hint: i18n('End FilterString'), shortcut: '}', mode: ['filter'], selected: false } ]); self.activeHints = ko.computed(function() { var ret = []; $(self.hints()).each(function(i, hint) { if (hint.mode.indexOf(self.mode()) > -1) ret.push(hint); }); return ret; }); self.activeHint = ko.observable(-1); self.modes = { 'rowkey': { hint: i18n('Row Key Value'), type: i18n('String') }, 'scan': { hint: i18n('Length of Scan or Row Key'), type: i18n('Integer') }, 'columns': { hint: i18n('Column Family: Column Name'), type: i18n('String') }, 'prefix': { hint: i18n('Rows starting with'), type: i18n('String') }, 'filter': { hint: i18n('Thrift FilterString'), type: i18n('String') } } self.modeQueue = ['idle']; self.focused = ko.observable(false); self.activeSuggestions = ko.observableArray(); self.insertTag = function(tag) { var mode = tag.indexOf('+') != -1 ? 'scan' : 'rowkey'; var tag = {value: tag, tag: mode} //parse mode self.tags.push(tag); } self.render = function(input, renderers) { var keys = Object.keys(renderers); for(var i=0; i" + ('nested' in renderers[keys[i]] ? self.render(tagged, renderers[keys[i]].nested) : tagged) + ""; }); if(hasMatched && renderers[keys[i]]['strip']) processed = processed.replace(renderers[keys[i]].strip, ''); return processed; }); } return input; }; self.updateMode = function(value) { self.submitted(false); var selection = value.slice(0, self.selectionEnd()); var endindex = selection.slice(selection.lastIndexOf(',')).indexOf(','); if(endindex == -1) endindex = selection.length; var lastbit = value.substring(selection.lastIndexOf(','), endindex).trim(); if(lastbit == "," || lastbit == "") { self.mode('idle'); return; } var tokens = "[]+*{}"; var m = 'rowkey'; for(var i=selection.length - 1; i>=0; i--) { if(tokens.indexOf(selection[i]) != -1) { if(selection[i] == '{') m = 'filter'; else if(selection[i] == '[') m = 'columns'; else if(selection[i] == ']' || selection[i] == '}') m = 'rowkey'; else if(selection[i] == '+') m = 'scan'; else if(selection[i] == '-') m = 'prefix'; break; } } self.mode(m.trim()); }; self.selectionStart = ko.observable(0); self.selectionEnd = ko.observable(0); self.hintText = ko.computed(function() { var pre, s, e; try { var r = window.getSelection().getRangeAt(0); pre = r.startContainer.nodeValue; s = r.startOffset; e = r.endOffset; return pre.slice(0, s) + "" + pre.slice(s, e) + "" + pre.slice(e); } catch (e) { var value = self.cur_input(); var selection = value.slice(0, self.selectionEnd()); var index = selection.lastIndexOf(',') + 1; var endindex = value.slice(index).indexOf(','); endindex = endindex == -1 ? value.length : endindex; pre = value.substring(index, index + endindex); s = self.selectionStart() - index; e = self.selectionEnd() - index; if(s == e) e += 1; s = s < 0 ? 0 : s; e = e > pre.length ? pre.length : e; } return pre.slice(0, s) + "" + pre.slice(s, e) + "" + pre.slice(e); }); self.onKeyDown = function(target, ev) { if(ev.keyCode == 13 && self.cur_input().slice(self.cur_input().lastIndexOf(',')).trim() != ",") { self.evaluate(); return false; } setTimeout(self.updateMenu, 1); return true; }; self.updateMenu = function() { try{ var pos = getEditablePosition(document.getElementById('search-tags')); self.selectionStart(pos); self.selectionEnd(pos); } catch (err) {} self.updateMode(self.cur_input()); self.updateSuggestions(); }; self.replaceFocusNode = function(text) { window.getSelection().getRangeAt(0).startContainer.nodeValue = text; }; self.updateSuggestions = function() { var val = window.getSelection().getRangeAt(0).startContainer.nodeValue; switch(self.mode()) { case 'filter': var focus = val.replace(/\{|\}|\s|&[^;]+?;/g,"").split(/(AND|OR|SKIP|WHILE)/).slice(-1)[0]; self.activeSuggestions(self.filters.filter(function(a) { return a.replace(" ","").indexOf(focus) != -1; })); return; case 'rowkey': var validate = window.getSelection().getRangeAt(0).startContainer.nodeValue; function cancel() { return window.getSelection().getRangeAt(0).startContainer.nodeValue != validate; } function callback() { if(cancel()) return false; API.queryTable('getAutocompleteRows', 10, prepForTransport(validate.trim())).done(function(data) { if(cancel()) return false; self.activeSuggestions(data); }); } setTimeout(callback, 200); return; default: self.activeSuggestions([]); return; } } self.evaluate = function() { table_search(self.cur_input()); self.submitted(true); self.mode('idle'); }; $('#search-tags').blur(function(){ self.focused(false); }); self.doBlur = function() { if(self.cur_input().trim() == "") { function doClick() { if(self.cur_input().trim() != "") return false; setTimeout(function() { $('#search-tags').focus(); }, 1); } $('#search-tags').html('' + $('#search-tags').data("placeholder") + '').one('click', doClick).find('small').on('mousedown', doClick); } } $('#search-tags').focus(function(){ self.focused(true); }); }; var CellHistoryPage = function(options) { var self = this; self.items = ko.observableArray(options.items); self.loading = ko.observable(false); self.reload = function(timestamp, append) { if(!timestamp) timestamp = options.timestamp API.queryTable("getVerTs", options.row, options.column, timestamp, 10, 'null').done(function(res) { self.loading = ko.observable(true); if(!append) self.items(res); else self.items(self.items() + res); self.loading = ko.observable(false); }); }; };