explorer.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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 = 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. }).error(network_error_handler(url)
  85. ).complete(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. }).error(network_error_handler(url))
  127. .complete(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. $('#file-info-preview').click(function() {
  170. var offset = d.fileLength - TAIL_CHUNK_SIZE;
  171. var url = offset > 0 ? download_url + '&offset=' + offset : download_url;
  172. $.get(url, function(t) {
  173. $('#file-info-preview-body').val(t);
  174. $('#file-info-tail').show();
  175. }, "text").error(network_error_handler(url));
  176. });
  177. if (d.fileLength > 0) {
  178. show_block_info(d.locatedBlocks);
  179. $('#file-info-blockinfo-panel').show();
  180. } else {
  181. $('#file-info-blockinfo-panel').hide();
  182. }
  183. $('#file-info').modal();
  184. }).error(network_error_handler(url));
  185. }
  186. /**Use X-editable to make fields editable with a nice UI.
  187. * elementType is the class of element(s) you want to make editable
  188. * op is the WebHDFS operation that will be triggered
  189. * parameter is (currently the 1) parameter which will be passed along with
  190. * the value entered by the user
  191. */
  192. function makeEditable(elementType, op, parameter) {
  193. $(elementType).each(function(index, value) {
  194. $(this).editable({
  195. url: function(params) {
  196. var inode_name = $(this).closest('tr').attr('inode-path');
  197. var absolute_file_path = append_path(current_directory, inode_name);
  198. var url = '/webhdfs/v1' + encode_path(absolute_file_path) + '?op=' +
  199. op + '&' + parameter + '=' + encodeURIComponent(params.value);
  200. return $.ajax(url, { type: 'PUT', })
  201. .error(network_error_handler(url))
  202. .success(function() {
  203. browse_directory(current_directory);
  204. });
  205. },
  206. error: function(response, newValue) {return "";}
  207. });
  208. });
  209. }
  210. function func_size_render(data, type, row, meta) {
  211. if(type == 'display') {
  212. return dust.filters.fmt_bytes(data);
  213. }
  214. else return data;
  215. }
  216. // Change the format of date-time depending on how old the
  217. // the timestamp is. If older than 6 months, no need to be
  218. // show exact time.
  219. function func_time_render(data, type, row, meta) {
  220. if(type == 'display') {
  221. var cutoff = moment().subtract(6, 'months').unix() * 1000;
  222. if(data < cutoff) {
  223. return moment(Number(data)).format('MMM DD YYYY');
  224. } else {
  225. return moment(Number(data)).format('MMM DD HH:mm');
  226. }
  227. }
  228. return data;
  229. }
  230. function browse_directory(dir) {
  231. var HELPERS = {
  232. 'helper_date_tostring' : function (chunk, ctx, bodies, params) {
  233. var value = dust.helpers.tap(params.value, chunk, ctx);
  234. return chunk.write('' + moment(Number(value)).format('ddd MMM DD HH:mm:ss ZZ YYYY'));
  235. }
  236. };
  237. var url = '/webhdfs/v1' + encode_path(dir) + '?op=LISTSTATUS';
  238. $.get(url, function(data) {
  239. var d = get_response(data, "FileStatuses");
  240. if (d === null) {
  241. show_err_msg(get_response_err_msg(data));
  242. return;
  243. }
  244. current_directory = dir;
  245. $('#directory').val(dir);
  246. window.location.hash = dir;
  247. var base = dust.makeBase(HELPERS);
  248. dust.render('explorer', base.push(d), function(err, out) {
  249. $('#panel').html(out);
  250. $('.explorer-browse-links').click(function() {
  251. var type = $(this).attr('inode-type');
  252. var path = $(this).closest('tr').attr('inode-path');
  253. var abs_path = append_path(current_directory, path);
  254. if (type == 'DIRECTORY') {
  255. browse_directory(abs_path);
  256. } else {
  257. view_file_details(path, abs_path);
  258. }
  259. });
  260. //Set the handler for changing permissions
  261. $('.explorer-perm-links').click(function() {
  262. var filename = $(this).closest('tr').attr('inode-path');
  263. var abs_path = append_path(current_directory, filename);
  264. var perms = $(this).closest('tr').attr('data-permission');
  265. view_perm_details($(this), filename, abs_path, perms);
  266. });
  267. makeEditable('.explorer-owner-links', 'SETOWNER', 'owner');
  268. makeEditable('.explorer-group-links', 'SETOWNER', 'group');
  269. makeEditable('.explorer-replication-links', 'SETREPLICATION', 'replication');
  270. $('.explorer-entry .glyphicon-trash').click(function() {
  271. var inode_name = $(this).closest('tr').attr('inode-path');
  272. var absolute_file_path = append_path(current_directory, inode_name);
  273. delete_path(inode_name, absolute_file_path);
  274. });
  275. $('#table-explorer').dataTable( {
  276. 'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
  277. 'columns': [
  278. {'searchable': false }, //Permissions
  279. null, //Owner
  280. null, //Group
  281. { 'searchable': false, 'render': func_size_render}, //Size
  282. { 'searchable': false, 'render': func_time_render}, //Last Modified
  283. { 'searchable': false }, //Replication
  284. null, //Block Size
  285. null, //Name
  286. { 'sortable' : false } //Trash
  287. ],
  288. "deferRender": true
  289. });
  290. });
  291. }).error(network_error_handler(url));
  292. }
  293. function init() {
  294. dust.loadSource(dust.compile($('#tmpl-explorer').html(), 'explorer'));
  295. dust.loadSource(dust.compile($('#tmpl-block-info').html(), 'block-info'));
  296. var b = function() { browse_directory($('#directory').val()); };
  297. $('#btn-nav-directory').click(b);
  298. var dir = window.location.hash.slice(1);
  299. if(dir == "") {
  300. window.location.hash = "/";
  301. } else {
  302. browse_directory(dir);
  303. }
  304. }
  305. $('#btn-create-directory').on('show.bs.modal', function(event) {
  306. $('#new_directory_pwd').text(current_directory);
  307. });
  308. $('#btn-create-directory-send').click(function () {
  309. $(this).prop('disabled', true);
  310. $(this).button('complete');
  311. var url = '/webhdfs/v1' + encode_path(append_path(current_directory,
  312. $('#new_directory').val())) + '?op=MKDIRS';
  313. $.ajax(url, { type: 'PUT' }
  314. ).done(function(data) {
  315. browse_directory(current_directory);
  316. }).error(network_error_handler(url)
  317. ).complete(function() {
  318. $('#btn-create-directory').modal('hide');
  319. $('#btn-create-directory-send').button('reset');
  320. });
  321. })
  322. init();
  323. })();