1
0

explorer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. (function() {
  19. "use strict";
  20. // The chunk size of tailing the files, i.e., how many bytes will be shown
  21. // in the preview.
  22. var TAIL_CHUNK_SIZE = 32768;
  23. //This stores the current directory which is being browsed
  24. var current_directory = "";
  25. function show_err_msg(msg) {
  26. $('#alert-panel-body').html(msg);
  27. $('#alert-panel').show();
  28. }
  29. $(window).bind('hashchange', function () {
  30. $('#alert-panel').hide();
  31. var dir = decodeURIComponent(window.location.hash.slice(1));
  32. if(dir == "") {
  33. dir = "/";
  34. }
  35. if(current_directory != dir) {
  36. browse_directory(dir);
  37. }
  38. });
  39. function network_error_handler(url) {
  40. return function (jqxhr, text, err) {
  41. switch(jqxhr.status) {
  42. case 401:
  43. var msg = '<p>Authentication failed when trying to open ' + url + ': Unauthorized.</p>';
  44. break;
  45. case 403:
  46. if(jqxhr.responseJSON !== undefined && jqxhr.responseJSON.RemoteException !== undefined) {
  47. var msg = '<p>' + jqxhr.responseJSON.RemoteException.message + "</p>";
  48. break;
  49. }
  50. var msg = '<p>Permission denied when trying to open ' + url + ': ' + err + '</p>';
  51. break;
  52. case 404:
  53. var msg = '<p>Path does not exist on HDFS or WebHDFS is disabled. Please check your path or enable WebHDFS</p>';
  54. break;
  55. default:
  56. var msg = '<p>Failed to retrieve data from ' + url + ': ' + err + '</p>';
  57. }
  58. show_err_msg(msg);
  59. };
  60. }
  61. function append_path(prefix, s) {
  62. var l = prefix.length;
  63. var p = l > 0 && prefix[l - 1] == '/' ? prefix.substring(0, l - 1) : prefix;
  64. return p + '/' + s;
  65. }
  66. function get_response(data, type) {
  67. return data[type] !== undefined ? data[type] : null;
  68. }
  69. function get_response_err_msg(data) {
  70. return data.RemoteException !== undefined ? data.RemoteException.message : "";
  71. }
  72. function delete_path(inode_name, absolute_file_path) {
  73. $('#delete-modal-title').text("Delete - " + inode_name);
  74. $('#delete-prompt').text("Are you sure you want to delete " + inode_name
  75. + " ?");
  76. $('#delete-button').click(function() {
  77. // DELETE /webhdfs/v1/<path>?op=DELETE&recursive=<true|false>
  78. var url = '/webhdfs/v1' + encode_path(absolute_file_path) +
  79. '?op=DELETE' + '&recursive=true';
  80. $.ajax(url,
  81. { type: 'DELETE'
  82. }).done(function(data) {
  83. browse_directory(current_directory);
  84. }).fail(network_error_handler(url)
  85. ).always(function() {
  86. $('#delete-modal').modal('hide');
  87. $('#delete-button').button('reset');
  88. });
  89. })
  90. $('#delete-modal').modal();
  91. }
  92. /* This method loads the checkboxes on the permission info modal. It accepts
  93. * the octal permissions, eg. '644' or '755' and infers the checkboxes that
  94. * should be true and false
  95. */
  96. function view_perm_details(e, filename, abs_path, perms) {
  97. $('.explorer-perm-links').popover('destroy');
  98. e.popover({html: true, content: $('#explorer-popover-perm-info').html(), trigger: 'focus'})
  99. .on('shown.bs.popover', function(e) {
  100. var popover = $(this), parent = popover.parent();
  101. //Convert octal to binary permissions
  102. var bin_perms = parseInt(perms, 8).toString(2);
  103. bin_perms = bin_perms.length == 9 ? "0" + bin_perms : bin_perms;
  104. parent.find('#explorer-perm-cancel').on('click', function() { popover.popover('destroy'); });
  105. parent.find('#explorer-set-perm-button').off().click(function() { set_permissions(abs_path); });
  106. parent.find('input[type=checkbox]').each(function(idx, element) {
  107. var e = $(element);
  108. e.prop('checked', bin_perms.charAt(9 - e.attr('data-bit')) == '1');
  109. });
  110. })
  111. .popover('show');
  112. }
  113. // Use WebHDFS to set permissions on an absolute path
  114. function set_permissions(abs_path) {
  115. var p = 0;
  116. $.each($('.popover .explorer-popover-perm-body input:checked'), function(idx, e) {
  117. p |= 1 << (+$(e).attr('data-bit'));
  118. });
  119. var permission_mask = p.toString(8);
  120. // PUT /webhdfs/v1/<path>?op=SETPERMISSION&permission=<permission>
  121. var url = '/webhdfs/v1' + encode_path(abs_path) +
  122. '?op=SETPERMISSION' + '&permission=' + permission_mask;
  123. $.ajax(url, { type: 'PUT'
  124. }).done(function(data) {
  125. browse_directory(current_directory);
  126. }).fail(network_error_handler(url))
  127. .always(function() {
  128. $('.explorer-perm-links').popover('destroy');
  129. });
  130. }
  131. function encode_path(abs_path) {
  132. abs_path = encodeURIComponent(abs_path);
  133. var re = /%2F/g;
  134. return abs_path.replace(re, '/');
  135. }
  136. function view_file_details(path, abs_path) {
  137. function show_block_info(blocks) {
  138. var menus = $('#file-info-blockinfo-list');
  139. menus.empty();
  140. menus.data("blocks", blocks);
  141. menus.change(function() {
  142. var d = $(this).data('blocks')[$(this).val()];
  143. if (d === undefined) {
  144. return;
  145. }
  146. dust.render('block-info', d, function(err, out) {
  147. $('#file-info-blockinfo-body').html(out);
  148. });
  149. });
  150. for (var i = 0; i < blocks.length; ++i) {
  151. var item = $('<option value="' + i + '">Block ' + i + '</option>');
  152. menus.append(item);
  153. }
  154. menus.change();
  155. }
  156. abs_path = encode_path(abs_path);
  157. var url = '/webhdfs/v1' + abs_path + '?op=GET_BLOCK_LOCATIONS';
  158. $.ajax({url: url, dataType: 'text'}).done(function(data_text) {
  159. var data = JSONParseBigNum(data_text);
  160. var d = get_response(data, "LocatedBlocks");
  161. if (d === null) {
  162. show_err_msg(get_response_err_msg(data));
  163. return;
  164. }
  165. $('#file-info-tail').hide();
  166. $('#file-info-title').text("File information - " + path);
  167. var download_url = '/webhdfs/v1' + abs_path + '?op=OPEN';
  168. $('#file-info-download').attr('href', download_url);
  169. var processPreview = function(url) {
  170. url += "&noredirect=true";
  171. if(request && request.readyState != 4){
  172. request.abort();
  173. }
  174. request = $.ajax({
  175. cache: false,
  176. type: 'GET',
  177. url: url,
  178. async: false,
  179. processData: false,
  180. crossDomain: true
  181. }).done(function(data, textStatus, jqXHR) {
  182. url = data.Location;
  183. $.ajax({
  184. cache: false,
  185. type: 'GET',
  186. url: url,
  187. async: false,
  188. processData: false,
  189. crossDomain: true
  190. }).always(function(data, textStatus, jqXHR) {
  191. $('#file-info-preview-body').val(jqXHR.responseText);
  192. $('#file-info-tail').show();
  193. }).fail(function(jqXHR, textStatus, errorThrown) {
  194. show_err_msg("Couldn't preview the file. " + errorThrown);
  195. });
  196. }).fail(function(jqXHR, textStatus, errorThrown) {
  197. show_err_msg("Couldn't find datanode to read file from. " + errorThrown);
  198. });
  199. }
  200. var request = null;
  201. $('#file-info-preview-tail')
  202. .off('click')
  203. .on('click', function() {
  204. var offset = d.fileLength - TAIL_CHUNK_SIZE;
  205. var url = offset > 0 ? download_url + '&offset=' + offset : download_url;
  206. processPreview(url);
  207. });
  208. $('#file-info-preview-head')
  209. .off('click')
  210. .on('click', function() {
  211. var url = d.fileLength > TAIL_CHUNK_SIZE ? download_url + '&length=' + TAIL_CHUNK_SIZE : download_url;
  212. processPreview(url);
  213. });
  214. if (d.fileLength > 0) {
  215. show_block_info(d.locatedBlocks);
  216. $('#file-info-blockinfo-panel').show();
  217. } else {
  218. $('#file-info-blockinfo-panel').hide();
  219. }
  220. $('#file-info').modal();
  221. }).fail(network_error_handler(url));
  222. }
  223. /**Use X-editable to make fields editable with a nice UI.
  224. * elementType is the class of element(s) you want to make editable
  225. * op is the WebHDFS operation that will be triggered
  226. * parameter is (currently the 1) parameter which will be passed along with
  227. * the value entered by the user
  228. */
  229. function makeEditable(elementType, op, parameter) {
  230. $(elementType).each(function(index, value) {
  231. $(this).editable({
  232. url: function(params) {
  233. var inode_name = $(this).closest('tr').attr('inode-path');
  234. var absolute_file_path = append_path(current_directory, inode_name);
  235. var url = '/webhdfs/v1' + encode_path(absolute_file_path) + '?op=' +
  236. op + '&' + parameter + '=' + encodeURIComponent(params.value);
  237. return $.ajax(url, { type: 'PUT', })
  238. .fail(network_error_handler(url))
  239. .done(function() {
  240. browse_directory(current_directory);
  241. });
  242. },
  243. error: function(response, newValue) {return "";}
  244. });
  245. });
  246. }
  247. function func_size_render(data, type, row, meta) {
  248. if(type == 'display') {
  249. return dust.filters.fmt_bytes(data);
  250. }
  251. else return data;
  252. }
  253. // Change the format of date-time depending on how old the
  254. // the timestamp is. If older than 6 months, no need to be
  255. // show exact time.
  256. function func_time_render(data, type, row, meta) {
  257. if(type == 'display') {
  258. var cutoff = moment().subtract(6, 'months').unix() * 1000;
  259. if(data < cutoff) {
  260. return moment(Number(data)).format('MMM DD YYYY');
  261. } else {
  262. return moment(Number(data)).format('MMM DD HH:mm');
  263. }
  264. }
  265. return data;
  266. }
  267. function browse_directory(dir) {
  268. var HELPERS = {
  269. 'helper_date_tostring' : function (chunk, ctx, bodies, params) {
  270. var value = dust.helpers.tap(params.value, chunk, ctx);
  271. return chunk.write('' + moment(Number(value)).format('ddd MMM DD HH:mm:ss ZZ YYYY'));
  272. }
  273. };
  274. var url = '/webhdfs/v1' + encode_path(dir) + '?op=LISTSTATUS';
  275. $.get(url, function(data) {
  276. var d = get_response(data, "FileStatuses");
  277. if (d === null) {
  278. show_err_msg(get_response_err_msg(data));
  279. return;
  280. }
  281. current_directory = dir;
  282. $('#directory').val(dir);
  283. window.location.hash = dir;
  284. var base = dust.makeBase(HELPERS);
  285. dust.render('explorer', base.push(d), function(err, out) {
  286. $('#panel').html(out);
  287. $('.explorer-browse-links').click(function() {
  288. var type = $(this).attr('inode-type');
  289. var path = $(this).closest('tr').attr('inode-path');
  290. var abs_path = append_path(current_directory, path);
  291. if (type == 'DIRECTORY') {
  292. browse_directory(abs_path);
  293. } else {
  294. view_file_details(path, abs_path);
  295. }
  296. });
  297. //Set the handler for changing permissions
  298. $('.explorer-perm-links').click(function() {
  299. var filename = $(this).closest('tr').attr('inode-path');
  300. var abs_path = append_path(current_directory, filename);
  301. var perms = $(this).closest('tr').attr('data-permission');
  302. view_perm_details($(this), filename, abs_path, perms);
  303. });
  304. makeEditable('.explorer-owner-links', 'SETOWNER', 'owner');
  305. makeEditable('.explorer-group-links', 'SETOWNER', 'group');
  306. makeEditable('.explorer-replication-links', 'SETREPLICATION', 'replication');
  307. $('.explorer-entry .glyphicon-trash').click(function() {
  308. var inode_name = $(this).closest('tr').attr('inode-path');
  309. var absolute_file_path = append_path(current_directory, inode_name);
  310. delete_path(inode_name, absolute_file_path);
  311. });
  312. $('#file-selector-all').click(function() {
  313. $('.file_selector').prop('checked', $('#file-selector-all')[0].checked );
  314. });
  315. //This needs to be last because it repaints the table
  316. $('#table-explorer').dataTable( {
  317. 'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
  318. 'columns': [
  319. { 'orderable' : false }, //select
  320. {'searchable': false }, //Permissions
  321. null, //Owner
  322. null, //Group
  323. { 'searchable': false, 'render': func_size_render}, //Size
  324. { 'searchable': false, 'render': func_time_render}, //Last Modified
  325. { 'searchable': false }, //Replication
  326. null, //Block Size
  327. null, //Name
  328. { 'orderable' : false } //Trash
  329. ],
  330. "deferRender": true
  331. });
  332. });
  333. }).fail(network_error_handler(url));
  334. }
  335. $('#parentDir').click(function () {
  336. var current = current_directory;
  337. var lastIndex = current.lastIndexOf('/');
  338. var parent = current.substr(0, lastIndex);
  339. browse_directory(parent);
  340. })
  341. function init() {
  342. dust.loadSource(dust.compile($('#tmpl-explorer').html(), 'explorer'));
  343. dust.loadSource(dust.compile($('#tmpl-block-info').html(), 'block-info'));
  344. var b = function() { browse_directory($('#directory').val()); };
  345. $('#btn-nav-directory').click(b);
  346. //Also navigate to the directory when a user presses enter.
  347. $('#directory').on('keyup', function (e) {
  348. if (e.which == 13) {
  349. browse_directory($('#directory').val());
  350. }
  351. });
  352. var dir = window.location.hash.slice(1);
  353. if(dir == "") {
  354. window.location.hash = "/";
  355. } else {
  356. browse_directory(dir);
  357. }
  358. }
  359. $('#btn-create-directory').on('show.bs.modal', function(event) {
  360. $('#new_directory_pwd').text(current_directory);
  361. });
  362. $('#btn-create-directory-send').click(function () {
  363. $(this).prop('disabled', true);
  364. $(this).button('complete');
  365. var url = '/webhdfs/v1' + encode_path(append_path(current_directory,
  366. $('#new_directory').val())) + '?op=MKDIRS';
  367. $.ajax(url, { type: 'PUT' }
  368. ).done(function(data) {
  369. browse_directory(current_directory);
  370. }).fail(network_error_handler(url)
  371. ).always(function() {
  372. $('#btn-create-directory').modal('hide');
  373. $('#btn-create-directory-send').button('reset');
  374. });
  375. })
  376. $('#btn-upload-files').click(function() {
  377. $('#modal-upload-file-button').prop('disabled', true).button('reset');
  378. $('#modal-upload-file-input').val(null);
  379. });
  380. $('#btn-create-dir').click(function() {
  381. $('#btn-create-directory-send').prop('disabled', true).button('reset');
  382. $('#new_directory').val(null);
  383. });
  384. $('#modal-upload-file-input').change(function() {
  385. if($('#modal-upload-file-input').prop('files').length >0) {
  386. $('#modal-upload-file-button').prop('disabled', false);
  387. }
  388. else {
  389. $('#modal-upload-file-button').prop('disabled', true);
  390. }
  391. });
  392. $('#new_directory').on('keyup keypress blur change',function() {
  393. if($('#new_directory').val() == '' || $('#new_directory').val() == null) {
  394. $('#btn-create-directory-send').prop('disabled', true);
  395. }
  396. else {
  397. $('#btn-create-directory-send').prop('disabled', false);
  398. }
  399. });
  400. $('#modal-upload-file-button').click(function() {
  401. $(this).prop('disabled', true);
  402. $(this).button('complete');
  403. var files = []
  404. var numCompleted = 0
  405. for(var i = 0; i < $('#modal-upload-file-input').prop('files').length; i++) {
  406. (function() {
  407. var file = $('#modal-upload-file-input').prop('files')[i];
  408. var url = '/webhdfs/v1' + encode_path(append_path(current_directory, file.name));
  409. url += '?op=CREATE&noredirect=true';
  410. files.push( { file: file } )
  411. files[i].request = $.ajax({
  412. type: 'PUT',
  413. url: url,
  414. processData: false,
  415. crossDomain: true
  416. });
  417. })()
  418. }
  419. for(var f in files) {
  420. (function() {
  421. var file = files[f];
  422. file.request.done(function(data) {
  423. var url = data['Location'];
  424. $.ajax({
  425. type: 'PUT',
  426. url: url,
  427. data: file.file,
  428. processData: false,
  429. crossDomain: true
  430. }).always(function(data) {
  431. numCompleted++;
  432. if(numCompleted == files.length) {
  433. reset_upload_button();
  434. browse_directory(current_directory);
  435. }
  436. }).fail(function(jqXHR, textStatus, errorThrown) {
  437. numCompleted++;
  438. reset_upload_button();
  439. show_err_msg("Couldn't upload the file " + file.file.name + ". "+ errorThrown);
  440. });
  441. }).fail(function(jqXHR, textStatus, errorThrown) {
  442. numCompleted++;
  443. reset_upload_button();
  444. show_err_msg("Couldn't find datanode to write file. " + errorThrown);
  445. });
  446. })();
  447. }
  448. });
  449. //Reset the upload button
  450. function reset_upload_button() {
  451. $('#modal-upload-file').modal('hide');
  452. $('#modal-upload-file-button').button('reset');
  453. }
  454. //Store the list of files which have been checked into session storage
  455. function store_selected_files(current_directory) {
  456. sessionStorage.setItem("source_directory", current_directory);
  457. var selected_files = $("input:checked.file_selector");
  458. var selected_file_names = new Array();
  459. selected_files.each(function(index) {
  460. selected_file_names[index] = $(this).closest('tr').attr('inode-path');
  461. })
  462. sessionStorage.setItem("selected_file_names", JSON.stringify(selected_file_names));
  463. alert("Cut " + selected_file_names.length + " files/directories");
  464. }
  465. //Retrieve the list of files from session storage and rename them to the current
  466. //directory
  467. function paste_selected_files() {
  468. var files = JSON.parse(sessionStorage.getItem("selected_file_names"));
  469. var source_directory = sessionStorage.getItem("source_directory");
  470. $.each(files, function(index, value) {
  471. var url = "/webhdfs/v1"
  472. + encode_path(append_path(source_directory, value))
  473. + '?op=RENAME&destination='
  474. + encode_path(append_path(current_directory, value));
  475. $.ajax({
  476. type: 'PUT',
  477. url: url
  478. }).done(function(data) {
  479. if(index == files.length - 1) {
  480. browse_directory(current_directory);
  481. }
  482. }).fail(function(jqXHR, textStatus, errorThrown) {
  483. show_err_msg("Couldn't move file " + value + ". " + errorThrown);
  484. });
  485. })
  486. }
  487. $('#explorer-cut').click(function() {
  488. store_selected_files(current_directory);
  489. });
  490. $('#explorer-paste').click(function() {
  491. paste_selected_files();
  492. });
  493. init();
  494. })();