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

This commit is contained in:
Vivek Ratnavel Subramanian 2020-08-27 11:36:30 -07:00 committed by GitHub
parent d1c60a53f6
commit 06793da100
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 167 additions and 36 deletions

View File

@ -250,13 +250,15 @@
<script type="text/x-dust-template" id="tmpl-snapshot">
<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>
<table class="table">
<table class="table" id="table-snapshots">
<thead>
<tr>
<th></th>
<th>Path</th>
<th>Snapshot Number</th>
<th>Snapshots Count</th>
<th>Snapshot Quota</th>
<th>Modification Time</th>
<th>Permission</th>
@ -264,42 +266,23 @@
<th>Group</th>
</tr>
</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>
<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>
</thead>
{#Snapshots}
<tr>
<td>{snapshotID}</td>
<td>{snapshotDirectory}</td>
<td>{modificationTime|date_tostring}</td>
<td>{status}</td>
</tr>
{/Snapshots}
{/SnapshottableDirectories}
</tbody>
</table>
</small>
</script>
<script type="text/x-dust-template" id="tmpl-datanode">

View File

@ -429,10 +429,127 @@
dust.render('snapshot-info', resp.beans[0], function(err, out) {
$('#tab-snapshot').html(out);
$('#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);
}
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() {
var hash = window.location.hash;
switch(hash) {

View File

@ -370,3 +370,34 @@ header.bs-docs-nav, header.bs-docs-nav .navbar-brand {
fill: #fff;
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;
}