controls.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  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: /\+[0-9 ]+/g, tag: /.+/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.querySet.removeAll();
  150. self._reloadcfs();
  151. self.evaluateQuery();
  152. }); //fix and decouple
  153. self.lastReloadTime = ko.observable(1);
  154. //self.columnFamilies.subscribe(function(){self.reload();});
  155. self.searchQuery.subscribe(function goToRow(value) //make this as nice as the renderfucnction and split into two, also fire not down on keyup events
  156. {
  157. var inputs = value.match(searchRenderers['rowkey']['select']);
  158. self.querySet.removeAll();
  159. if(inputs) {
  160. for(var i=0; i<inputs.length; i++) {
  161. if(inputs[i].trim() != "" && inputs[i].trim() != ',') {
  162. var p = inputs[i].split('+');
  163. var scan = p.length > 1 ? parseInt(p[1].trim()) : 0;
  164. var extract = inputs[i].match(searchRenderers['rowkey']['nested']['columns']['select']);
  165. var columns = extract != null ? extract[0].match(searchRenderers['rowkey']['nested']['columns']['tag']) : [];
  166. var filter = inputs[i].match(searchRenderers['rowkey']['nested']['filter']['select']);
  167. self.querySet.push(new QuerySetPiece({
  168. 'row_key': p[0].replace(/[\[\{].+[\]\}]|\*/g,'').trim(), //clean up with column regex selectors instead
  169. 'scan_length': scan ? scan + 1 : 1,
  170. 'columns': columns,
  171. 'prefix': inputs[i].match(searchRenderers['rowkey']['nested']['prefix']['select']) != null,
  172. 'filter': filter != null && filter.length > 0 ? escape(filter[0].slice(1, -1)) : null
  173. }));
  174. }
  175. }
  176. }
  177. routie(app.cluster() + '/' + app.views.tabledata.name() +'/query/' + value);
  178. self.evaluateQuery();
  179. });
  180. self._reloadcfs = function() {
  181. return API.queryTable("getColumnDescriptors").done(function(data) {
  182. self.columnFamilies.removeAll();
  183. var keys = Object.keys(data);
  184. for(var i=0;i<keys.length;i++) {
  185. self.columnFamilies.push(new ColumnFamily({name:keys[i], enabled:false}));
  186. }
  187. self.reload();
  188. });
  189. };
  190. self.columnQuery = ko.observable("");
  191. self.columnQuery.subscribe(function(query) {
  192. $(self.items()).each(function() {
  193. this.searchQuery(query);
  194. });
  195. });
  196. self.rows = ko.computed(function() {
  197. var a = [];
  198. var items = this.items();
  199. for(var i=0; i<items.length; i++) {
  200. a.push(items[i].row);
  201. }
  202. return a;
  203. }, self);
  204. self.querySet = ko.observableArray();
  205. self.validateQuery = function() {
  206. if(self.querySet().length == 0) {
  207. self.querySet.push(new QuerySetPiece({
  208. 'row_key': 'null',
  209. 'scan_length': 10,
  210. 'prefix': 'false'
  211. }));
  212. } else {
  213. $(self.querySet()).each(function() {
  214. this.validate();
  215. this.editing(false);
  216. });
  217. }
  218. };
  219. self.addQuery = function() {
  220. self.validateQuery();
  221. self.querySet.push(new QuerySetPiece({onValidate: function() {
  222. //self.reload();
  223. }}))
  224. };
  225. self.evaluateQuery = function(callback) {
  226. self.validateQuery();
  227. self.reload(callback);
  228. };
  229. var _reload = self.reload;
  230. self.reload = function(callback) {
  231. logGA('view_table');
  232. var queryStart = new Date();
  233. _reload(function() {
  234. self.lastReloadTime((new Date() - queryStart)/1000);
  235. if(callback!=null)
  236. callback();
  237. self.isLoading(false);
  238. });
  239. };
  240. self.truncateLimit = ko.observable(1500);
  241. };
  242. var SmartViewDataRow = function(options) {
  243. var self = this;
  244. options = ko.utils.extend({
  245. sortFields: {
  246. 'Column Family': function(a, b) {
  247. return a.name.localeCompare(b.name);
  248. },
  249. 'Column Name': function(a, b) {
  250. return a.name.split(':')[1].localeCompare(b.name.split(':')[1]);
  251. },
  252. 'Cell Value': function(a, b) {
  253. a = a.value.length;
  254. b = b.value.length;
  255. if(a > b)
  256. return 1;
  257. if(a < b)
  258. return -1;
  259. return 0;
  260. },
  261. 'Timestamp': function(a, b) {
  262. a = parseInt(a.timestamp);
  263. b = parseInt(b.timestamp);
  264. if(a > b)
  265. return 1;
  266. if(a < b)
  267. return -1;
  268. return 0;
  269. },
  270. 'MIME Type': function() {
  271. },
  272. 'Column Name Length': function(a, b) {
  273. a = a.name.split(':')[1].length;
  274. b = b.name.split(':')[1].length;
  275. if(a > b)
  276. return 1;
  277. if(a < b)
  278. return -1;
  279. return 0;
  280. }
  281. }
  282. }, options);
  283. DataRow.apply(self,[options]);
  284. ListViewModel.apply(self,[options]);
  285. self.displayedItems = ko.observableArray();
  286. self.displayRangeStart = 0;
  287. self.displayRangeLength = 20;
  288. self.items.subscribe(function() {
  289. self.displayedItems([]);
  290. self.updateDisplayedItems();
  291. });
  292. self.searchQuery.subscribe(function(searchValue) {
  293. self.scrollLoadSource = ko.computed(function(){
  294. return self.items().filter(function(column) {
  295. return column.name.toLowerCase().indexOf(searchValue.toLowerCase()) != -1;
  296. });
  297. });
  298. self.displayRangeLength = 20;
  299. self.updateDisplayedItems();
  300. });
  301. self.scrollLoadSource = self.items;
  302. self.updateDisplayedItems = function() {
  303. var x = self.displayRangeStart;
  304. self.displayedItems(self.scrollLoadSource().slice(x, x + self.displayRangeLength));
  305. };
  306. self.resetScrollLoad = function() {
  307. self.scrollLoadSource = self.items;
  308. self.updateDisplayedItems();
  309. };
  310. self.toggleSelectedCollapse = function() {
  311. if(self.displayedItems().length == self.displayRangeStart + self.displayRangeLength) {
  312. self.displayedItems(self.displayedItems().filter(function(item) {
  313. return item.isSelected();
  314. }));
  315. self.scrollLoadSource = self.displayedItems;
  316. }
  317. else {
  318. self.resetScrollLoad();
  319. }
  320. };
  321. self.onScroll = function(target, ev) {
  322. if($(ev.target).scrollLeft() == ev.target.scrollWidth - ev.target.clientWidth && self.displayedItems().length < self.scrollLoadSource().length) {
  323. self.displayRangeLength += 15;
  324. self.updateDisplayedItems();
  325. }
  326. };
  327. self.drop = function(cont) {
  328. function doDrop() {
  329. self.isLoading(true);
  330. return API.queryTable('deleteAllRow', self.row, "{}").complete(function() {
  331. app.views.tabledata.items.remove(self); //decouple later
  332. self.isLoading(false);
  333. });
  334. }
  335. (cont === true) ? doDrop() : confirm("Confirm Delete", 'Delete row ' + self.row + '? (This cannot be undone)', doDrop);
  336. };
  337. self.setItems = function(cols) {
  338. var colKeys = Object.keys(cols);
  339. var items = [];
  340. for(var q=0; q<colKeys.length; q++) {
  341. items.push(new ColumnRow({name: colKeys[q],
  342. timestamp: cols[colKeys[q]].timestamp,
  343. value: cols[colKeys[q]].value,
  344. parent: self}));
  345. }
  346. self.items(items);
  347. return self.items();
  348. };
  349. self.selectAllVisible = function(){
  350. for(t=0; t<self.displayedItems().length; t++)
  351. self.displayedItems()[t].isSelected(true);
  352. return self;
  353. };
  354. self.toggleSelectAllVisible = function() {
  355. if(self.selected().length != self.displayedItems().length)
  356. return self.selectAllVisible();
  357. return self.deselectAll();
  358. };
  359. self.push = function(item) {
  360. var column = new ColumnRow(item);
  361. self.items.push(column);
  362. };
  363. var _reload = self.reload;
  364. self.reload = function(callback) {
  365. logGA('get_row');
  366. _reload(function() {
  367. if(callback!=null)
  368. callback();
  369. self.isLoading(false);
  370. });
  371. };
  372. };
  373. var ColumnRow = function(options) {
  374. var self = this;
  375. ko.utils.extend(self,options);
  376. DataRow.apply(self,[options]);
  377. self.value = ko.observable(self.value);
  378. self.history = new CellHistoryPage({row: self.parent.row, column: self.name, timestamp: self.timestamp, items: []});
  379. self.drop = function(cont) {
  380. function doDrop() {
  381. logGA('filter_columns');
  382. self.parent.isLoading(true);
  383. return API.queryTable('deleteColumn', self.parent.row, self.name).done(function(data) {
  384. self.parent.items.remove(self);
  385. if(self.parent.items().length > 0)
  386. self.parent.reload(); //change later
  387. self.parent.isLoading(false);
  388. });
  389. }
  390. (cont === true) ? doDrop() : confirm("Confirm Delete", "Are you sure you want to drop this column?", doDrop);
  391. };
  392. self.reload = function(callback, skipPut) {
  393. self.isLoading(true);
  394. API.queryTable('get', self.parent.row, self.name, 'null').done(function(data) {
  395. if(data.length > 0 && !skipPut)
  396. self.value(data[0].value);
  397. if(typeof callback !== "undefined" && callback != null)
  398. callback();
  399. self.isLoading(false);
  400. });
  401. };
  402. self.value.subscribe(function(value) {
  403. //change transport prep to object wrapper
  404. logGA('put_column');
  405. API.queryTable('putColumn', self.parent.row, self.name, "hbase-post-key-" + JSON.stringify(value)).done(function(data) {
  406. self.reload(function(){}, true);
  407. });
  408. self.editing(false);
  409. });
  410. self.editing = ko.observable(false);
  411. self.isLoading = ko.observable(false); //move to baseclass
  412. };
  413. var SortDropDownView = function(options) {
  414. var self = this;
  415. options = ko.utils.extend({
  416. sortFields: {},
  417. target: null
  418. }, options);
  419. BaseModel.apply(self,[options]);
  420. self.target = options.target;
  421. self.sortAsc = ko.observable(true);
  422. self.sortAsc.subscribe(function(){self.sort()});
  423. self.sortField = ko.observable("");
  424. self.sortField.subscribe(function(){self.sort()});
  425. self.sortFields = ko.observableArray(Object.keys(options.sortFields)); // change to ko.computed?
  426. self.sortFunctionHash = ko.observable(options.sortFields);
  427. self.toggleSortMode = function() {
  428. self.sortAsc(!self.sortAsc());
  429. };
  430. self.sort = function() {
  431. if (!self.target || !(self.sortFields().length > 0)) return;
  432. self.target.sort(function(a, b) {
  433. 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
  434. });
  435. };
  436. };
  437. var TableDataRow = function(options) {
  438. var self = this;
  439. options = ko.utils.extend({
  440. name:"",
  441. enabled:true
  442. }, options);
  443. DataRow.apply(self,[options]);
  444. self.name = options['name'];
  445. self.enabled = ko.observable(options['enabled']);
  446. self.toggle = function(viewModel,event){
  447. var action = ['enable','disable'][self.enabled() << 0], el = $(event.currentTarget);
  448. confirm("Confirm "+action, "Are you sure you want to " + action + " this table?", function() //gotta i18n this!
  449. {
  450. el.showIndicator();
  451. return self[action](el).complete(function() {
  452. el.hideIndicator();
  453. });
  454. });
  455. };
  456. self.enable = function(el) {
  457. return API.queryCluster('enableTable',self.name).complete(function() {
  458. self.enabled(true);
  459. });
  460. };
  461. self.disable = function(el) {
  462. return API.queryCluster('disableTable',self.name).complete(function() {
  463. self.enabled(false);
  464. });
  465. };
  466. self.drop = function(el) {
  467. return API.queryCluster('deleteTable',self.name);
  468. };
  469. };
  470. var QuerySetPiece = function(options) {
  471. var self = this;
  472. options = ko.utils.extend({
  473. row_key: "null",
  474. scan_length: 1,
  475. prefix: false,
  476. columns: [],
  477. filter: null,
  478. onValidate: function(){}
  479. }, options);
  480. BaseModel.apply(self,[options]);
  481. self.row_key = ko.observable(options.row_key);
  482. self.scan_length = ko.observable(options.scan_length);
  483. self.columns = ko.observableArray(options.columns);
  484. self.prefix = ko.observable(options.prefix);
  485. self.filter = ko.observable(options.filter);
  486. self.editing = ko.observable(true);
  487. self.validate = function() {
  488. if(self.scan_length() <= 0 || self.row_key() == "")
  489. return app.views.tabledata.querySet.remove(self); //change later
  490. return options.onValidate();
  491. };
  492. self.row_key.subscribe(self.validate.bind());
  493. self.scan_length.subscribe(self.validate.bind());
  494. };
  495. var ColumnFamily = function(options) {
  496. this.name = options.name;
  497. this.enabled = ko.observable(options.enabled);
  498. this.toggle = function() {
  499. this.enabled(!this.enabled());
  500. app.views.tabledata.reload();
  501. };
  502. }
  503. //tagsearch
  504. var tagsearch = function() {
  505. var self = this;
  506. self.tags = ko.observableArray();
  507. self.mode = ko.observable('idle');
  508. self.cur_input = ko.observable('');
  509. self.hints = ko.observableArray([ {
  510. hint: 'End Query',
  511. shortcut: ',',
  512. mode: ['rowkey', 'prefix', 'scan'],
  513. selected: false
  514. }, {
  515. hint: 'Mark Row Prefix',
  516. shortcut: '*',
  517. mode: ['rowkey'],
  518. selected: false
  519. }, {
  520. hint: 'Start Scan',
  521. shortcut: '+',
  522. mode: ['rowkey', 'prefix'],
  523. selected: false
  524. }, {
  525. hint: 'Start Select Columns',
  526. shortcut: '[',
  527. mode: ['rowkey', 'prefix'],
  528. selected: false
  529. }, {
  530. hint: 'End Column/Family',
  531. shortcut: ',',
  532. mode: ['columns'],
  533. selected: false
  534. }, {
  535. hint: 'End Select Columns',
  536. shortcut: ']',
  537. mode: ['columns'],
  538. selected: false
  539. },
  540. {
  541. hint: 'Start FilterString',
  542. shortcut: '{',
  543. mode: ['rowkey'],
  544. selected: false
  545. },
  546. {
  547. hint: 'End FilterString',
  548. shortcut: '}',
  549. mode: ['filter'],
  550. selected: false
  551. }
  552. ]);
  553. self.activeHints = ko.computed(function() {
  554. var ret = [];
  555. $(self.hints()).each(function(i, hint) {
  556. if (hint.mode.indexOf(self.mode()) > -1)
  557. ret.push(hint);
  558. });
  559. return ret;
  560. });
  561. self.activeHint = ko.observable(-1);
  562. self.modes = {
  563. 'rowkey': {
  564. hint: 'Row Key Value',
  565. type: 'String'
  566. },
  567. 'scan': {
  568. hint: 'Length of Scan or Row Key',
  569. type: 'Integer'
  570. },
  571. 'columns': {
  572. hint: 'Column Family: Column Name',
  573. type: 'String'
  574. },
  575. 'prefix': {
  576. hint: 'Rows starting with',
  577. type: 'String'
  578. },
  579. 'filter': {
  580. hint: 'Thrift FilterString',
  581. type: 'String'
  582. }
  583. }
  584. self.modeQueue = ['idle'];
  585. self.focused = ko.observable(false);
  586. self.insertTag = function(tag) {
  587. var mode = tag.indexOf('+') != -1 ? 'scan' : 'rowkey';
  588. var tag = {value: tag, tag: mode} //parse mode
  589. self.tags.push(tag);
  590. }
  591. self.render = function(input, renderers) {
  592. var keys = Object.keys(renderers);
  593. for(var i=0; i<keys.length; i++) {
  594. input = input.replace(renderers[keys[i]].select, function(selected) {
  595. var hasMatched = false;
  596. var processed = selected.replace(renderers[keys[i]].tag, function(tagged) {
  597. hasMatched = true;
  598. 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>";
  599. });
  600. if(hasMatched && renderers[keys[i]]['strip'])
  601. processed = processed.replace(renderers[keys[i]].strip, '');
  602. return processed;
  603. });
  604. }
  605. return input;
  606. };
  607. self.updateMode = function(value) {
  608. var selection = value.slice(0, self.selectionEnd());
  609. var endindex = selection.slice(selection.lastIndexOf(',')).indexOf(',');
  610. if(endindex == -1) endindex = selection.length;
  611. var lastbit = value.substring(selection.lastIndexOf(','), endindex).trim();
  612. if(lastbit == "," || lastbit == "") {
  613. self.mode('idle');
  614. return;
  615. }
  616. var tokens = "[]+,*{}";
  617. var m = 'rowkey';
  618. for(var i=selection.length - 1; i>=0; i--) {
  619. if(tokens.indexOf(selection[i]) != -1) {
  620. if(selection[i] == '{')
  621. m = 'filter';
  622. else if(selection[i] == '[')
  623. m = 'columns';
  624. else if(selection[i] == ']' || selection[i] == '}')
  625. m = 'rowkey';
  626. else if(selection[i] == '+')
  627. m = 'scan';
  628. else if(selection[i] == '-')
  629. m = 'prefix';
  630. break;
  631. }
  632. }
  633. self.mode(m.trim());
  634. };
  635. self.selectionStart = ko.observable(0);
  636. self.selectionEnd = ko.observable(0);
  637. self.hintText = ko.computed(function() {
  638. var value = self.cur_input();
  639. var selection = value.slice(0, self.selectionEnd());
  640. var index = selection.lastIndexOf(',') + 1;
  641. var endindex = value.slice(index).indexOf(',');
  642. endindex = endindex == -1 ? value.length : endindex;
  643. var pre = value.substring(index, index + endindex);
  644. var s = self.selectionStart() - index, e = self.selectionEnd() - index;
  645. if(s == e)
  646. e += 1;
  647. s = s < 0 ? 0 : s;
  648. e = e > pre.length ? pre.length : e;
  649. return pre.slice(0, s) + "<span class='selection'>" + pre.slice(s, e) + "</span>" + pre.slice(e);
  650. });
  651. self.onKeyDown = function(target, ev) {
  652. if(ev.keyCode == 13 && self.cur_input().slice(self.cur_input().lastIndexOf(',')).trim() != ",") {
  653. self.evaluate();
  654. return false;
  655. }
  656. setTimeout(self.updateMenu, 1);
  657. return true;
  658. };
  659. self.updateMenu = function() {
  660. try{
  661. var pos = getEditablePosition(document.getElementById('search-tags'));
  662. self.selectionStart(pos);
  663. self.selectionEnd(pos);
  664. } catch (err) {}
  665. self.updateMode(self.cur_input());
  666. };
  667. self.evaluate = function() {
  668. app.views.tabledata.searchQuery(self.cur_input());
  669. };
  670. $('#search-tags').blur(function(){
  671. self.focused(false);
  672. });
  673. self.doBlur = function() {
  674. if(self.cur_input().trim() == "") {
  675. function doClick() {
  676. $('#search-tags').html('');
  677. setTimeout(function() {
  678. $('#search-tags').focus();
  679. }, 1);
  680. }
  681. $('#search-tags').html('<small>' + $('#search-tags').data("placeholder") + '</small>').one('click', doClick).find('small').on('mousedown', doClick);
  682. }
  683. }
  684. $('#search-tags').focus(function(){
  685. self.focused(true);
  686. });
  687. };
  688. var CellHistoryPage = function(options) {
  689. var self = this;
  690. self.items = ko.observableArray(options.items);
  691. self.loading = ko.observable(false);
  692. self.reload = function(timestamp, append) {
  693. if(!timestamp)
  694. timestamp = options.timestamp
  695. API.queryTable("getVerTs", options.row, options.column, timestamp, 10, 'null').done(function(res) {
  696. self.loading = ko.observable(true);
  697. if(!append)
  698. self.items(res);
  699. else
  700. self.items(self.items() + res);
  701. self.loading = ko.observable(false);
  702. });
  703. };
  704. };