controls.js 29 KB

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