// 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: /\+[0-9 ]+/g, tag: /.+/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.querySet.removeAll(); self.querySet.push(new QuerySetPeice({ 'row_key': 'null', 'scan_length': 10, 'prefix': 'false' })); self._reloadcfs(); }); //fix and decouple self.lastReloadTime = ko.observable(1); //self.columnFamilies.subscribe(function(){self.reload();}); self.searchQuery.subscribe(function goToRow(value) //make this as nice as the renderfucnction and split into two, also fire not down on keyup events { var inputs = value.match(searchRenderers['rowkey']['select']); self.querySet.removeAll(); for(var i=0;i 1 ? parseInt(p[1].trim()) : 0; var extract = inputs[i].match(searchRenderers['rowkey']['nested']['columns']['select']); var columns = extract != null ? extract[0].match(searchRenderers['rowkey']['nested']['columns']['tag']) : []; var filter = inputs[i].match(searchRenderers['rowkey']['nested']['filter']['select']); self.querySet.push(new QuerySetPeice({ 'row_key': p[0].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 != null && filter.length > 0 ? escape(filter[0].slice(1, -1)) : null })); } } routie(app.cluster() + '/' + app.views.tabledata.name() +'/query/' + value); 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; }, 'Timestamp': function(a, b) { a = parseInt(a.timestamp); b = parseInt(b.timestamp); if(a > b) return 1; if(a < b) return -1; return 0; }, 'MIME Type': function() { }, '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) { if($(ev.target).scrollLeft() == ev.target.scrollWidth - ev.target.clientWidth && self.displayedItems().length < self.scrollLoadSource().length) { self.displayRangeLength += 15; self.updateDisplayedItems(); } }; self.drop = function(cont) { function doDrop() { self.isLoading(true); return API.queryTable('deleteAllRow', self.row, "{}").complete(function() { app.views.tabledata.items.remove(self); //decouple later self.isLoading(false); }); } (cont === true) ? doDrop() : confirm("Confirm Delete", 'Delete row ' + self.row + '? (This cannot be undone)', doDrop); }; self.setItems = function(cols) { var colKeys = Object.keys(cols); var items = []; for(var q=0;q 0) self.parent.reload(); //change later self.parent.isLoading(false); }); } (cont === true) ? doDrop() : confirm("Confirm Delete", "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 = ['enable','disable'][self.enabled() << 0], el = $(event.currentTarget); confirm("Confirm "+action, "Are you sure you want to " + action + " this table?", function() //gotta i18n this! { 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(el) { return API.queryCluster('disableTable',self.name).complete(function() { self.enabled(false); }); }; self.drop = function(el) { return API.queryCluster('deleteTable',self.name); }; }; var QuerySetPeice = 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.hints = ko.observableArray([ { hint: 'End Query', shortcut: ',', mode: ['rowkey', 'prefix', 'scan'], selected: false }, { hint: 'Mark Row Prefix', shortcut: '*', mode: ['rowkey'], selected: false }, { hint: 'Start Scan', shortcut: '+', mode: ['rowkey', 'prefix'], selected: false }, { hint: 'Start Select Columns', shortcut: '[', mode: ['rowkey', 'prefix'], selected: false }, { hint: 'End Column/Family', shortcut: ',', mode: ['columns'], selected: false }, { hint: 'End Select Columns', shortcut: ']', mode: ['columns'], selected: false }, { hint: 'Start FilterString', shortcut: '{', mode: ['rowkey'], selected: false }, { hint: '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: 'Row Key Value', type: 'String' }, 'scan': { hint: 'Length of Scan or Row Key', type: 'Integer' }, 'columns': { hint: 'Column Family: Column Name', type: 'String' }, 'prefix': { hint: 'Rows starting with', type: 'String' }, 'filter': { hint: 'Thrift FilterString', type: 'String' } } self.modeQueue = ['idle']; self.focused = ko.observable(false); 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) { 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 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; var pre = value.substring(index, index + endindex); var 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.evaluate = function() { app.views.tabledata.searchQuery(self.cur_input()); }; $('#search-tags').blur(function(){ self.focused(false); }); self.doBlur = function() { if(self.cur_input().trim() == "") { function doClick() { $('#search-tags').html(''); 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); }); }; };