explorer.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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. $.ajax({
  172. type: 'GET',
  173. url: url,
  174. processData: false,
  175. crossDomain: true
  176. }).done(function(data) {
  177. url = data.Location;
  178. $.ajax({
  179. type: 'GET',
  180. url: url,
  181. processData: false,
  182. crossDomain: true
  183. }).always(function(data) {
  184. $('#file-info-preview-body').val(data.responseText);
  185. $('#file-info-tail').show();
  186. }).fail(function(jqXHR, textStatus, errorThrown) {
  187. show_err_msg("Couldn't preview the file. " + errorThrown);
  188. });
  189. }).fail(function(jqXHR, textStatus, errorThrown) {
  190. show_err_msg("Couldn't find datanode to read file from. " + errorThrown);
  191. });
  192. }
  193. $('#file-info-preview-tail').click(function() {
  194. var offset = d.fileLength - TAIL_CHUNK_SIZE;
  195. var url = offset > 0 ? download_url + '&offset=' + offset : download_url;
  196. processPreview(url);
  197. });
  198. $('#file-info-preview-head').click(function() {
  199. var url = d.fileLength > TAIL_CHUNK_SIZE ? download_url + '&length=' + TAIL_CHUNK_SIZE : download_url;
  200. processPreview(url);
  201. });
  202. if (d.fileLength > 0) {
  203. show_block_info(d.locatedBlocks);
  204. $('#file-info-blockinfo-panel').show();
  205. } else {
  206. $('#file-info-blockinfo-panel').hide();
  207. }
  208. $('#file-info').modal();
  209. }).fail(network_error_handler(url));
  210. }
  211. /**Use X-editable to make fields editable with a nice UI.
  212. * elementType is the class of element(s) you want to make editable
  213. * op is the WebHDFS operation that will be triggered
  214. * parameter is (currently the 1) parameter which will be passed along with
  215. * the value entered by the user
  216. */
  217. function makeEditable(elementType, op, parameter) {
  218. $(elementType).each(function(index, value) {
  219. $(this).editable({
  220. url: function(params) {
  221. var inode_name = $(this).closest('tr').attr('inode-path');
  222. var absolute_file_path = append_path(current_directory, inode_name);
  223. var url = '/webhdfs/v1' + encode_path(absolute_file_path) + '?op=' +
  224. op + '&' + parameter + '=' + encodeURIComponent(params.value);
  225. return $.ajax(url, { type: 'PUT', })
  226. .fail(network_error_handler(url))
  227. .done(function() {
  228. browse_directory(current_directory);
  229. });
  230. },
  231. error: function(response, newValue) {return "";}
  232. });
  233. });
  234. }
  235. function func_size_render(data, type, row, meta) {
  236. if(type == 'display') {
  237. return dust.filters.fmt_bytes(data);
  238. }
  239. else return data;
  240. }
  241. // Change the format of date-time depending on how old the
  242. // the timestamp is. If older than 6 months, no need to be
  243. // show exact time.
  244. function func_time_render(data, type, row, meta) {
  245. if(type == 'display') {
  246. var cutoff = moment().subtract(6, 'months').unix() * 1000;
  247. if(data < cutoff) {
  248. return moment(Number(data)).format('MMM DD YYYY');
  249. } else {
  250. return moment(Number(data)).format('MMM DD HH:mm');
  251. }
  252. }
  253. return data;
  254. }
  255. function browse_directory(dir) {
  256. var HELPERS = {
  257. 'helper_date_tostring' : function (chunk, ctx, bodies, params) {
  258. var value = dust.helpers.tap(params.value, chunk, ctx);
  259. return chunk.write('' + moment(Number(value)).format('ddd MMM DD HH:mm:ss ZZ YYYY'));
  260. }
  261. };
  262. var url = '/webhdfs/v1' + encode_path(dir) + '?op=LISTSTATUS';
  263. $.get(url, function(data) {
  264. var d = get_response(data, "FileStatuses");
  265. if (d === null) {
  266. show_err_msg(get_response_err_msg(data));
  267. return;
  268. }
  269. current_directory = dir;
  270. $('#directory').val(dir);
  271. window.location.hash = dir;
  272. var base = dust.makeBase(HELPERS);
  273. dust.render('explorer', base.push(d), function(err, out) {
  274. $('#panel').html(out);
  275. $('.explorer-browse-links').click(function() {
  276. var type = $(this).attr('inode-type');
  277. var path = $(this).closest('tr').attr('inode-path');
  278. var abs_path = append_path(current_directory, path);
  279. if (type == 'DIRECTORY') {
  280. browse_directory(abs_path);
  281. } else {
  282. view_file_details(path, abs_path);
  283. }
  284. });
  285. //Set the handler for changing permissions
  286. $('.explorer-perm-links').click(function() {
  287. var filename = $(this).closest('tr').attr('inode-path');
  288. var abs_path = append_path(current_directory, filename);
  289. var perms = $(this).closest('tr').attr('data-permission');
  290. view_perm_details($(this), filename, abs_path, perms);
  291. });
  292. makeEditable('.explorer-owner-links', 'SETOWNER', 'owner');
  293. makeEditable('.explorer-group-links', 'SETOWNER', 'group');
  294. makeEditable('.explorer-replication-links', 'SETREPLICATION', 'replication');
  295. $('.explorer-entry .glyphicon-trash').click(function() {
  296. var inode_name = $(this).closest('tr').attr('inode-path');
  297. var absolute_file_path = append_path(current_directory, inode_name);
  298. delete_path(inode_name, absolute_file_path);
  299. });
  300. $('#file-selector-all').click(function() {
  301. $('.file_selector').prop('checked', $('#file-selector-all')[0].checked );
  302. });
  303. //This needs to be last because it repaints the table
  304. $('#table-explorer').dataTable( {
  305. 'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
  306. 'columns': [
  307. { 'orderable' : false }, //select
  308. {'searchable': false }, //Permissions
  309. null, //Owner
  310. null, //Group
  311. { 'searchable': false, 'render': func_size_render}, //Size
  312. { 'searchable': false, 'render': func_time_render}, //Last Modified
  313. { 'searchable': false }, //Replication
  314. null, //Block Size
  315. null, //Name
  316. { 'orderable' : false } //Trash
  317. ],
  318. "deferRender": true
  319. });
  320. });
  321. }).fail(network_error_handler(url));
  322. }
  323. function init() {
  324. dust.loadSource(dust.compile($('#tmpl-explorer').html(), 'explorer'));
  325. dust.loadSource(dust.compile($('#tmpl-block-info').html(), 'block-info'));
  326. var b = function() { browse_directory($('#directory').val()); };
  327. $('#btn-nav-directory').click(b);
  328. //Also navigate to the directory when a user presses enter.
  329. $('#directory').on('keyup', function (e) {
  330. if (e.which == 13) {
  331. browse_directory($('#directory').val());
  332. }
  333. });
  334. var dir = window.location.hash.slice(1);
  335. if(dir == "") {
  336. window.location.hash = "/";
  337. } else {
  338. browse_directory(dir);
  339. }
  340. }
  341. $('#btn-create-directory').on('show.bs.modal', function(event) {
  342. $('#new_directory_pwd').text(current_directory);
  343. });
  344. $('#btn-create-directory-send').click(function () {
  345. $(this).prop('disabled', true);
  346. $(this).button('complete');
  347. var url = '/webhdfs/v1' + encode_path(append_path(current_directory,
  348. $('#new_directory').val())) + '?op=MKDIRS';
  349. $.ajax(url, { type: 'PUT' }
  350. ).done(function(data) {
  351. browse_directory(current_directory);
  352. }).fail(network_error_handler(url)
  353. ).always(function() {
  354. $('#btn-create-directory').modal('hide');
  355. $('#btn-create-directory-send').button('reset');
  356. });
  357. })
  358. $('#modal-upload-file-button').click(function() {
  359. $(this).prop('disabled', true);
  360. $(this).button('complete');
  361. var files = []
  362. var numCompleted = 0
  363. for(var i = 0; i < $('#modal-upload-file-input').prop('files').length; i++) {
  364. (function() {
  365. var file = $('#modal-upload-file-input').prop('files')[i];
  366. var url = '/webhdfs/v1' + current_directory;
  367. url = encode_path(append_path(url, file.name));
  368. url += '?op=CREATE&noredirect=true';
  369. files.push( { file: file } )
  370. files[i].request = $.ajax({
  371. type: 'PUT',
  372. url: url,
  373. processData: false,
  374. crossDomain: true
  375. });
  376. })()
  377. }
  378. for(var f in files) {
  379. (function() {
  380. var file = files[f];
  381. file.request.done(function(data) {
  382. var url = data['Location'];
  383. $.ajax({
  384. type: 'PUT',
  385. url: url,
  386. data: file.file,
  387. processData: false,
  388. crossDomain: true
  389. }).always(function(data) {
  390. numCompleted++;
  391. if(numCompleted == files.length) {
  392. reset_upload_button();
  393. browse_directory(current_directory);
  394. }
  395. }).fail(function(jqXHR, textStatus, errorThrown) {
  396. numCompleted++;
  397. reset_upload_button();
  398. show_err_msg("Couldn't upload the file " + file.file.name + ". "+ errorThrown);
  399. });
  400. }).fail(function(jqXHR, textStatus, errorThrown) {
  401. numCompleted++;
  402. reset_upload_button();
  403. show_err_msg("Couldn't find datanode to write file. " + errorThrown);
  404. });
  405. })();
  406. }
  407. });
  408. //Reset the upload button
  409. function reset_upload_button() {
  410. $('#modal-upload-file').modal('hide');
  411. $('#modal-upload-file-button').button('reset');
  412. }
  413. //Store the list of files which have been checked into session storage
  414. function store_selected_files(current_directory) {
  415. sessionStorage.setItem("source_directory", current_directory);
  416. var selected_files = $("input:checked.file_selector");
  417. var selected_file_names = new Array();
  418. selected_files.each(function(index) {
  419. selected_file_names[index] = $(this).closest('tr').attr('inode-path');
  420. })
  421. sessionStorage.setItem("selected_file_names", JSON.stringify(selected_file_names));
  422. alert("Cut " + selected_file_names.length + " files/directories");
  423. }
  424. //Retrieve the list of files from session storage and rename them to the current
  425. //directory
  426. function paste_selected_files() {
  427. var files = JSON.parse(sessionStorage.getItem("selected_file_names"));
  428. var source_directory = sessionStorage.getItem("source_directory");
  429. $.each(files, function(index, value) {
  430. var url = "/webhdfs/v1"
  431. + encode_path(append_path(source_directory, value))
  432. + '?op=RENAME&destination='
  433. + encode_path(append_path(current_directory, value));
  434. $.ajax({
  435. type: 'PUT',
  436. url: url
  437. }).done(function(data) {
  438. if(index == files.length - 1) {
  439. browse_directory(current_directory);
  440. }
  441. }).fail(function(jqXHR, textStatus, errorThrown) {
  442. show_err_msg("Couldn't move file " + value + ". " + errorThrown);
  443. });
  444. })
  445. }
  446. $('#explorer-cut').click(function() {
  447. store_selected_files(current_directory);
  448. });
  449. $('#explorer-paste').click(function() {
  450. paste_selected_files();
  451. });
  452. init();
  453. })();