Ver Fonte

HDFS-15531. Namenode UI: List snapshots in separate table for each snapshottable directory (#2230)

Vivek Ratnavel Subramanian há 4 anos atrás
pai
commit
06793da100

+ 18 - 35
hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.html

@@ -250,13 +250,15 @@
 
 
 <script type="text/x-dust-template" id="tmpl-snapshot">
 <script type="text/x-dust-template" id="tmpl-snapshot">
 <div class="page-header"><h1>Snapshot Summary</h1></div>
 <div class="page-header"><h1>Snapshot Summary</h1></div>
-<div class="page-header"><h1><small>Snapshottable directories: {@size key=SnapshottableDirectories}{/size}</small></div>
+<div class="snapshot-stats"><h2><small>Snapshottable Directories: {@size key=SnapshottableDirectories}{/size}</small></div>
+<div class="snapshot-stats"><h2><small>Total Snapshots: {@size key=Snapshots}{/size}</small></div>
 <small>
 <small>
-<table class="table">
+<table class="table" id="table-snapshots">
   <thead>
   <thead>
     <tr>
     <tr>
+      <th></th>
       <th>Path</th>
       <th>Path</th>
-      <th>Snapshot Number</th>
+      <th>Snapshots Count</th>
       <th>Snapshot Quota</th>
       <th>Snapshot Quota</th>
       <th>Modification Time</th>
       <th>Modification Time</th>
       <th>Permission</th>
       <th>Permission</th>
@@ -264,42 +266,23 @@
       <th>Group</th>
       <th>Group</th>
     </tr>
     </tr>
   </thead>
   </thead>
-  {#SnapshottableDirectories}
-  <tr>
-    <td>{path}</td>
-    <td>{snapshotNumber}</td>
-    <td>{snapshotQuota}</td>
-    <td>{modificationTime|date_tostring}</td>
-    <td>{permission|helper_to_permission}</td>
-    <td>{owner}</td>
-    <td>{group}</td>
-  </tr>
-  {/SnapshottableDirectories}
-</table>
-</small>
-
-<div class="page-header"><h1><small>Snapshots: {@size key=Snapshots}{/size}</small></div>
-
-<small>
-<table class="table">
-  <thead>
+  <tbody>
+    {#SnapshottableDirectories}
     <tr>
     <tr>
-      <th>Snapshot ID</th>
-      <th>Snapshot Directory</th>
-      <th>Modification Time</th>
-      <th>Status</th>
+      <td class="details-control"></td>
+      <td ng-value="{path}">{path}</td>
+      <td ng-value="{snapshotNumber}">{snapshotNumber}</td>
+      <td ng-value="{snapshotQuota}">{snapshotQuota}</td>
+      <td ng-value="{modificationTime}">{modificationTime|date_tostring}</td>
+      <td>{permission|helper_to_permission}</td>
+      <td ng-value="{owner}">{owner}</td>
+      <td ng-value="{group}">{group}</td>
     </tr>
     </tr>
-  </thead>
-  {#Snapshots}
-  <tr>
-    <td>{snapshotID}</td>
-    <td>{snapshotDirectory}</td>
-    <td>{modificationTime|date_tostring}</td>
-    <td>{status}</td>
-  </tr>
-  {/Snapshots}
+    {/SnapshottableDirectories}
+  </tbody>
 </table>
 </table>
 </small>
 </small>
+
 </script>
 </script>
 
 
 <script type="text/x-dust-template" id="tmpl-datanode">
 <script type="text/x-dust-template" id="tmpl-datanode">

+ 117 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.js

@@ -429,10 +429,127 @@
       dust.render('snapshot-info', resp.beans[0], function(err, out) {
       dust.render('snapshot-info', resp.beans[0], function(err, out) {
           $('#tab-snapshot').html(out);
           $('#tab-snapshot').html(out);
           $('#ui-tabs a[href="#tab-snapshot"]').tab('show');
           $('#ui-tabs a[href="#tab-snapshot"]').tab('show');
+
+          // Build a map to store snapshottable directory -> snapshots
+          var snapshots = 'Snapshots' in resp.beans[0] ? resp.beans[0].Snapshots : [];
+          var snapshotsMap = snapshots.reduce(function(result, snapshot) {
+            var rootPath = snapshot.snapshotDirectory.substr(0, snapshot.snapshotDirectory.indexOf(".snapshot") -1 );
+            if (rootPath in result) {
+              var arr = result[rootPath];
+              arr.push(snapshot);
+              result[rootPath] = arr;
+            } else {
+              result[rootPath] = [snapshot];
+            }
+            return result;
+          }, {});
+
+          var table = $('#table-snapshots').DataTable( {
+            'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
+            'columns': [
+              { 'orderable': false, 'searchable': false, 'data': null, 'defaultContent': "" },
+              { 'data': 'path', 'orderDataType': 'ng-value', 'searchable': true , 'type': 'string', 'defaultContent': "" },
+              { 'data': 'snapshotNumber', 'orderDataType': 'ng-value', 'searchable': false , 'type': 'num', 'defaultContent': 0 },
+              { 'data': 'snapshotQuota', 'orderDataType': 'ng-value', 'searchable': false , 'type': 'num', 'defaultContent': 0 },
+              { 'data': 'modificationTime', 'orderDataType': 'ng-value', 'searchable': false , 'type': 'string', 'defaultContent': "" },
+              { 'data': 'permission', 'orderable': false, 'searchable': false , 'type': 'string', 'defaultContent': "" },
+              { 'data': 'owner', 'orderDataType': 'ng-value', 'searchable': true , 'type': 'string', 'defaultContent': "" },
+              { 'data': 'group', 'orderDataType': 'ng-value', 'searchable': true , 'type': 'string', 'defaultContent': "" }
+            ],
+            'order': [[ 1, 'asc' ]]
+          });
+          // Add event listener for opening and closing details
+          $('#table-snapshots tbody').on('click', 'td.details-control', function () {
+            var tr = $(this).closest('tr');
+            var row = table.row( tr );
+
+            if ( row.child.isShown() ) {
+              // This row is already open - close it
+              row.child.hide();
+              tr.removeClass('shown');
+            }
+            else {
+              // Open this row
+              row.child( formatExpandedRow(row.data(), snapshotsMap) ).show();
+              var tableId = getSubTableId(row.data());
+              if (!$.fn.dataTable.isDataTable('#'+tableId)) {
+                $('#' + tableId).DataTable({
+                  'lengthMenu': [[25, 50, 100, -1], [25, 50, 100, "All"]],
+                  'columns': [
+                    {
+                      'orderDataType': 'ng-value',
+                      'searchable': true,
+                      'type': 'num',
+                      'defaultContent': 0
+                    },
+                    {
+                      'orderDataType': 'ng-value',
+                      'searchable': true,
+                      'type': 'string',
+                      'defaultContent': ""
+                    },
+                    {
+                      'orderDataType': 'ng-value',
+                      'searchable': true,
+                      'type': 'string',
+                      'defaultContent': ""
+                    },
+                    {
+                      'orderDataType': 'ng-value',
+                      'searchable': true,
+                      'type': 'string',
+                      'defaultContent': ""
+                    }
+                  ],
+                  'order': [[0, 'asc']]
+                });
+              }
+              tr.addClass('shown');
+            }
+          });
         });
         });
       })).fail(ajax_error_handler);
       })).fail(ajax_error_handler);
   }
   }
 
 
+  function getSubTableId(row) {
+    var path = row.path;
+    // replace all "/" with "-"
+    path = path.replace(/\//g, '-');
+    return "table-snapshots"+path;
+  }
+
+  function formatExpandedRow (row, snapshotsMap) {
+    // `row` is the original data object for the row
+    var tableId = getSubTableId(row);
+    var path = row.path;
+    var snapshots = snapshotsMap[path];
+    if (!snapshots || snapshots.length === 0) {
+      return 'No snapshots found for this path';
+    }
+    var tbody = snapshots.reduce(function(result, snapshot) {
+      var html = '<tr>'+
+          '<td ng-value="'+snapshot.snapshotID+'">'+ snapshot.snapshotID +'</td>'+
+          '<td ng-value="'+snapshot.snapshotDirectory+'">'+ snapshot.snapshotDirectory +'</td>'+
+          '<td ng-value="'+snapshot.modificationTime+'">'+ moment(Number(snapshot.modificationTime)).format('ddd MMM DD HH:mm:ss ZZ YYYY') +'</td>'+
+          '<td ng-value="'+snapshot.status+'">'+ snapshot.status +'</td>'+
+        '</tr>';
+      return result + html;
+    }, "");
+    return '<table class="table sub-table" id='+ tableId +'>'+
+      '<thead>'+
+      '<tr>'+
+      '<th>Snapshot ID</th>'+
+      '<th>Snapshot Directory</th>'+
+      '<th>Modification Time</th>' +
+      '<th>Status</th>' +
+      '</tr>'+
+      '</thead>'+
+      '<tbody>'+
+      tbody +
+      '</tbody>'+
+      '</table>';
+  }
+
   function load_page() {
   function load_page() {
     var hash = window.location.hash;
     var hash = window.location.hash;
     switch(hash) {
     switch(hash) {

+ 32 - 1
hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/hadoop.css

@@ -369,4 +369,35 @@ header.bs-docs-nav, header.bs-docs-nav .navbar-brand {
 .bar text {
 .bar text {
     fill: #fff;
     fill: #fff;
     font: 10px sans-serif;
     font: 10px sans-serif;
-}
+}
+
+td.details-control:before {
+    color: #5fa341;
+    content: "\2b";
+    cursor: pointer;
+    font-size: 1.5em;
+    line-height: 1em;
+}
+
+tr.shown td.details-control:before {
+    color: #c7254e;
+    content: "\2212";
+    cursor: pointer;
+    font-size: 1.5em;
+    line-height: 1em;
+}
+
+#table-snapshots_wrapper {
+    margin-top: 20px;
+}
+
+table#table-snapshots>tbody>tr>td>.dataTables_wrapper {
+    padding-left: 50px;
+    padding-right: 10px;
+    padding-top: 10px;
+    background-color: #ddd;
+}
+
+.snapshot-stats>h2 {
+    margin: 0;
+}