Ver Fonte

HDFS-13470. RBF: Add Browse the Filesystem button to the UI.

Inigo Goiri há 5 anos atrás
pai
commit
679631b188

+ 323 - 0
hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/explorer.html

@@ -0,0 +1,323 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+    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.
+  -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <link rel="stylesheet" type="text/css" href="/static/bootstrap-3.4.1/css/bootstrap.min.css" />
+    <link rel="stylesheet" type="text/css" href="/static/bootstrap-3.4.1/css/bootstrap-editable.css"/>
+    <link rel="stylesheet" type="text/css" href="/static/dataTables.bootstrap.css" />
+    <link rel="stylesheet" type="text/css" href="/static/hadoop.css" />
+    <title>Browsing HDFS</title>
+  </head>
+  <body>
+
+    <header class="navbar navbar-inverse bs-docs-nav" role="banner">
+    <div class="container">
+      <div class="navbar-header">
+        <div class="navbar-brand">Hadoop</div>
+      </div>
+
+      <ul class="nav navbar-nav" id="ui-tabs">
+        <li><a href="federationhealth.html#tab-overview">Overview</a></li>
+        <li><a href="federationhealth.html#tab-namenode">Subclusters</a></li>
+        <li><a href="federationhealth.html#tab-router">Routers</a></li>
+        <li><a href="federationhealth.html#tab-datanode">Datanodes</a></li>
+        <li><a href="federationhealth.html#tab-mounttable">Mount table</a></li>
+        <li class="dropdown">
+          <a href="#" class="dropdown-toggle" data-toggle="dropdown">Utilities <b class="caret"></b></a>
+          <ul class="dropdown-menu">
+            <li><a href="#">Browse the file system</a></li>
+            <li><a href="logs">Logs</a></li>
+            <li><a href="logLevel">Log Level</a></li>
+            <li><a href="jmx">Metrics</a></li>
+            <li><a href="conf">Configuration</a></li>
+            <li><a href="stacks">Process Thread Dump</a></li>
+          </ul>
+        </li>
+      </ul>
+    </div>
+    </header>
+
+    <div class="modal" id="file-info" tabindex="-1" role="dialog" aria-hidden="true">
+      <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+        <h4 class="modal-title" id="file-info-title">File information</h4>
+      </div>
+      <div class="modal-body" id="file-info-body">
+        <div class=row>
+              <span class="col-xs-4">
+                <a id="file-info-download">Download</a>
+              </span>
+              <span class="col-xs-4">
+                <a id="file-info-preview-head" style="cursor:pointer">Head the file (first 32K)</a>
+              </span>
+              <span class="col-xs-4">
+                <a id="file-info-preview-tail" style="cursor:pointer">Tail the file (last 32K)</a>
+          </span>
+        </div>
+        <hr />
+        <div class="panel panel-success" id="file-info-blockinfo-panel">
+          <div class="panel-heading">
+        Block information --
+        <select class="btn btn-default" id="file-info-blockinfo-list">
+        </select>
+          </div>
+          <div class="panel-body" id="file-info-blockinfo-body"></div>
+        </div>
+        <div class="panel panel-info" id="file-info-tail" style="display:none">
+          <div class="panel-heading">File contents</div>
+          <div class="panel-body">
+        <div class="input-group-sm">
+        <textarea class="form-control" style="height: 150px" id="file-info-preview-body"></textarea>
+        </div>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer"><button type="button" class="btn btn-success"
+                        data-dismiss="modal">Close</button></div>
+    </div>
+      </div>
+    </div>
+    <div class="container">
+      <div class="page-header">
+    <h1>Browse Directory</h1>
+      </div>
+      <div class="alert alert-danger" id="alert-panel" style="display:none">
+    <button type="button" class="close" onclick="$('#alert-panel').hide();">&times;</button>
+    <div class="alert-body" id="alert-panel-body"></div>
+      </div>
+    <div class="modal" id="btn-create-directory" tabindex="-1" role="dialog"
+      aria-hidden="true">
+      <div class="modal-dialog">
+        <div class="modal-content">
+          <div class="modal-header">
+            <button type="button" class="close"
+              data-dismiss="modal" aria-hidden="true">&times;</button>
+            <h4 class="modal-title">Create Directory</h4>
+          </div>
+          <div class="modal-body">
+            <div class="form-group">
+              <div class="input-group">
+                <span class="input-group-addon" id="new_directory_pwd"></span>
+                <input type="text" class="form-control" id="new_directory"
+                  placeholder="New Directory Name" />
+              </div>
+            </div>
+          </div>
+          <div class="modal-footer">
+            <button type="button" class="btn" data-dismiss="modal">Cancel</button>
+            <button type="button" class="btn btn-success"
+              id="btn-create-directory-send" data-complete-text="Creating...">
+              Create
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="modal" id="modal-upload-file" tabindex="-1" role="dialog" aria-hidden="true">
+      <div class="modal-dialog">
+         <div class="modal-content">
+           <div class="modal-header"><button type="button" class="close"
+             data-dismiss="modal" aria-hidden="true">&times;</button>
+             <h4 class="modal-title" id="file-upload-title">Upload File</h4>
+           </div>
+           <div class="modal-body" id="file-upload-body">
+             <input id="modal-upload-file-input" type="file" class="file" multiple>
+           </div>
+           <div class="modal-footer">
+             <button type="button" class="btn btn-success" data-dismiss="modal">Close</button>
+             <button type="button" class="btn btn-success" id="modal-upload-file-button" data-complete-text="Uploading...">Upload</button>
+           </div>
+         </div>
+      </div>
+    </div>
+  <div class="modal" id="delete-modal" tabindex="-1" role="dialog" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal"
+            aria-hidden="true">&times;</button>
+          <h4 class="modal-title" id="delete-modal-title">Delete</h4>
+        </div>
+        <div class="modal-body">
+          <div class="panel-body">
+            <div id="delete-prompt"></div>
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn" data-dismiss="modal">Cancel</button>
+          <button type="button" class="btn btn-success" id="delete-button"
+            data-complete-text="Deleting...">Delete</button>
+        </div>
+      </div>
+    </div>
+  </div>
+
+      <div class="row">
+      <div class="col-xs-10 col-md-10">
+        <form onsubmit="return false;">
+          <div class="input-group">
+            <input type="text" class="form-control" id="directory"/>
+            <span class="input-group-btn">
+              <button class="btn btn-default" type="button" id="btn-nav-directory">Go!</button>
+            </span>
+          </div>
+        </form>
+      </div>
+      <div class="col-xs-2 col-md-2">
+        <button type="button" class="btn btn-default" data-toggle="modal"
+          aria-label="New Directory" data-target="#btn-create-directory"
+          title="Create Directory" id="btn-create-dir">
+            <span class="glyphicon glyphicon-folder-open"></span>
+        </button>
+        <button type="button" class="btn btn-default" data-toggle="modal"
+          data-target="#modal-upload-file" title="Upload Files" id="btn-upload-files">
+            <span class="glyphicon glyphicon-cloud-upload"></span>
+        </button>
+        <button class="btn btn-default dropdown-toggle" type="button"
+          data-toggle="dropdown" title="Cut & Paste">
+        <span class="glyphicon glyphicon-list-alt"></span></button>
+        <ul class="dropdown-menu cut-paste">
+          <li><a id="explorer-cut">Cut</a></li>
+          <li><a id="explorer-paste">Paste</a></li>
+        </ul>
+      </div>
+    </div>
+
+      <br />
+      <div id="panel"></div>
+
+      <div class="row">
+        <hr />
+        <div class="col-xs-2"><p>Hadoop, {release-year-token}.</p></div>
+      </div>
+
+    </div>
+
+    <script type="text/x-template" id="explorer-popover-perm-info">
+      <div class="explorer-popover-perm-body">
+        <table class="table table-striped">
+          <thead>
+            <tr>
+              <th class="text-center">User</th>
+              <th class="text-center">Group</th>
+              <th class="text-center">Other</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td><label><input type="checkbox" data-bit="8" /> Read</label></td>
+              <td><label><input type="checkbox" data-bit="5" /> Read</label></td>
+              <td><label><input type="checkbox" data-bit="2" /> Read</label></td>
+            </tr>
+            <tr>
+              <td><label><input type="checkbox" data-bit="7" /> Write</label></td>
+              <td><label><input type="checkbox" data-bit="4" /> Write</label></td>
+              <td><label><input type="checkbox" data-bit="1" /> Write</label></td>
+            </tr>
+            <tr>
+              <td><label><input type="checkbox" data-bit="6" /> Execute</label></td>
+              <td><label><input type="checkbox" data-bit="3" /> Execute</label></td>
+              <td><label><input type="checkbox" data-bit="0" /> Execute</label></td>
+            </tr>
+          </tbody>
+        </table>
+        <div style="text-align: right; margin-right: 10px">
+          <label><input type="checkbox" id="explorer-perm-sticky" data-bit="9" /> Sticky bit</label>
+        </div>
+        <hr/>
+        <div style="text-align: right">
+          <button type="button" class="btn" id="explorer-perm-cancel">Cancel</button>
+          <button type="button" class="btn btn-success" id="explorer-set-perm-button"
+                  data-complete-text="Updating...">Set</button>
+        </div>
+      </div>
+    </script>
+
+    <script type="text/x-dust-template" id="tmpl-explorer">
+      <table class="table" id="table-explorer">
+        <thead>
+          <tr>
+            <th><input type="checkbox" id="file-selector-all"></th>
+            <th title="Permissions">Permission</th>
+            <th>Owner</th>
+            <th>Group</th>
+            <th>Size</th>
+            <th title="Last Modified">Last Modified</th>
+            <th title="Replication">Replication</th>
+            <th>Block Size</th>
+            <th>Name</th>
+            <th></th>
+          </tr>
+        </thead>
+        <tbody>
+          {#FileStatus}
+          <tr inode-path="{pathSuffix}" data-permission="{permission}"
+            class="explorer-entry">
+            <td><input type="checkbox" class="file_selector"> </td>
+            <td><span class="explorer-perm-links editable-click">
+              {type|helper_to_directory}{permission|helper_to_permission}
+              {aclBit|helper_to_acl_bit}
+              </span></td>
+            <td><span class="explorer-owner-links">{owner}</span></td>
+            <td><span class="explorer-group-links">{group}</span></td>
+            <td>{length}</td>
+            <td>{modificationTime}</td>
+            <td><span class="explorer-replication-links">{replication}</span></td>
+            <td>{blockSize|fmt_bytes}</td>
+            <td><a inode-type="{type}" class="explorer-browse-links">{pathSuffix}</a></td>
+            <td><span class="glyphicon glyphicon-trash"></span></td>
+          </tr>
+          {/FileStatus}
+        </tbody>
+      </table>
+    </script>
+
+    <script type="text/x-dust-template" id="tmpl-block-info">
+      {#block}
+      <p>Block ID: {blockId}</p>
+      <p>Block Pool ID: {blockPoolId}</p>
+      <p>Generation Stamp: {generationStamp}</p>
+      <p>Size: {numBytes}</p>
+      {/block}
+      <p>Availability:
+        <ul>
+          {#locations}
+          <li>{hostName}</li>
+          {/locations}
+        </ul>
+      </p>
+    </script>
+    <script type="text/javascript" src="/static/jquery-3.4.1.min.js">
+    </script><script type="text/javascript" src="/static/jquery.dataTables.min.js">
+    </script><script type="text/javascript" src="/static/bootstrap-3.4.1/js/bootstrap.min.js">
+    </script><script type="text/javascript" src="/static/bootstrap-3.4.1/js/bootstrap-editable.min.js">
+    </script><script type="text/javascript" src="/static/dataTables.bootstrap.js">
+    </script><script type="text/javascript" src="/static/dust-full-2.0.0.min.js">
+    </script><script type="text/javascript" src="/static/dust-helpers-1.1.1.min.js">
+    </script><script type="text/javascript" src="/static/dfs-dust.js">
+    </script><script type="text/javascript" src="/static/json-bignum.js">
+    </script><script type="text/javascript" src="/static/rest-csrf.js">
+    </script><script type="text/javascript" src="explorer.js">
+    </script><script type="text/javascript" src="/static/moment.min.js">
+    </script>
+  </body>
+</html>

+ 552 - 0
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 = '<p>Authentication failed when trying to open ' + url + ': Unauthorized.</p>';
+          break;
+        case 403:
+          if(jqxhr.responseJSON !== undefined && jqxhr.responseJSON.RemoteException !== undefined) {
+            var msg = '<p>' + jqxhr.responseJSON.RemoteException.message + "</p>";
+            break;
+          }
+          var msg = '<p>Permission denied when trying to open ' + url + ': ' + err + '</p>';
+          break;
+        case 404:
+          var msg = '<p>Path does not exist on HDFS or WebHDFS is disabled.  Please check your path or enable WebHDFS</p>';
+          break;
+        default:
+          var msg = '<p>Failed to retrieve data from ' + url + ': ' + err + '</p>';
+        }
+      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/<path>?op=DELETE&recursive=<true|false>
+      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/<path>?op=SETPERMISSION&permission=<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 = $('<option value="' + i + '">Block ' + i + '</option>');
+        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();
+})();

+ 4 - 1
hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/webapps/router/federationhealth.html

@@ -41,9 +41,12 @@
     <li class="dropdown">
       <a href="#" class="dropdown-toggle" data-toggle="dropdown">Utilities <b class="caret"></b></a>
       <ul class="dropdown-menu">
+        <li><a href="explorer.html">Browse the file system</a></li>
+        <li><a href="logs">Logs</a></li>
+        <li><a href="logLevel">Log Level</a></li>
         <li><a href="jmx">Metrics</a></li>
         <li><a href="conf">Configuration</a></li>
-        <li><a href="logs">Logs</a></li>
+        <li><a href="stacks">Process Thread Dump</a></li>
       </ul>
     </li>
   </ul>