controls.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  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(){
  157. self.columnFamilies([]);
  158. self._reloadcfs();
  159. if(app.station() == 'table')
  160. return;
  161. self.querySet.removeAll();
  162. self.evaluateQuery();
  163. }); //fix and decouple
  164. self.lastReloadTime = ko.observable(1);
  165. self.searchQuery.subscribe(function goToRow(value) //make this as nice as the renderfucnction and split into two, also fire not down on keyup events
  166. {
  167. if(app.station() != 'table')
  168. return;
  169. if(value.replace(/\s/g, "") == '' || value == null)
  170. routie(app.cluster() + '/' + app.views.tabledata.name());
  171. var inputs = value.match(searchRenderers['rowkey']['select']);
  172. self.querySet.removeAll();
  173. if(inputs) {
  174. for(var i=0; i<inputs.length; i++) {
  175. if(inputs[i].trim() != "" && inputs[i].trim() != ',') {
  176. //pull out filters
  177. var filter = inputs[i].match(searchRenderers['rowkey']['nested']['filter']['select']) || "";
  178. filter = filter != null && filter.length > 0 ? escape(filter[0].trim().slice(1, -1)) : "";
  179. function filterPostfix(postfix) {
  180. return (filter != null && filter.length > 0 ? ' AND ' : ' ' ) + postfix;
  181. }
  182. //pull out columns
  183. var extract = inputs[i].match(searchRenderers['rowkey']['nested']['columns']['select']);
  184. var columns = extract != null ? extract[0].match(searchRenderers['rowkey']['nested']['columns']['tag']) : [];
  185. inputs[i] = inputs[i].replace(extract, '');
  186. //pull out scan
  187. var p = pullFromRenderer(inputs[i], searchRenderers['rowkey']['nested']['scan']);
  188. inputs[i] = inputs[i].replace(p, '');
  189. p = p.split('+');
  190. var scan = p.length > 1 ? parseInt(p[1].trim()) : 0;
  191. //pull out column filters
  192. var toRemove = [];
  193. var cfs = [];
  194. for(var n = 0; n < columns.length; n++) {
  195. var o = columns[n];
  196. if(columns[n].match(searchRenderers['rowkey']['nested']['columns']['nested']['range']['select'])) {
  197. var partitions = columns[n].split(searchRenderers['rowkey']['nested']['columns']['nested']['range']['select']);
  198. filter += filterPostfix("ColumnRangeFilter('" + partitions[0] + "', false, '" + partitions[1] + "', true)");
  199. toRemove.push(n);
  200. } else {
  201. if(o.indexOf(':') == -1) {
  202. toRemove.push(n);
  203. //for each column family push cf and then column
  204. $(self.columnFamilies()).each(function(i, item) {
  205. columns.push(item.name + o);
  206. });
  207. continue;
  208. } else {
  209. o = o.slice(o.indexOf(':') + 1);
  210. }
  211. var colscan = pullFromRenderer(o, searchRenderers['rowkey']['nested']['scan']);
  212. if(colscan) {
  213. colscan = parseInt(colscan.split('+')[1]) + 1;
  214. filter += filterPostfix("ColumnPaginationFilter(" + colscan + ", 0)");
  215. }
  216. var fc = o.replace(pullFromRenderer(o, searchRenderers['rowkey']['nested']['prefix']), '');
  217. if(fc != o) {
  218. filter += filterPostfix("ColumnPrefixFilter('" + o.match(/[^:*]+/g)[0] + "')");
  219. columns[n] = columns[n].split(':')[0] + ':';
  220. }
  221. }
  222. }
  223. for(var n = toRemove.length - 1; n>=0; n--) {
  224. columns.splice(toRemove[n], 1);
  225. };
  226. self.querySet.push(new QuerySetPiece({
  227. 'row_key': inputs[i].replace(/\\(\+|\*|\,)/g, '$1').replace(/[\[\{].+[\]\}]|\*/g,'').trim(), //clean up with column regex selectors instead
  228. 'scan_length': scan ? scan + 1 : 1,
  229. 'columns': columns,
  230. 'prefix': inputs[i].match(searchRenderers['rowkey']['nested']['prefix']['select']) != null,
  231. 'filter': filter.length > 0 ? filter : null
  232. }));
  233. }
  234. }
  235. }
  236. self.evaluateQuery();
  237. });
  238. self._reloadcfs = function(callback) {
  239. return API.queryTable("getColumnDescriptors").done(function(data) {
  240. self.columnFamilies.removeAll();
  241. var keys = Object.keys(data);
  242. for(var i=0;i<keys.length;i++) {
  243. self.columnFamilies.push(new ColumnFamily({name:keys[i], enabled:false}));
  244. }
  245. if(callback!=null)
  246. callback();
  247. });
  248. };
  249. self.columnQuery = ko.observable("");
  250. self.columnQuery.subscribe(function(query) {
  251. $(self.items()).each(function() {
  252. table_search(query);
  253. });
  254. });
  255. self.rows = ko.computed(function() {
  256. var a = [];
  257. var items = this.items();
  258. for(var i=0; i<items.length; i++) {
  259. a.push(items[i].row);
  260. }
  261. return a;
  262. }, self);
  263. self.querySet = ko.observableArray();
  264. self.validateQuery = function() {
  265. if(self.querySet().length == 0) {
  266. self.querySet.push(new QuerySetPiece({
  267. 'row_key': 'null',
  268. 'scan_length': 10,
  269. 'prefix': 'false'
  270. }));
  271. } else {
  272. $(self.querySet()).each(function() {
  273. this.validate();
  274. this.editing(false);
  275. });
  276. }
  277. };
  278. self.addQuery = function() {
  279. self.validateQuery();
  280. self.querySet.push(new QuerySetPiece({onValidate: function() {
  281. //self.reload();
  282. }}))
  283. };
  284. self.evaluateQuery = function(callback) {
  285. self.validateQuery();
  286. self.reload(callback);
  287. };
  288. var _reload = self.reload;
  289. self.reload = function(callback) {
  290. var queryStart = new Date();
  291. _reload(function() {
  292. self.lastReloadTime((new Date() - queryStart)/1000);
  293. if(callback!=null)
  294. callback();
  295. self.isLoading(false);
  296. });
  297. };
  298. self.showGrid = ko.observable(false);
  299. self.showGrid.subscribe(function(val) {
  300. if(val) {
  301. var rows = self.items();
  302. var columns = {};
  303. //build full lookup hash of columns
  304. for(var i=0; i<rows.length; i++) {
  305. var cols = rows[i].items();
  306. for(var q=0; q<cols.length; q++) {
  307. if(!columns[cols[q].name])
  308. columns[cols[q].name] = "";
  309. }
  310. }
  311. for(var i=0; i<rows.length; i++) {
  312. //clone blank template from hash
  313. var new_row = $.extend({}, columns);
  314. var cols = rows[i].items();
  315. var col_list = [];
  316. //set existing values
  317. for(var q=0; q<cols.length; q++) {
  318. new_row[cols[q].name] = cols[q];
  319. }
  320. //build actual row from hash
  321. var keys = Object.keys(new_row);
  322. for(var r=0; r<keys.length; r++) {
  323. if(!new_row[keys[r]]) new_row[keys[r]] = new ColumnRow({ name: keys[r], value: '', parent: rows[i] });
  324. col_list.push(new_row[keys[r]]);
  325. }
  326. //set and sort
  327. rows[i].items(col_list);
  328. rows[i].sortDropDown.sort();
  329. }
  330. } else {
  331. self.reload();
  332. }
  333. });
  334. self.truncateLimit = ko.observable(1500);
  335. self.reachedLimit = ko.computed(function() {
  336. var items = self.items();
  337. for(var i=0; i<items.length; i++) {
  338. if(self.truncateLimit() < items[i].items().length) return true;
  339. }
  340. return false;
  341. });
  342. };
  343. var SmartViewDataRow = function(options) {
  344. var self = this;
  345. options = ko.utils.extend({
  346. sortFields: {
  347. 'Column Family': function(a, b) {
  348. return a.name.localeCompare(b.name);
  349. },
  350. 'Column Name': function(a, b) {
  351. return a.name.split(':')[1].localeCompare(b.name.split(':')[1]);
  352. },
  353. 'Cell Size': function(a, b) {
  354. a = a.value().length;
  355. b = b.value().length;
  356. if(a > b)
  357. return 1;
  358. if(a < b)
  359. return -1;
  360. return 0;
  361. },
  362. 'Cell Value': function(a, b) {
  363. return a.value().localeCompare(b.value());
  364. },
  365. 'Timestamp': function(a, b) {
  366. a = parseInt(a.timestamp);
  367. b = parseInt(b.timestamp);
  368. if(a > b)
  369. return 1;
  370. if(a < b)
  371. return -1;
  372. return 0;
  373. },
  374. 'Column Name Length': function(a, b) {
  375. a = a.name.split(':')[1].length;
  376. b = b.name.split(':')[1].length;
  377. if(a > b)
  378. return 1;
  379. if(a < b)
  380. return -1;
  381. return 0;
  382. }
  383. }
  384. }, options);
  385. DataRow.apply(self,[options]);
  386. ListViewModel.apply(self,[options]);
  387. self.displayedItems = ko.observableArray();
  388. self.displayRangeStart = 0;
  389. self.displayRangeLength = 20;
  390. self.items.subscribe(function() {
  391. self.displayedItems([]);
  392. self.updateDisplayedItems();
  393. });
  394. self.searchQuery.subscribe(function(searchValue) {
  395. self.scrollLoadSource = ko.computed(function(){
  396. return self.items().filter(function(column) {
  397. return column.name.toLowerCase().indexOf(searchValue.toLowerCase()) != -1;
  398. });
  399. });
  400. self.displayRangeLength = 20;
  401. self.updateDisplayedItems();
  402. });
  403. self.scrollLoadSource = self.items;
  404. self.updateDisplayedItems = function() {
  405. var x = self.displayRangeStart;
  406. self.displayedItems(self.scrollLoadSource().slice(x, x + self.displayRangeLength));
  407. };
  408. self.resetScrollLoad = function() {
  409. self.scrollLoadSource = self.items;
  410. self.updateDisplayedItems();
  411. };
  412. self.isCollapsed = ko.observable(false);
  413. self.toggleSelectedCollapse = function() {
  414. if(!self.isCollapsed()) {
  415. self.displayedItems(self.displayedItems().filter(function(item) {
  416. return item.isSelected();
  417. }));
  418. self.scrollLoadSource = self.displayedItems;
  419. self.isCollapsed(true);
  420. }
  421. else {
  422. self.isCollapsed(false);
  423. self.resetScrollLoad();
  424. }
  425. };
  426. self.onScroll = function(target, ev) {
  427. var displayRangeDelta = 15;
  428. if($(ev.target).scrollLeft() == ev.target.scrollWidth - ev.target.clientWidth) {
  429. if(self.displayedItems().length < self.scrollLoadSource().length) {
  430. self.displayRangeLength += displayRangeDelta;
  431. self.updateDisplayedItems();
  432. } else {
  433. self.displayRangeLength = self.items().length + displayRangeDelta;
  434. var validate = self.items().length;
  435. API.queryTable('getRowPartial' , prepForTransport(self.row), self.items().length, 100).done(function(data) {
  436. if(self.items().length != validate) return false;
  437. var cols = data[0].columns;
  438. var keys = Object.keys(cols);
  439. var temp = [];
  440. for(var i=0; i<keys.length; i++) {
  441. var col = cols[keys[i]];
  442. temp.push(new ColumnRow({name: keys[i],
  443. timestamp: col.timestamp,
  444. value: col.value,
  445. parent: self}));
  446. }
  447. self.items(self.items().concat(temp));
  448. });
  449. }
  450. }
  451. };
  452. self.drop = function(cont) {
  453. function doDrop() {
  454. self.isLoading(true);
  455. return API.queryTable('deleteAllRow', self.row, "{}").complete(function() {
  456. app.views.tabledata.items.remove(self); //decouple later
  457. self.isLoading(false);
  458. });
  459. }
  460. (cont === true) ? doDrop() : confirm(i18n("Confirm Delete"), i18n('Delete row ') + self.row + i18n('? (This cannot be undone)'), doDrop);
  461. };
  462. self.setItems = function(cols) {
  463. var colKeys = Object.keys(cols);
  464. var items = [];
  465. for(var q=0; q<colKeys.length; q++) {
  466. items.push(new ColumnRow({name: colKeys[q],
  467. timestamp: cols[colKeys[q]].timestamp,
  468. value: cols[colKeys[q]].value,
  469. parent: self}));
  470. }
  471. self.items(items);
  472. return self.items();
  473. };
  474. self.selectAllVisible = function(){
  475. for(t=0; t<self.displayedItems().length; t++)
  476. self.displayedItems()[t].isSelected(true);
  477. return self;
  478. };
  479. self.toggleSelectAllVisible = function() {
  480. if(self.selected().length != self.displayedItems().length)
  481. return self.selectAllVisible();
  482. return self.deselectAll();
  483. };
  484. self.push = function(item) {
  485. var column = new ColumnRow(item);
  486. self.items.push(column);
  487. };
  488. var _reload = self.reload;
  489. self.reload = function(callback) {
  490. logGA('get_row');
  491. _reload(function() {
  492. if(callback!=null)
  493. callback();
  494. self.isLoading(false);
  495. });
  496. };
  497. };
  498. var ColumnRow = function(options) {
  499. var self = this;
  500. ko.utils.extend(self,options);
  501. DataRow.apply(self,[options]);
  502. self.value = ko.observable(self.value);
  503. self.history = new CellHistoryPage({row: self.parent.row, column: self.name, timestamp: self.timestamp, items: []});
  504. self.drop = function(cont) {
  505. function doDrop() {
  506. logGA('filter_columns');
  507. self.parent.isLoading(true);
  508. return API.queryTable('deleteColumn', prepForTransport(self.parent.row), prepForTransport(self.name)).done(function(data) {
  509. self.parent.items.remove(self);
  510. if(self.parent.items().length > 0)
  511. self.parent.reload(); //change later
  512. self.parent.isLoading(false);
  513. });
  514. }
  515. (cont === true) ? doDrop() : confirm(i18n("Confirm Delete"), i18n("Are you sure you want to drop this column?"), doDrop);
  516. };
  517. self.reload = function(callback, skipPut) {
  518. self.isLoading(true);
  519. API.queryTable('get', prepForTransport(self.parent.row), prepForTransport(self.name), 'null').done(function(data) {
  520. if(data.length > 0 && !skipPut)
  521. self.value(data[0].value);
  522. if(typeof callback !== "undefined" && callback != null)
  523. callback();
  524. self.isLoading(false);
  525. });
  526. };
  527. self.value.subscribe(function(value) {
  528. //change transport prep to object wrapper
  529. logGA('put_column');
  530. API.queryTable('putColumn', prepForTransport(self.parent.row), prepForTransport(self.name), "hbase-post-key-" + JSON.stringify(value)).done(function(data) {
  531. self.reload(function(){}, true);
  532. });
  533. self.editing(false);
  534. });
  535. self.editing = ko.observable(false);
  536. self.isLoading = ko.observable(false); //move to baseclass
  537. };
  538. var SortDropDownView = function(options) {
  539. var self = this;
  540. options = ko.utils.extend({
  541. sortFields: {},
  542. target: null
  543. }, options);
  544. BaseModel.apply(self,[options]);
  545. self.target = options.target;
  546. self.sortAsc = ko.observable(true);
  547. self.sortAsc.subscribe(function(){self.sort()});
  548. self.sortField = ko.observable("");
  549. self.sortField.subscribe(function(){self.sort()});
  550. self.sortFields = ko.observableArray(Object.keys(options.sortFields)); // change to ko.computed?
  551. self.sortFunctionHash = ko.observable(options.sortFields);
  552. self.toggleSortMode = function() {
  553. self.sortAsc(!self.sortAsc());
  554. };
  555. self.sort = function() {
  556. if (!self.target || !(self.sortFields().length > 0)) return;
  557. self.target.sort(function(a, b) {
  558. 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
  559. });
  560. };
  561. };
  562. var TableDataRow = function(options) {
  563. var self = this;
  564. options = ko.utils.extend({
  565. name:"",
  566. enabled:true
  567. }, options);
  568. DataRow.apply(self,[options]);
  569. self.name = options['name'];
  570. self.enabled = ko.observable(options['enabled']);
  571. self.toggle = function(viewModel,event){
  572. var action = [i18n('enable'), i18n('disable')][self.enabled() << 0], el = $(event.currentTarget);
  573. confirm(i18n("Confirm") + " " + action, i18n("Are you sure you want to") + " " + action + " " + i18n("this table?"), function()
  574. {
  575. el.showIndicator();
  576. return self[action](el).complete(function() {
  577. el.hideIndicator();
  578. });
  579. });
  580. };
  581. self.enable = function(el) {
  582. return API.queryCluster('enableTable',self.name).complete(function() {
  583. self.enabled(true);
  584. });
  585. };
  586. self.disable = function(callback) {
  587. return API.queryCluster('disableTable',self.name).complete(function() {
  588. self.enabled(false);
  589. if($.isFunction(callback)) callback();
  590. });
  591. };
  592. self.drop = function(el) {
  593. return API.queryCluster('deleteTable',self.name);
  594. };
  595. };
  596. var QuerySetPiece = function(options) {
  597. var self = this;
  598. options = ko.utils.extend({
  599. row_key: "null",
  600. scan_length: 1,
  601. prefix: false,
  602. columns: [],
  603. filter: null,
  604. onValidate: function(){}
  605. }, options);
  606. BaseModel.apply(self,[options]);
  607. self.row_key = ko.observable(options.row_key);
  608. self.scan_length = ko.observable(options.scan_length);
  609. self.columns = ko.observableArray(options.columns);
  610. self.prefix = ko.observable(options.prefix);
  611. self.filter = ko.observable(options.filter);
  612. self.editing = ko.observable(true);
  613. self.validate = function() {
  614. if(self.scan_length() <= 0 || self.row_key() == "")
  615. return app.views.tabledata.querySet.remove(self); //change later
  616. return options.onValidate();
  617. };
  618. self.row_key.subscribe(self.validate.bind());
  619. self.scan_length.subscribe(self.validate.bind());
  620. };
  621. var ColumnFamily = function(options) {
  622. this.name = options.name;
  623. this.enabled = ko.observable(options.enabled);
  624. this.toggle = function() {
  625. this.enabled(!this.enabled());
  626. app.views.tabledata.reload();
  627. };
  628. }
  629. //tagsearch
  630. var tagsearch = function() {
  631. var self = this;
  632. self.tags = ko.observableArray();
  633. self.mode = ko.observable('idle');
  634. self.cur_input = ko.observable('');
  635. self.submitted = ko.observable(false);
  636. 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)"];
  637. self.hints = ko.observableArray([ {
  638. hint: i18n('End Query'),
  639. shortcut: ',',
  640. mode: ['rowkey', 'prefix', 'scan'],
  641. selected: false
  642. }, {
  643. hint: i18n('Prefix Scan'),
  644. shortcut: '*',
  645. mode: ['rowkey', 'columns'],
  646. selected: false
  647. }, {
  648. hint: i18n('Start Scan'),
  649. shortcut: '+',
  650. mode: ['rowkey', 'prefix', 'columns'],
  651. selected: false
  652. }, {
  653. hint: i18n('Start Select Columns'),
  654. shortcut: '[',
  655. mode: ['rowkey', 'prefix'],
  656. selected: false
  657. }, {
  658. hint: i18n('End Column/Family'),
  659. shortcut: ',',
  660. mode: ['columns'],
  661. selected: false
  662. }, {
  663. hint: i18n('End Select Columns'),
  664. shortcut: ']',
  665. mode: ['columns'],
  666. selected: false
  667. }, {
  668. hint: i18n('Column Range'),
  669. shortcut: ' to ',
  670. mode: ['columns'],
  671. selected: false
  672. },
  673. {
  674. hint: i18n('Start FilterString'),
  675. shortcut: '{',
  676. mode: ['rowkey'],
  677. selected: false
  678. },
  679. {
  680. hint: i18n('End FilterString'),
  681. shortcut: '}',
  682. mode: ['filter'],
  683. selected: false
  684. }
  685. ]);
  686. self.activeHints = ko.computed(function() {
  687. var ret = [];
  688. $(self.hints()).each(function(i, hint) {
  689. if (hint.mode.indexOf(self.mode()) > -1)
  690. ret.push(hint);
  691. });
  692. return ret;
  693. });
  694. self.activeHint = ko.observable(-1);
  695. self.modes = {
  696. 'rowkey': {
  697. hint: i18n('Row Key Value'),
  698. type: i18n('String')
  699. },
  700. 'scan': {
  701. hint: i18n('Length of Scan or Row Key'),
  702. type: i18n('Integer')
  703. },
  704. 'columns': {
  705. hint: i18n('Column Family: Column Name'),
  706. type: i18n('String')
  707. },
  708. 'prefix': {
  709. hint: i18n('Rows starting with'),
  710. type: i18n('String')
  711. },
  712. 'filter': {
  713. hint: i18n('Thrift FilterString'),
  714. type: i18n('String')
  715. }
  716. }
  717. self.modeQueue = ['idle'];
  718. self.focused = ko.observable(false);
  719. self.activeSuggestions = ko.observableArray();
  720. self.activeSuggestion = ko.observable(-1);
  721. self.insertTag = function(tag) {
  722. var mode = tag.indexOf('+') != -1 ? 'scan' : 'rowkey';
  723. var tag = {value: tag, tag: mode} //parse mode
  724. self.tags.push(tag);
  725. }
  726. self.render = function(input, renderers) {
  727. var keys = Object.keys(renderers);
  728. for(var i=0; i<keys.length; i++) {
  729. input = input.replace(renderers[keys[i]].select, function(selected) {
  730. var hasMatched = false;
  731. var processed = selected.replace(renderers[keys[i]].tag, function(tagged) {
  732. hasMatched = true;
  733. 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>";
  734. });
  735. if(hasMatched && renderers[keys[i]]['strip'])
  736. processed = processed.replace(renderers[keys[i]].strip, '');
  737. return processed;
  738. });
  739. }
  740. return input;
  741. };
  742. self.updateMode = function(value) {
  743. self.submitted(false);
  744. var selection = value.slice(0, self.selectionEnd());
  745. var endindex = selection.slice(selection.lastIndexOf(',')).indexOf(',');
  746. if(endindex == -1) endindex = selection.length;
  747. var lastbit = value.substring(selection.lastIndexOf(','), endindex).trim();
  748. if(lastbit == "," || lastbit == "") {
  749. self.mode('idle');
  750. return;
  751. }
  752. var tokens = "[]+*{}";
  753. var m = 'rowkey';
  754. for(var i=selection.length - 1; i>=0; i--) {
  755. if(tokens.indexOf(selection[i]) != -1) {
  756. if(selection[i] == '{')
  757. m = 'filter';
  758. else if(selection[i] == '[')
  759. m = 'columns';
  760. else if(selection[i] == ']' || selection[i] == '}')
  761. m = 'rowkey';
  762. else if(selection[i] == '+')
  763. m = 'scan';
  764. else if(selection[i] == '-')
  765. m = 'prefix';
  766. break;
  767. }
  768. }
  769. self.mode(m.trim());
  770. };
  771. self.selectionStart = ko.observable(0);
  772. self.selectionEnd = ko.observable(0);
  773. self.hintText = ko.computed(function() {
  774. var pre, s, e;
  775. try {
  776. var r = window.getSelection().getRangeAt(0);
  777. pre = r.startContainer.nodeValue;
  778. s = r.startOffset;
  779. e = r.endOffset;
  780. return pre.slice(0, s) + "<span class='selection'>" + pre.slice(s, e) + "</span>" + pre.slice(e);
  781. } catch (e) {
  782. var value = self.cur_input();
  783. var selection = value.slice(0, self.selectionEnd());
  784. var index = selection.lastIndexOf(',') + 1;
  785. var endindex = value.slice(index).indexOf(',');
  786. endindex = endindex == -1 ? value.length : endindex;
  787. pre = value.substring(index, index + endindex);
  788. s = self.selectionStart() - index;
  789. e = self.selectionEnd() - index;
  790. if(s == e)
  791. e += 1;
  792. s = s < 0 ? 0 : s;
  793. e = e > pre.length ? pre.length : e;
  794. }
  795. return pre.slice(0, s) + "<span class='selection'>" + pre.slice(s, e) + "</span>" + pre.slice(e);
  796. });
  797. self.onKeyDown = function(target, ev) {
  798. if(ev.keyCode == 13 && self.cur_input().slice(self.cur_input().lastIndexOf(',')).trim() != ",") {
  799. if(self.activeSuggestion() > -1) {
  800. self.replaceFocusNode(self.activeSuggestions()[self.activeSuggestion()]);
  801. self.activeSuggestion(-1);
  802. setTimeout(function() {
  803. var s = self.cur_input(), r;
  804. if(s.lastIndexOf('(') != -1 && s.lastIndexOf(')') != -1) {
  805. r = setCursor($('#search-tags')[0], s.lastIndexOf('(') + 1);
  806. r.setEnd(r.endContainer, r.startOffset + (s.lastIndexOf(')') - s.lastIndexOf('(') - 1));
  807. } else {
  808. r = setCursor($('#search-tags')[0], s.length);
  809. }
  810. var sel = window.getSelection();
  811. sel.removeAllRanges();
  812. sel.addRange(r);
  813. }, 1);
  814. } else {
  815. self.evaluate();
  816. }
  817. return false;
  818. } else if (ev.keyCode == 219) {
  819. setTimeout(function() {
  820. var ep = getEditablePosition($('#search-tags')[0], true);
  821. self.cur_input(self.cur_input().slice(0, ep) + (ev.shiftKey ? '}' : ']') + self.cur_input().slice(ep));
  822. }, 1);
  823. } else if (ev.keyCode == 40) {
  824. if(self.activeSuggestion() < self.activeSuggestions().length - 1)
  825. self.activeSuggestion(self.activeSuggestion() + 1);
  826. return false;
  827. } else if(ev.keyCode == 38) {
  828. if(self.activeSuggestion() > 0)
  829. self.activeSuggestion(self.activeSuggestion() - 1);
  830. return false;
  831. }
  832. setTimeout(self.updateMenu, 1);
  833. return true;
  834. };
  835. self.updateMenu = function() {
  836. self.activeSuggestion(-1);
  837. try{
  838. var pos = getEditablePosition(document.getElementById('search-tags'));
  839. self.selectionStart(pos);
  840. self.selectionEnd(pos);
  841. } catch (err) {}
  842. self.updateMode(self.cur_input());
  843. self.updateSuggestions();
  844. };
  845. self.replaceFocusNode = function(text) {
  846. window.getSelection().getRangeAt(0).startContainer.nodeValue = text;
  847. };
  848. self.updateSuggestions = function() {
  849. var val = window.getSelection().getRangeAt(0).startContainer.nodeValue;
  850. switch(self.mode()) {
  851. case 'filter':
  852. var focus = val.replace(/\{|\}|\s|&[^;]+?;/g,"").split(searchRenderers.rowkey.nested.filter.nested.linker.select).slice(-1)[0];
  853. self.activeSuggestions(self.filters.filter(function(a) {
  854. return a.toLowerCase().replace(" ","").indexOf(focus.toLowerCase()) != -1;
  855. }));
  856. return;
  857. case 'rowkey':
  858. var validate = window.getSelection().getRangeAt(0).startContainer.nodeValue;
  859. function cancel() {
  860. return window.getSelection().getRangeAt(0).startContainer.nodeValue != validate;
  861. }
  862. function callback() {
  863. if(cancel()) return false;
  864. if(validate.trim() != "") {
  865. API.queryTable('getAutocompleteRows', 10, prepForTransport(validate.trim())).done(function(data) {
  866. if(cancel()) return false;
  867. self.activeSuggestions(data);
  868. });
  869. }
  870. }
  871. setTimeout(callback, 200);
  872. return;
  873. default:
  874. self.activeSuggestions([]);
  875. return;
  876. }
  877. }
  878. self.evaluate = function() {
  879. table_search(self.cur_input());
  880. self.submitted(true);
  881. self.mode('idle');
  882. };
  883. $('#search-tags').blur(function(){
  884. self.focused(false);
  885. });
  886. self.doBlur = function() {
  887. if(self.cur_input().trim() == "") {
  888. function doClick() {
  889. if(self.cur_input().trim() != "") return false;
  890. setTimeout(function() {
  891. $('#search-tags').focus();
  892. }, 1);
  893. }
  894. $('#search-tags').html('<small>' + $('#search-tags').data("placeholder") + '</small>').one('click', doClick).find('small').on('mousedown', doClick);
  895. }
  896. }
  897. $('#search-tags').focus(function(){
  898. self.focused(true);
  899. });
  900. };
  901. var CellHistoryPage = function(options) {
  902. var self = this;
  903. self.items = ko.observableArray(options.items);
  904. self.loading = ko.observable(false);
  905. self.reload = function(timestamp, append) {
  906. if(!timestamp)
  907. timestamp = options.timestamp
  908. API.queryTable("getVerTs", prepForTransport(options.row), prepForTransport(options.column), timestamp, 10, 'null').done(function(res) {
  909. self.loading = ko.observable(true);
  910. if(!append)
  911. self.items(res);
  912. else
  913. self.items(self.items() + res);
  914. self.loading = ko.observable(false);
  915. });
  916. };
  917. self.pickHistory = function(data) {
  918. data.history = self;
  919. launchModal('cell_edit_modal',{ content: data, mime: detectMimeType(data.value), readonly: true })
  920. };
  921. };