controls.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. var searchRenderers = {
  17. 'rowkey': { //class to tag selection
  18. select: /[^\,\{\[]+(([\{\[][^\}\]]+[\}\]])+|)([^\,]+|)/g, //select the substring to process, useful as JS has no lookbehinds old: ([^,]+\[([^,]+(,|)+)+\]|[^,]+)
  19. tag: /.+/g, //select the matches to wrap with tags
  20. nested: {
  21. 'scan': { select: /(([^\\]|\b)\+[0-9]+)/g, tag: /\+[0-9]+/g },
  22. 'columns': {
  23. select: /\[.+\]/g,
  24. tag: /[^,\[\]]+/g, //forced to do this select due to lack of lookbehinds /[\[\]]/g
  25. nested: {
  26. 'range': {
  27. select: /\sto\s/g,
  28. tag: /.+/g
  29. }
  30. }
  31. },
  32. 'prefix': { select: /[^\*\\]+\*/g, tag: /\*/g},
  33. 'filter': {
  34. select: /\{[^\{\}]+\}/,
  35. tag:/[^\{\}]+/g,
  36. nested: {
  37. 'linker': {
  38. select: /\ (AND|OR|SKIP|WHILE)\ /g,
  39. tag: /.+/g
  40. }/*,
  41. 'compare_op': {
  42. select: /[\<\=\!\>]{1,2}/g,
  43. tag: /.+/g
  44. }*/ //will be added eventually after html bug is figured out
  45. }
  46. }
  47. }
  48. }
  49. };
  50. var DataTableViewModel = function(options) {
  51. var self = this, _defaults = {
  52. name: '',
  53. columns: [],
  54. items: [],
  55. reload: function() {
  56. },
  57. el:''
  58. };
  59. options = ko.utils.extend(_defaults,options);
  60. ListViewModel.apply(this, [options]);
  61. self.name = ko.observable(options.name);
  62. self.searchQuery.subscribe(function(value) {
  63. self._table.fnFilter(value);
  64. });
  65. self.columns = ko.observableArray(options.columns);
  66. self._el = $('table[data-datasource="' + options.el + '"]');
  67. self._table = null;
  68. self._initTable = function() {
  69. if(!self._table) {
  70. self._table = self._el.dataTable({
  71. "aoColumnDefs": [ { "bSortable": false, "aTargets": [ 0 ] }
  72. ],
  73. "sDom": 'tr',//this has to be in, change to sDom so you can call filter()
  74. 'bAutoWidth':false,
  75. "iDisplayLength": -1});
  76. return self._table;
  77. }
  78. };
  79. self.sort = function(viewModel, event) {
  80. var el = $(event.currentTarget);
  81. };
  82. var _reload = self.reload;
  83. self.reload = function(callback) {
  84. if(self._table) {
  85. self._table.fnClearTable();
  86. self._table.fnDestroy();
  87. self._table = null;
  88. }
  89. _reload(function() {
  90. self._initTable();
  91. if(callback!=null)
  92. callback();
  93. });
  94. };
  95. self.canDrop = ko.computed(function() {
  96. var selected = self.selected();
  97. if(selected.length <= 0) return false;
  98. for(var i=0; i<selected.length; i++) {
  99. if(selected[i].enabled()) return false;
  100. }
  101. return true;
  102. });
  103. self.canDisable = ko.computed(function() {
  104. var selected = self.selected();
  105. if(selected.length <= 0) return false;
  106. for(var i=0; i<selected.length; i++) {
  107. if(!selected[i].enabled()) return false;
  108. }
  109. return true;
  110. });
  111. self.canEnable = ko.computed(function() {
  112. var selected = self.selected();
  113. if(selected.length <= 0) return false;
  114. for(var i=0; i<selected.length; i++) {
  115. if(selected[i].enabled()) return false;
  116. }
  117. return true;
  118. });
  119. };
  120. //a Listview of Listviews
  121. var SmartViewModel = function(options) {
  122. var self = this;
  123. options = ko.utils.extend({
  124. name: '',
  125. items: [],
  126. reload: function() {
  127. },
  128. el:'',
  129. sortFields: {
  130. 'Row Key': function(a, b) {
  131. return a.row.localeCompare(b.row);
  132. },
  133. 'Column Count': function(a, b) {
  134. a = a.items().length;
  135. b = b.items().length;
  136. if(a > b)
  137. return 1;
  138. if(a < b)
  139. return -1;
  140. return 0;
  141. },
  142. 'Row Key Length': function(a, b) {
  143. a = a.row.length;
  144. b = b.row.length;
  145. if(a > b)
  146. return 1;
  147. if(a < b)
  148. return -1;
  149. return 0;
  150. }
  151. }
  152. }, options);
  153. ListViewModel.apply(this, [options]); //items: [ListView.items[],ListView.items[]]
  154. self.columnFamilies = ko.observableArray();
  155. self.name = ko.observable(options.name);
  156. self.name.subscribe(function(val){
  157. if(!val) return;
  158. self.columnFamilies([]);
  159. self._reloadcfs();
  160. if(app.station() == 'table' && app.search.cur_input())
  161. return;
  162. self.querySet.removeAll();
  163. self.evaluateQuery();
  164. }); //fix and decouple
  165. self.lastReloadTime = ko.observable(1);
  166. self.searchQuery.subscribe(function(value) //make this as nice as the render function and split into two, also fire not down on keyup events
  167. {
  168. if(app.station() != 'table')
  169. return;
  170. if(value.replace(/\s/g, "") == '' || value == null)
  171. routie(app.cluster() + '/' + app.views.tabledata.name());
  172. var inputs = value.match(searchRenderers['rowkey']['select']);
  173. self.querySet.removeAll();
  174. if(inputs) {
  175. for(var i=0; i<inputs.length; i++) {
  176. if(inputs[i].trim() != "" && inputs[i].trim() != ',') {
  177. //pull out filters
  178. var filter = inputs[i].match(searchRenderers['rowkey']['nested']['filter']['select']) || "";
  179. filter = filter != null && filter.length > 0 ? escape(filter[0].trim().slice(1, -1)) : "";
  180. function filterPostfix(postfix) {
  181. return (filter != null && filter.length > 0 ? ' AND ' : ' ' ) + postfix;
  182. }
  183. //pull out columns
  184. var extract = inputs[i].match(searchRenderers['rowkey']['nested']['columns']['select']);
  185. var columns = extract != null ? extract[0].match(searchRenderers['rowkey']['nested']['columns']['tag']) : [];
  186. inputs[i] = inputs[i].replace(extract, '');
  187. //pull out scan
  188. var p = pullFromRenderer(inputs[i], searchRenderers['rowkey']['nested']['scan']);
  189. inputs[i] = inputs[i].replace(p, '');
  190. p = p.split('+');
  191. var scan = p.length > 1 ? parseInt(p[1].trim()) : 0;
  192. //pull out column filters
  193. var toRemove = [];
  194. var cfs = [];
  195. for(var n = 0; n < columns.length; n++) {
  196. var o = columns[n];
  197. if(columns[n].match(searchRenderers['rowkey']['nested']['columns']['nested']['range']['select'])) {
  198. var partitions = columns[n].split(searchRenderers['rowkey']['nested']['columns']['nested']['range']['select']);
  199. filter += filterPostfix("ColumnRangeFilter('" + partitions[0] + "', false, '" + partitions[1] + "', true)");
  200. toRemove.push(n);
  201. } else {
  202. if(o.indexOf(':') == -1) {
  203. toRemove.push(n);
  204. //for each column family push cf and then column
  205. $(self.columnFamilies()).each(function(i, item) {
  206. columns.push(item.name + o);
  207. });
  208. continue;
  209. } else {
  210. o = o.slice(o.indexOf(':') + 1);
  211. }
  212. var colscan = pullFromRenderer(o, searchRenderers['rowkey']['nested']['scan']);
  213. if(colscan) {
  214. colscan = parseInt(colscan.split('+')[1]) + 1;
  215. filter += filterPostfix("ColumnPaginationFilter(" + colscan + ", 0)");
  216. }
  217. var fc = o.replace(pullFromRenderer(o, searchRenderers['rowkey']['nested']['prefix']), '');
  218. if(fc != o) {
  219. filter += filterPostfix("ColumnPrefixFilter('" + o.match(/[^:*]+/g)[0] + "')");
  220. columns[n] = columns[n].split(':')[0] + ':';
  221. }
  222. }
  223. }
  224. for(var n = toRemove.length - 1; n>=0; n--) {
  225. columns.splice(toRemove[n], 1);
  226. };
  227. self.querySet.push(new QuerySetPiece({
  228. 'row_key': inputs[i].replace(/\\(\+|\*|\,)/g, '$1').replace(/[\[\{].+[\]\}]|\*/g,'').trim(), //clean up with column regex selectors instead
  229. 'scan_length': scan ? scan + 1 : 1,
  230. 'columns': columns,
  231. 'prefix': inputs[i].match(searchRenderers['rowkey']['nested']['prefix']['select']) != null,
  232. 'filter': filter.length > 0 ? filter : null
  233. }));
  234. }
  235. }
  236. }
  237. self.evaluateQuery();
  238. });
  239. self._reloadcfs = function(callback) {
  240. return API.queryTable("getColumnDescriptors").done(function(data) {
  241. self.columnFamilies.removeAll();
  242. var keys = Object.keys(data);
  243. for(var i=0;i<keys.length;i++) {
  244. self.columnFamilies.push(new ColumnFamily({name:keys[i], enabled:false}));
  245. }
  246. if(callback!=null)
  247. callback();
  248. });
  249. };
  250. self.columnQuery = ko.observable("");
  251. self.columnQuery.subscribe(function(query) {
  252. var dataRowFilter = function(index, data_row) {
  253. data_row.searchQuery(query);
  254. };
  255. if (self.selected().length > 0) {
  256. $.each(self.selected(), dataRowFilter);
  257. } else {
  258. $.each(self.items(), dataRowFilter);
  259. }
  260. });
  261. self.rows = ko.computed(function() {
  262. var a = [];
  263. var items = this.items();
  264. for(var i=0; i<items.length; i++) {
  265. a.push(items[i].row);
  266. }
  267. return a;
  268. }, self);
  269. self.querySet = ko.observableArray();
  270. self.validateQuery = function() {
  271. if(self.querySet().length == 0) {
  272. self.querySet.push(new QuerySetPiece({
  273. 'row_key': 'null',
  274. 'scan_length': 10,
  275. 'prefix': 'false'
  276. }));
  277. } else {
  278. $(self.querySet()).each(function() {
  279. this.validate();
  280. this.editing(false);
  281. });
  282. }
  283. };
  284. self.addQuery = function() {
  285. self.validateQuery();
  286. self.querySet.push(new QuerySetPiece({onValidate: function() {
  287. //self.reload();
  288. }}))
  289. };
  290. self.evaluateQuery = function(callback) {
  291. self.validateQuery();
  292. self.reload(callback);
  293. };
  294. var _reload = self.reload;
  295. self.reload = function(callback) {
  296. var queryStart = new Date();
  297. _reload(function() {
  298. self.lastReloadTime((new Date() - queryStart)/1000);
  299. if(callback!=null)
  300. callback();
  301. self.isLoading(false);
  302. });
  303. };
  304. self.showGrid = ko.observable(false);
  305. self.showGrid.subscribe(function(val) {
  306. if(val) {
  307. var rows = self.items();
  308. var columns = {};
  309. //build full lookup hash of columns
  310. for(var i=0; i<rows.length; i++) {
  311. var cols = rows[i].items();
  312. for(var q=0; q<cols.length; q++) {
  313. if(!columns[cols[q].name])
  314. columns[cols[q].name] = "";
  315. }
  316. }
  317. for(var i=0; i<rows.length; i++) {
  318. //clone blank template from hash
  319. var new_row = $.extend({}, columns);
  320. var cols = rows[i].items();
  321. var col_list = [];
  322. //set existing values
  323. for(var q=0; q<cols.length; q++) {
  324. new_row[cols[q].name] = cols[q];
  325. }
  326. //build actual row from hash
  327. var keys = Object.keys(new_row);
  328. for(var r=0; r<keys.length; r++) {
  329. if(!new_row[keys[r]]) new_row[keys[r]] = new ColumnRow({ name: keys[r], value: '', parent: rows[i] });
  330. col_list.push(new_row[keys[r]]);
  331. }
  332. //set and sort
  333. rows[i].items(col_list);
  334. rows[i].sortDropDown.sort();
  335. }
  336. } else {
  337. self.reload();
  338. }
  339. });
  340. self.truncateLimit = ko.observable(1500);
  341. self.reachedLimit = ko.computed(function() {
  342. var items = self.items();
  343. for(var i=0; i<items.length; i++) {
  344. if(self.truncateLimit() < items[i].items().length) return true;
  345. }
  346. return false;
  347. });
  348. };
  349. var SmartViewDataRow = function(options) {
  350. var self = this;
  351. options = ko.utils.extend({
  352. sortFields: {
  353. 'Column Family': function(a, b) {
  354. return a.name.localeCompare(b.name);
  355. },
  356. 'Column Name': function(a, b) {
  357. return a.name.split(':')[1].localeCompare(b.name.split(':')[1]);
  358. },
  359. 'Cell Size': function(a, b) {
  360. a = a.value().length;
  361. b = b.value().length;
  362. if(a > b)
  363. return 1;
  364. if(a < b)
  365. return -1;
  366. return 0;
  367. },
  368. 'Cell Value': function(a, b) {
  369. return a.value().localeCompare(b.value());
  370. },
  371. 'Timestamp': function(a, b) {
  372. a = parseInt(a.timestamp);
  373. b = parseInt(b.timestamp);
  374. if(a > b)
  375. return 1;
  376. if(a < b)
  377. return -1;
  378. return 0;
  379. },
  380. 'Column Name Length': function(a, b) {
  381. a = a.name.split(':')[1].length;
  382. b = b.name.split(':')[1].length;
  383. if(a > b)
  384. return 1;
  385. if(a < b)
  386. return -1;
  387. return 0;
  388. }
  389. }
  390. }, options);
  391. DataRow.apply(self,[options]);
  392. ListViewModel.apply(self,[options]);
  393. self.displayedItems = ko.observableArray();
  394. self.displayRangeStart = 0;
  395. self.displayRangeLength = 20;
  396. self.items.subscribe(function() {
  397. self.displayedItems([]);
  398. self.updateDisplayedItems();
  399. });
  400. self.searchQuery.subscribe(function(searchValue) {
  401. self.scrollLoadSource = ko.computed(function(){
  402. return self.items().filter(function(column) {
  403. return column.name.toLowerCase().indexOf(searchValue.toLowerCase()) != -1;
  404. });
  405. });
  406. self.displayRangeLength = 20;
  407. self.updateDisplayedItems();
  408. });
  409. self.scrollLoadSource = self.items;
  410. self.updateDisplayedItems = function() {
  411. var x = self.displayRangeStart;
  412. self.displayedItems(self.scrollLoadSource().slice(x, x + self.displayRangeLength));
  413. };
  414. self.resetScrollLoad = function() {
  415. self.scrollLoadSource = self.items;
  416. self.updateDisplayedItems();
  417. };
  418. self.isCollapsed = ko.observable(false);
  419. self.toggleSelectedCollapse = function() {
  420. if(!self.isCollapsed()) {
  421. self.displayedItems(self.displayedItems().filter(function(item) {
  422. return item.isSelected();
  423. }));
  424. self.scrollLoadSource = self.displayedItems;
  425. self.isCollapsed(true);
  426. }
  427. else {
  428. self.isCollapsed(false);
  429. self.resetScrollLoad();
  430. }
  431. };
  432. self.onScroll = function(target, ev) {
  433. var displayRangeDelta = 15;
  434. if($(ev.target).scrollLeft() == ev.target.scrollWidth - ev.target.clientWidth) {
  435. if(self.displayedItems().length < self.scrollLoadSource().length) {
  436. self.displayRangeLength += displayRangeDelta;
  437. self.updateDisplayedItems();
  438. } else {
  439. self.displayRangeLength = self.items().length + displayRangeDelta;
  440. var validate = self.items().length;
  441. API.queryTable('getRowPartial' , prepForTransport(self.row), self.items().length, 100).done(function(data) {
  442. if(self.items().length != validate) return false;
  443. var cols = data[0].columns;
  444. var keys = Object.keys(cols);
  445. var temp = [];
  446. for(var i=0; i<keys.length; i++) {
  447. var col = cols[keys[i]];
  448. temp.push(new ColumnRow({name: keys[i],
  449. timestamp: col.timestamp,
  450. value: col.value,
  451. parent: self}));
  452. }
  453. self.items(self.items().concat(temp));
  454. });
  455. }
  456. }
  457. };
  458. self.drop = function(cont) {
  459. function doDrop() {
  460. self.isLoading(true);
  461. return API.queryTable('deleteAllRow', self.row, "{}").complete(function() {
  462. app.views.tabledata.items.remove(self); //decouple later
  463. self.isLoading(false);
  464. });
  465. }
  466. (cont === true) ? doDrop() : confirm(i18n("Confirm Delete"), i18n('Delete row ') + self.row + i18n('? (This cannot be undone)'), doDrop);
  467. };
  468. self.setItems = function(cols) {
  469. var colKeys = Object.keys(cols);
  470. var items = [];
  471. for(var q=0; q<colKeys.length; q++) {
  472. items.push(new ColumnRow({name: colKeys[q],
  473. timestamp: cols[colKeys[q]].timestamp,
  474. value: cols[colKeys[q]].value,
  475. parent: self}));
  476. }
  477. self.items(items);
  478. return self.items();
  479. };
  480. self.selectAllVisible = function(){
  481. for(t=0; t<self.displayedItems().length; t++)
  482. self.displayedItems()[t].isSelected(true);
  483. return self;
  484. };
  485. self.deselectAllVisible = function(){
  486. for(t=0; t<self.displayedItems().length; t++)
  487. self.displayedItems()[t].isSelected(false);
  488. return self;
  489. };
  490. self.toggleSelectAllVisible = function() {
  491. if(self.selected().length != self.displayedItems().length)
  492. return self.selectAllVisible();
  493. return self.deselectAllVisible();
  494. };
  495. self.push = function(item) {
  496. var column = new ColumnRow(item);
  497. self.items.push(column);
  498. };
  499. var _reload = self.reload;
  500. self.reload = function(callback) {
  501. logGA('get_row');
  502. _reload(function() {
  503. if(callback!=null)
  504. callback();
  505. self.isLoading(false);
  506. });
  507. };
  508. };
  509. var ColumnRow = function(options) {
  510. var self = this;
  511. ko.utils.extend(self,options);
  512. DataRow.apply(self,[options]);
  513. self.value = ko.observable(self.value);
  514. self.history = new CellHistoryPage({row: self.parent.row, column: self.name, timestamp: self.timestamp, items: []});
  515. self.drop = function(cont) {
  516. function doDrop() {
  517. logGA('filter_columns');
  518. self.parent.isLoading(true);
  519. return API.queryTable('deleteColumn', prepForTransport(self.parent.row), prepForTransport(self.name)).done(function(data) {
  520. self.parent.items.remove(self);
  521. if(self.parent.items().length > 0)
  522. self.parent.reload(); //change later
  523. self.parent.isLoading(false);
  524. });
  525. }
  526. (cont === true) ? doDrop() : confirm(i18n("Confirm Delete"), i18n("Are you sure you want to drop this column?"), doDrop);
  527. };
  528. self.reload = function(callback, skipPut) {
  529. self.isLoading(true);
  530. API.queryTable('get', prepForTransport(self.parent.row), prepForTransport(self.name), 'null').done(function(data) {
  531. if(data.length > 0 && !skipPut)
  532. self.value(data[0].value);
  533. if(typeof callback !== "undefined" && callback != null)
  534. callback();
  535. self.isLoading(false);
  536. });
  537. };
  538. self.value.subscribe(function(value) {
  539. //change transport prep to object wrapper
  540. logGA('put_column');
  541. API.queryTable('putColumn', prepForTransport(self.parent.row), prepForTransport(self.name), "hbase-post-key-" + JSON.stringify(value)).done(function(data) {
  542. self.reload(function(){}, true);
  543. });
  544. self.editing(false);
  545. });
  546. self.editing = ko.observable(false);
  547. self.isLoading = ko.observable(false); //move to baseclass
  548. };
  549. var SortDropDownView = function(options) {
  550. var self = this;
  551. options = ko.utils.extend({
  552. sortFields: {},
  553. target: null
  554. }, options);
  555. BaseModel.apply(self,[options]);
  556. self.target = options.target;
  557. self.sortAsc = ko.observable(true);
  558. self.sortAsc.subscribe(function(){self.sort()});
  559. self.sortField = ko.observable("");
  560. self.sortField.subscribe(function(){self.sort()});
  561. self.sortFields = ko.observableArray(Object.keys(options.sortFields)); // change to ko.computed?
  562. self.sortFunctionHash = ko.observable(options.sortFields);
  563. self.toggleSortMode = function() {
  564. self.sortAsc(!self.sortAsc());
  565. };
  566. self.sort = function() {
  567. if (!self.target || !(self.sortFields().length > 0)) return;
  568. self.target.sort(function(a, b) {
  569. 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
  570. });
  571. };
  572. };
  573. var TableDataRow = function(options) {
  574. var self = this;
  575. options = ko.utils.extend({
  576. name:"",
  577. enabled:true
  578. }, options);
  579. DataRow.apply(self,[options]);
  580. self.name = options['name'];
  581. self.enabled = ko.observable(options['enabled']);
  582. self.toggle = function(viewModel,event){
  583. var action = [i18n('enable'), i18n('disable')][self.enabled() << 0], el = $(event.currentTarget);
  584. confirm(i18n("Confirm") + " " + action, i18n("Are you sure you want to") + " " + action + " " + i18n("this table?"), function()
  585. {
  586. el.showIndicator();
  587. return self[action](el).complete(function() {
  588. el.hideIndicator();
  589. });
  590. });
  591. };
  592. self.enable = function(el) {
  593. return API.queryCluster('enableTable',self.name).complete(function() {
  594. self.enabled(true);
  595. });
  596. };
  597. self.disable = function(callback) {
  598. return API.queryCluster('disableTable',self.name).complete(function() {
  599. self.enabled(false);
  600. if($.isFunction(callback)) callback();
  601. });
  602. };
  603. self.drop = function(el) {
  604. return API.queryCluster('deleteTable',self.name);
  605. };
  606. };
  607. var QuerySetPiece = function(options) {
  608. var self = this;
  609. options = ko.utils.extend({
  610. row_key: "null",
  611. scan_length: 1,
  612. prefix: false,
  613. columns: [],
  614. filter: null,
  615. onValidate: function(){}
  616. }, options);
  617. BaseModel.apply(self,[options]);
  618. self.row_key = ko.observable(options.row_key);
  619. self.scan_length = ko.observable(options.scan_length);
  620. self.columns = ko.observableArray(options.columns);
  621. self.prefix = ko.observable(options.prefix);
  622. self.filter = ko.observable(options.filter);
  623. self.editing = ko.observable(true);
  624. self.validate = function() {
  625. if(self.scan_length() <= 0 || self.row_key() == "")
  626. return app.views.tabledata.querySet.remove(self); //change later
  627. return options.onValidate();
  628. };
  629. self.row_key.subscribe(self.validate.bind());
  630. self.scan_length.subscribe(self.validate.bind());
  631. };
  632. var ColumnFamily = function(options) {
  633. this.name = options.name;
  634. this.enabled = ko.observable(options.enabled);
  635. this.toggle = function() {
  636. this.enabled(!this.enabled());
  637. app.views.tabledata.reload();
  638. };
  639. }
  640. //tagsearch
  641. var tagsearch = function() {
  642. var self = this;
  643. self.tags = ko.observableArray();
  644. self.mode = ko.observable('idle');
  645. self.cur_input = ko.observable('');
  646. self.submitted = ko.observable(false);
  647. self.filters = ["KeyOnlyFilter ()", "FirstKeyOnlyFilter ()", "PrefixFilter ('row_prefix')", "ColumnPrefixFilter('column_prefix')", "MultipleColumnPrefixFilter('column_prefix', 'column_prefix', …, 'column_prefix')", "ColumnCountGetFilter (limit)", "PageFilter (page_size)", "ColumnPaginationFilter(limit, offset)", "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)"];
  648. self.hints = ko.observableArray([ {
  649. hint: i18n('End Query'),
  650. shortcut: ',',
  651. mode: ['rowkey', 'prefix', 'scan'],
  652. selected: false
  653. }, {
  654. hint: i18n('Prefix Scan'),
  655. shortcut: '*',
  656. mode: ['rowkey', 'columns'],
  657. selected: false
  658. }, {
  659. hint: i18n('Start Scan'),
  660. shortcut: '+',
  661. mode: ['rowkey', 'prefix', 'columns'],
  662. selected: false
  663. }, {
  664. hint: i18n('Start Select Columns'),
  665. shortcut: '[',
  666. mode: ['rowkey', 'prefix'],
  667. selected: false
  668. }, {
  669. hint: i18n('End Column/Family'),
  670. shortcut: ',',
  671. mode: ['columns'],
  672. selected: false
  673. }, {
  674. hint: i18n('End Select Columns'),
  675. shortcut: ']',
  676. mode: ['columns'],
  677. selected: false
  678. }, {
  679. hint: i18n('Column Range'),
  680. shortcut: ' to ',
  681. mode: ['columns'],
  682. selected: false
  683. },
  684. {
  685. hint: i18n('Start FilterString'),
  686. shortcut: '{',
  687. mode: ['rowkey'],
  688. selected: false
  689. },
  690. {
  691. hint: i18n('End FilterString'),
  692. shortcut: '}',
  693. mode: ['filter'],
  694. selected: false
  695. }
  696. ]);
  697. self.activeHints = ko.computed(function() {
  698. var ret = [];
  699. $(self.hints()).each(function(i, hint) {
  700. if (hint.mode.indexOf(self.mode()) > -1)
  701. ret.push(hint);
  702. });
  703. return ret;
  704. });
  705. self.activeHint = ko.observable(-1);
  706. self.modes = {
  707. 'rowkey': {
  708. hint: i18n('Row Key Value'),
  709. type: i18n('String')
  710. },
  711. 'scan': {
  712. hint: i18n('Length of Scan or Row Key'),
  713. type: i18n('Integer')
  714. },
  715. 'columns': {
  716. hint: i18n('Column Family: Column Name'),
  717. type: i18n('String')
  718. },
  719. 'prefix': {
  720. hint: i18n('Rows starting with'),
  721. type: i18n('String')
  722. },
  723. 'filter': {
  724. hint: i18n('Thrift FilterString'),
  725. type: i18n('String')
  726. }
  727. }
  728. self.modeQueue = ['idle'];
  729. self.focused = ko.observable(false);
  730. self.activeSuggestions = ko.observableArray();
  731. self.activeSuggestion = ko.observable(-1);
  732. self.insertTag = function(tag) {
  733. var mode = tag.indexOf('+') != -1 ? 'scan' : 'rowkey';
  734. var tag = {value: tag, tag: mode} //parse mode
  735. self.tags.push(tag);
  736. }
  737. self.render = function(input, renderers) {
  738. var keys = Object.keys(renderers);
  739. for(var i=0; i<keys.length; i++) {
  740. input = input.replace(renderers[keys[i]].select, function(selected) {
  741. var hasMatched = false;
  742. var processed = selected.replace(renderers[keys[i]].tag, function(tagged) {
  743. hasMatched = true;
  744. return "<span class='" + keys[i] + " tagsearchTag' title='" + keys[i] + "' data-toggle='tooltip'>" + ('nested' in renderers[keys[i]] ? self.render(tagged, renderers[keys[i]].nested) : tagged) + "</span>";
  745. });
  746. if(hasMatched && renderers[keys[i]]['strip'])
  747. processed = processed.replace(renderers[keys[i]].strip, '');
  748. return processed;
  749. });
  750. }
  751. return input;
  752. };
  753. self.updateMode = function(value) {
  754. self.submitted(false);
  755. var selection = value.slice(0, self.selectionEnd());
  756. var endindex = selection.slice(selection.lastIndexOf(',')).indexOf(',');
  757. if(endindex == -1) endindex = selection.length;
  758. var lastbit = value.substring(selection.lastIndexOf(','), endindex).trim();
  759. if(lastbit == "," || lastbit == "") {
  760. self.mode('idle');
  761. return;
  762. }
  763. var tokens = "[]+*{}";
  764. var m = 'rowkey';
  765. for(var i=selection.length - 1; i>=0; i--) {
  766. if(tokens.indexOf(selection[i]) != -1) {
  767. if(selection[i] == '{')
  768. m = 'filter';
  769. else if(selection[i] == '[')
  770. m = 'columns';
  771. else if(selection[i] == ']' || selection[i] == '}')
  772. m = 'rowkey';
  773. else if(selection[i] == '+')
  774. m = 'scan';
  775. else if(selection[i] == '-')
  776. m = 'prefix';
  777. break;
  778. }
  779. }
  780. self.mode(m.trim());
  781. };
  782. self.selectionStart = ko.observable(0);
  783. self.selectionEnd = ko.observable(0);
  784. self.hintText = ko.computed(function() {
  785. var pre, s, e;
  786. try {
  787. var r = window.getSelection().getRangeAt(0);
  788. pre = r.startContainer.nodeValue;
  789. s = r.startOffset;
  790. e = r.endOffset;
  791. return pre.slice(0, s) + "<span class='selection'>" + pre.slice(s, e) + "</span>" + pre.slice(e);
  792. } catch (e) {
  793. var value = self.cur_input();
  794. var selection = value.slice(0, self.selectionEnd());
  795. var index = selection.lastIndexOf(',') + 1;
  796. var endindex = value.slice(index).indexOf(',');
  797. endindex = endindex == -1 ? value.length : endindex;
  798. pre = value.substring(index, index + endindex);
  799. s = self.selectionStart() - index;
  800. e = self.selectionEnd() - index;
  801. if(s == e)
  802. e += 1;
  803. s = s < 0 ? 0 : s;
  804. e = e > pre.length ? pre.length : e;
  805. }
  806. return pre.slice(0, s) + "<span class='selection'>" + pre.slice(s, e) + "</span>" + pre.slice(e);
  807. });
  808. self.onKeyDown = function(target, ev) {
  809. if(ev.keyCode == 13 && self.cur_input().slice(self.cur_input().lastIndexOf(',')).trim() != ",") {
  810. if(self.activeSuggestion() > -1) {
  811. self.replaceFocusNode(self.activeSuggestions()[self.activeSuggestion()]);
  812. self.activeSuggestion(-1);
  813. setTimeout(function() {
  814. var s = self.cur_input(), r;
  815. if(s.lastIndexOf('(') != -1 && s.lastIndexOf(')') != -1) {
  816. r = setCursor($('#search-tags')[0], s.lastIndexOf('(') + 1);
  817. r.setEnd(r.endContainer, r.startOffset + (s.lastIndexOf(')') - s.lastIndexOf('(') - 1));
  818. } else {
  819. r = setCursor($('#search-tags')[0], s.length);
  820. }
  821. var sel = window.getSelection();
  822. sel.removeAllRanges();
  823. sel.addRange(r);
  824. }, 1);
  825. } else {
  826. self.evaluate();
  827. }
  828. return false;
  829. } else if (ev.keyCode == 219) {
  830. setTimeout(function() {
  831. var ep = getEditablePosition($('#search-tags')[0], true);
  832. self.cur_input(self.cur_input().slice(0, ep) + (ev.shiftKey ? '}' : ']') + self.cur_input().slice(ep));
  833. }, 1);
  834. } else if (ev.keyCode == 40) {
  835. if(self.activeSuggestion() < self.activeSuggestions().length - 1)
  836. self.activeSuggestion(self.activeSuggestion() + 1);
  837. return false;
  838. } else if(ev.keyCode == 38) {
  839. if(self.activeSuggestion() > 0)
  840. self.activeSuggestion(self.activeSuggestion() - 1);
  841. return false;
  842. }
  843. setTimeout(self.updateMenu, 1);
  844. return true;
  845. };
  846. self.updateMenu = function() {
  847. self.activeSuggestion(-1);
  848. try{
  849. var pos = getEditablePosition(document.getElementById('search-tags'));
  850. self.selectionStart(pos);
  851. self.selectionEnd(pos);
  852. } catch (err) {}
  853. self.updateMode(self.cur_input());
  854. self.updateSuggestions();
  855. };
  856. self.replaceFocusNode = function(text) {
  857. window.getSelection().getRangeAt(0).startContainer.nodeValue = text;
  858. };
  859. self.updateSuggestions = function() {
  860. var val = window.getSelection().getRangeAt(0).startContainer.nodeValue;
  861. switch(self.mode()) {
  862. case 'filter':
  863. var focus = val.replace(/\{|\}|\s|&[^;]+?;/g,"").split(searchRenderers.rowkey.nested.filter.nested.linker.select).slice(-1)[0];
  864. if(focus != "") {
  865. self.activeSuggestions(self.filters.filter(function(a) {
  866. return a.toLowerCase().replace(" ","").indexOf(focus.toLowerCase()) != -1;
  867. }));
  868. } else {
  869. self.activeSuggestions([]);
  870. }
  871. return;
  872. case 'rowkey':
  873. var validate = window.getSelection().getRangeAt(0).startContainer.nodeValue;
  874. function cancel() {
  875. return window.getSelection().getRangeAt(0).startContainer.nodeValue != validate;
  876. }
  877. function callback() {
  878. if(cancel()) return false;
  879. if(validate.trim() != "") {
  880. API.queryTable('getAutocompleteRows', 10, prepForTransport(validate.trim())).done(function(data) {
  881. if(cancel()) return false;
  882. self.activeSuggestions(data);
  883. });
  884. }
  885. }
  886. setTimeout(callback, 200);
  887. return;
  888. default:
  889. self.activeSuggestions([]);
  890. return;
  891. }
  892. }
  893. self.evaluate = function() {
  894. table_search(self.cur_input());
  895. self.submitted(true);
  896. self.mode('idle');
  897. };
  898. $('#search-tags').blur(function(){
  899. self.focused(false);
  900. });
  901. self.doBlur = function() {
  902. if(self.cur_input().trim() == "") {
  903. function doClick() {
  904. if(self.cur_input().trim() != "") return false;
  905. setTimeout(function() {
  906. $('#search-tags').focus();
  907. }, 1);
  908. }
  909. $('#search-tags').html('<small>' + $('#search-tags').data("placeholder") + '</small>').one('click', doClick).find('small').on('mousedown', doClick);
  910. }
  911. }
  912. $('#search-tags').focus(function(){
  913. self.focused(true);
  914. });
  915. };
  916. var CellHistoryPage = function(options) {
  917. var self = this;
  918. self.items = ko.observableArray(options.items);
  919. self.loading = ko.observable(false);
  920. self.reload = function(timestamp, append) {
  921. if(!timestamp)
  922. timestamp = options.timestamp
  923. API.queryTable("getVerTs", prepForTransport(options.row), prepForTransport(options.column), timestamp, 10, 'null').done(function(res) {
  924. self.loading = ko.observable(true);
  925. if(!append)
  926. self.items(res);
  927. else
  928. self.items(self.items() + res);
  929. self.loading = ko.observable(false);
  930. });
  931. };
  932. self.pickHistory = function(data) {
  933. data.history = self;
  934. if(!ko.isObservable(data.value))
  935. data.value = ko.observable(data.value);
  936. launchModal('cell_edit_modal',{ content: data, mime: detectMimeType(data.value()), readonly: true })
  937. };
  938. };