From 679631b1885cffaf8fc8d2677da15152db139065 Mon Sep 17 00:00:00 2001 From: Inigo Goiri Date: Thu, 26 Mar 2020 08:23:18 -0700 Subject: [PATCH] HDFS-13470. RBF: Add Browse the Filesystem button to the UI. --- .../src/main/webapps/router/explorer.html | 323 ++++++++++ .../src/main/webapps/router/explorer.js | 552 ++++++++++++++++++ .../main/webapps/router/federationhealth.html | 5 +- 3 files changed, 879 insertions(+), 1 deletion(-) create mode 100644 hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.html create mode 100644 hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.js diff --git a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.html b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.html new file mode 100644 index 00000000000..3a5956d9e64 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.html @@ -0,0 +1,323 @@ + + + + + + + + + + Browsing HDFS + + + + + + +
+ + + + + + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+ +
+
+ +
+
+

Hadoop, {release-year-token}.

+
+ +
+ + + + + + + + + diff --git a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.js b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.js new file mode 100644 index 00000000000..59a4b8a4284 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.js @@ -0,0 +1,552 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + "use strict"; + + // The chunk size of tailing the files, i.e., how many bytes will be shown + // in the preview. + var TAIL_CHUNK_SIZE = 32768; + + //This stores the current directory which is being browsed + var current_directory = ""; + + function show_err_msg(msg) { + $('#alert-panel-body').html(msg); + $('#alert-panel').show(); + } + + $(window).bind('hashchange', function () { + $('#alert-panel').hide(); + + var dir = decodeURIComponent(window.location.hash.slice(1)); + if(dir == "") { + dir = "/"; + } + if(current_directory != dir) { + browse_directory(dir); + } + }); + + function network_error_handler(url) { + return function (jqxhr, text, err) { + switch(jqxhr.status) { + case 401: + var msg = '

Authentication failed when trying to open ' + url + ': Unauthorized.

'; + break; + case 403: + if(jqxhr.responseJSON !== undefined && jqxhr.responseJSON.RemoteException !== undefined) { + var msg = '

' + jqxhr.responseJSON.RemoteException.message + "

"; + break; + } + var msg = '

Permission denied when trying to open ' + url + ': ' + err + '

'; + break; + case 404: + var msg = '

Path does not exist on HDFS or WebHDFS is disabled. Please check your path or enable WebHDFS

'; + break; + default: + var msg = '

Failed to retrieve data from ' + url + ': ' + err + '

'; + } + show_err_msg(msg); + }; + } + + function append_path(prefix, s) { + var l = prefix.length; + var p = l > 0 && prefix[l - 1] == '/' ? prefix.substring(0, l - 1) : prefix; + return p + '/' + s; + } + + function get_response(data, type) { + return data[type] !== undefined ? data[type] : null; + } + + function get_response_err_msg(data) { + return data.RemoteException !== undefined ? data.RemoteException.message : ""; + } + + function delete_path(inode_name, absolute_file_path) { + $('#delete-modal-title').text("Delete - " + inode_name); + $('#delete-prompt').text("Are you sure you want to delete " + inode_name + + " ?"); + + $('#delete-button').click(function() { + // DELETE /webhdfs/v1/?op=DELETE&recursive= + var url = '/webhdfs/v1' + encode_path(absolute_file_path) + + '?op=DELETE' + '&recursive=true'; + + $.ajax(url, + { type: 'DELETE' + }).done(function(data) { + browse_directory(current_directory); + }).fail(network_error_handler(url) + ).always(function() { + $('#delete-modal').modal('hide'); + $('#delete-button').button('reset'); + }); + }) + $('#delete-modal').modal(); + } + + /* This method loads the checkboxes on the permission info modal. It accepts + * the octal permissions, eg. '644' or '755' and infers the checkboxes that + * should be true and false + */ + function view_perm_details(e, filename, abs_path, perms) { + $('.explorer-perm-links').popover('destroy'); + e.popover({html: true, content: $('#explorer-popover-perm-info').html(), trigger: 'focus'}) + .on('shown.bs.popover', function(e) { + var popover = $(this), parent = popover.parent(); + //Convert octal to binary permissions + var bin_perms = parseInt(perms, 8).toString(2); + bin_perms = bin_perms.length == 9 ? "0" + bin_perms : bin_perms; + parent.find('#explorer-perm-cancel').on('click', function() { popover.popover('destroy'); }); + parent.find('#explorer-set-perm-button').off().click(function() { set_permissions(abs_path); }); + parent.find('input[type=checkbox]').each(function(idx, element) { + var e = $(element); + e.prop('checked', bin_perms.charAt(9 - e.attr('data-bit')) == '1'); + }); + }) + .popover('show'); + } + + // Use WebHDFS to set permissions on an absolute path + function set_permissions(abs_path) { + var p = 0; + $.each($('.popover .explorer-popover-perm-body input:checked'), function(idx, e) { + p |= 1 << (+$(e).attr('data-bit')); + }); + + var permission_mask = p.toString(8); + + // PUT /webhdfs/v1/?op=SETPERMISSION&permission= + var url = '/webhdfs/v1' + encode_path(abs_path) + + '?op=SETPERMISSION' + '&permission=' + permission_mask; + + $.ajax(url, { type: 'PUT' + }).done(function(data) { + browse_directory(current_directory); + }).fail(network_error_handler(url)) + .always(function() { + $('.explorer-perm-links').popover('destroy'); + }); + } + + function encode_path(abs_path) { + abs_path = encodeURIComponent(abs_path); + var re = /%2F/g; + return abs_path.replace(re, '/'); + } + + function view_file_details(path, abs_path) { + function show_block_info(blocks) { + var menus = $('#file-info-blockinfo-list'); + menus.empty(); + + menus.data("blocks", blocks); + menus.change(function() { + var d = $(this).data('blocks')[$(this).val()]; + if (d === undefined) { + return; + } + + dust.render('block-info', d, function(err, out) { + $('#file-info-blockinfo-body').html(out); + }); + + }); + for (var i = 0; i < blocks.length; ++i) { + var item = $(''); + menus.append(item); + } + menus.change(); + } + + abs_path = encode_path(abs_path); + var url = '/webhdfs/v1' + abs_path + '?op=GET_BLOCK_LOCATIONS'; + $.ajax({url: url, dataType: 'text'}).done(function(data_text) { + var data = JSONParseBigNum(data_text); + var d = get_response(data, "LocatedBlocks"); + if (d === null) { + show_err_msg(get_response_err_msg(data)); + return; + } + + $('#file-info-tail').hide(); + $('#file-info-title').text("File information - " + path); + + var download_url = '/webhdfs/v1' + abs_path + '?op=OPEN'; + + $('#file-info-download').attr('href', download_url); + + var processPreview = function(url) { + url += "&noredirect=true"; + if(request && request.readyState != 4){ + request.abort(); + } + request = $.ajax({ + cache: false, + type: 'GET', + url: url, + async: false, + processData: false, + crossDomain: true + }).done(function(data, textStatus, jqXHR) { + + url = data.Location; + $.ajax({ + cache: false, + type: 'GET', + url: url, + async: false, + processData: false, + crossDomain: true + }).always(function(data, textStatus, jqXHR) { + $('#file-info-preview-body').val(jqXHR.responseText); + $('#file-info-tail').show(); + }).fail(function(jqXHR, textStatus, errorThrown) { + show_err_msg("Couldn't preview the file. " + errorThrown); + }); + }).fail(function(jqXHR, textStatus, errorThrown) { + show_err_msg("Couldn't find datanode to read file from. " + errorThrown); + }); + } + + var request = null; + $('#file-info-preview-tail') + .off('click') + .on('click', function() { + var offset = d.fileLength - TAIL_CHUNK_SIZE; + var url = offset > 0 ? download_url + '&offset=' + offset : download_url; + processPreview(url); + }); + $('#file-info-preview-head') + .off('click') + .on('click', function() { + var url = d.fileLength > TAIL_CHUNK_SIZE ? download_url + '&length=' + TAIL_CHUNK_SIZE : download_url; + processPreview(url); + }); + + if (d.fileLength > 0) { + show_block_info(d.locatedBlocks); + $('#file-info-blockinfo-panel').show(); + } else { + $('#file-info-blockinfo-panel').hide(); + } + $('#file-info').modal(); + }).fail(network_error_handler(url)); + } + + /**Use X-editable to make fields editable with a nice UI. + * elementType is the class of element(s) you want to make editable + * op is the WebHDFS operation that will be triggered + * parameter is (currently the 1) parameter which will be passed along with + * the value entered by the user + */ + function makeEditable(elementType, op, parameter) { + $(elementType).each(function(index, value) { + $(this).editable({ + url: function(params) { + var inode_name = $(this).closest('tr').attr('inode-path'); + var absolute_file_path = append_path(current_directory, inode_name); + var url = '/webhdfs/v1' + encode_path(absolute_file_path) + '?op=' + + op + '&' + parameter + '=' + encodeURIComponent(params.value); + + return $.ajax(url, { type: 'PUT', }) + .fail(network_error_handler(url)) + .done(function() { + browse_directory(current_directory); + }); + }, + error: function(response, newValue) {return "";} + }); + }); + } + + function func_size_render(data, type, row, meta) { + if(type == 'display') { + return dust.filters.fmt_bytes(data); + } + else return data; + } + + // Change the format of date-time depending on how old the + // the timestamp is. If older than 6 months, no need to be + // show exact time. + function func_time_render(data, type, row, meta) { + if(type == 'display') { + var cutoff = moment().subtract(6, 'months').unix() * 1000; + if(data < cutoff) { + return moment(Number(data)).format('MMM DD YYYY'); + } else { + return moment(Number(data)).format('MMM DD HH:mm'); + } + } + return data; + } + + function browse_directory(dir) { + var HELPERS = { + 'helper_date_tostring' : function (chunk, ctx, bodies, params) { + var value = dust.helpers.tap(params.value, chunk, ctx); + return chunk.write('' + moment(Number(value)).format('ddd MMM DD HH:mm:ss ZZ YYYY')); + } + }; + var url = '/webhdfs/v1' + encode_path(dir) + '?op=LISTSTATUS'; + $.get(url, function(data) { + var d = get_response(data, "FileStatuses"); + if (d === null) { + show_err_msg(get_response_err_msg(data)); + return; + } + + current_directory = dir; + $('#directory').val(dir); + window.location.hash = dir; + var base = dust.makeBase(HELPERS); + dust.render('explorer', base.push(d), function(err, out) { + $('#panel').html(out); + + + $('.explorer-browse-links').click(function() { + var type = $(this).attr('inode-type'); + var path = $(this).closest('tr').attr('inode-path'); + var abs_path = append_path(current_directory, path); + if (type == 'DIRECTORY') { + browse_directory(abs_path); + } else { + view_file_details(path, abs_path); + } + }); + + //Set the handler for changing permissions + $('.explorer-perm-links').click(function() { + var filename = $(this).closest('tr').attr('inode-path'); + var abs_path = append_path(current_directory, filename); + var perms = $(this).closest('tr').attr('data-permission'); + view_perm_details($(this), filename, abs_path, perms); + }); + + makeEditable('.explorer-owner-links', 'SETOWNER', 'owner'); + makeEditable('.explorer-group-links', 'SETOWNER', 'group'); + makeEditable('.explorer-replication-links', 'SETREPLICATION', 'replication'); + + $('.explorer-entry .glyphicon-trash').click(function() { + var inode_name = $(this).closest('tr').attr('inode-path'); + var absolute_file_path = append_path(current_directory, inode_name); + delete_path(inode_name, absolute_file_path); + }); + + $('#file-selector-all').click(function() { + $('.file_selector').prop('checked', $('#file-selector-all')[0].checked ); + }); + + //This needs to be last because it repaints the table + $('#table-explorer').dataTable( { + 'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ], + 'columns': [ + { 'orderable' : false }, //select + {'searchable': false }, //Permissions + null, //Owner + null, //Group + { 'searchable': false, 'render': func_size_render}, //Size + { 'searchable': false, 'render': func_time_render}, //Last Modified + { 'searchable': false }, //Replication + null, //Block Size + null, //Name + { 'orderable' : false } //Trash + ], + "deferRender": true + }); + }); + }).fail(network_error_handler(url)); + } + + + function init() { + dust.loadSource(dust.compile($('#tmpl-explorer').html(), 'explorer')); + dust.loadSource(dust.compile($('#tmpl-block-info').html(), 'block-info')); + + var b = function() { browse_directory($('#directory').val()); }; + $('#btn-nav-directory').click(b); + //Also navigate to the directory when a user presses enter. + $('#directory').on('keyup', function (e) { + if (e.which == 13) { + browse_directory($('#directory').val()); + } + }); + var dir = window.location.hash.slice(1); + if(dir == "") { + window.location.hash = "/"; + } else { + browse_directory(dir); + } + } + + $('#btn-create-directory').on('show.bs.modal', function(event) { + $('#new_directory_pwd').text(current_directory); + }); + + $('#btn-create-directory-send').click(function () { + $(this).prop('disabled', true); + $(this).button('complete'); + + var url = '/webhdfs/v1' + encode_path(append_path(current_directory, + $('#new_directory').val())) + '?op=MKDIRS'; + + $.ajax(url, { type: 'PUT' } + ).done(function(data) { + browse_directory(current_directory); + }).fail(network_error_handler(url) + ).always(function() { + $('#btn-create-directory').modal('hide'); + $('#btn-create-directory-send').button('reset'); + }); + }) + + $('#btn-upload-files').click(function() { + $('#modal-upload-file-button').prop('disabled', true).button('reset'); + $('#modal-upload-file-input').val(null); + }); + + $('#btn-create-dir').click(function() { + $('#btn-create-directory-send').prop('disabled', true).button('reset'); + $('#new_directory').val(null); + }); + + $('#modal-upload-file-input').change(function() { + if($('#modal-upload-file-input').prop('files').length >0) { + $('#modal-upload-file-button').prop('disabled', false); + } + else { + $('#modal-upload-file-button').prop('disabled', true); + } + }); + + $('#new_directory').on('keyup keypress blur change',function() { + if($('#new_directory').val() == '' || $('#new_directory').val() == null) { + $('#btn-create-directory-send').prop('disabled', true); + } + else { + $('#btn-create-directory-send').prop('disabled', false); + } + }); + + $('#modal-upload-file-button').click(function() { + $(this).prop('disabled', true); + $(this).button('complete'); + var files = [] + var numCompleted = 0 + + for(var i = 0; i < $('#modal-upload-file-input').prop('files').length; i++) { + (function() { + var file = $('#modal-upload-file-input').prop('files')[i]; + var url = '/webhdfs/v1' + encode_path(append_path(current_directory, file.name)); + url += '?op=CREATE&noredirect=true'; + files.push( { file: file } ) + files[i].request = $.ajax({ + type: 'PUT', + url: url, + processData: false, + crossDomain: true + }); + })() + } + for(var f in files) { + (function() { + var file = files[f]; + file.request.done(function(data) { + var url = data['Location']; + $.ajax({ + type: 'PUT', + url: url, + data: file.file, + processData: false, + crossDomain: true + }).always(function(data) { + numCompleted++; + if(numCompleted == files.length) { + reset_upload_button(); + browse_directory(current_directory); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + numCompleted++; + reset_upload_button(); + show_err_msg("Couldn't upload the file " + file.file.name + ". "+ errorThrown); + }); + }).fail(function(jqXHR, textStatus, errorThrown) { + numCompleted++; + reset_upload_button(); + show_err_msg("Couldn't find datanode to write file. " + errorThrown); + }); + })(); + } + }); + + //Reset the upload button + function reset_upload_button() { + $('#modal-upload-file').modal('hide'); + $('#modal-upload-file-button').button('reset'); + } + + //Store the list of files which have been checked into session storage + function store_selected_files(current_directory) { + sessionStorage.setItem("source_directory", current_directory); + var selected_files = $("input:checked.file_selector"); + var selected_file_names = new Array(); + selected_files.each(function(index) { + selected_file_names[index] = $(this).closest('tr').attr('inode-path'); + }) + sessionStorage.setItem("selected_file_names", JSON.stringify(selected_file_names)); + alert("Cut " + selected_file_names.length + " files/directories"); + } + + //Retrieve the list of files from session storage and rename them to the current + //directory + function paste_selected_files() { + var files = JSON.parse(sessionStorage.getItem("selected_file_names")); + var source_directory = sessionStorage.getItem("source_directory"); + $.each(files, function(index, value) { + var url = "/webhdfs/v1" + + encode_path(append_path(source_directory, value)) + + '?op=RENAME&destination=' + + encode_path(append_path(current_directory, value)); + $.ajax({ + type: 'PUT', + url: url + }).done(function(data) { + if(index == files.length - 1) { + browse_directory(current_directory); + } + }).fail(function(jqXHR, textStatus, errorThrown) { + show_err_msg("Couldn't move file " + value + ". " + errorThrown); + }); + + }) + } + + $('#explorer-cut').click(function() { + store_selected_files(current_directory); + }); + + $('#explorer-paste').click(function() { + paste_selected_files(); + }); + + + init(); +})(); diff --git a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/federationhealth.html b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/federationhealth.html index 15e29c4fb3a..66c030973d9 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/federationhealth.html +++ b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/federationhealth.html @@ -41,9 +41,12 @@