Skip to content

Commit 06793da

Browse files
HDFS-15531. Namenode UI: List snapshots in separate table for each snapshottable directory (#2230)
1 parent d1c60a5 commit 06793da

File tree

3 files changed

+167
-36
lines changed

3 files changed

+167
-36
lines changed

hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.html

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -250,56 +250,39 @@
250250

251251
<script type="text/x-dust-template" id="tmpl-snapshot">
252252
<div class="page-header"><h1>Snapshot Summary</h1></div>
253-
<div class="page-header"><h1><small>Snapshottable directories: {@size key=SnapshottableDirectories}{/size}</small></div>
253+
<div class="snapshot-stats"><h2><small>Snapshottable Directories: {@size key=SnapshottableDirectories}{/size}</small></div>
254+
<div class="snapshot-stats"><h2><small>Total Snapshots: {@size key=Snapshots}{/size}</small></div>
254255
<small>
255-
<table class="table">
256+
<table class="table" id="table-snapshots">
256257
<thead>
257258
<tr>
259+
<th></th>
258260
<th>Path</th>
259-
<th>Snapshot Number</th>
261+
<th>Snapshots Count</th>
260262
<th>Snapshot Quota</th>
261263
<th>Modification Time</th>
262264
<th>Permission</th>
263265
<th>Owner</th>
264266
<th>Group</th>
265267
</tr>
266268
</thead>
267-
{#SnapshottableDirectories}
268-
<tr>
269-
<td>{path}</td>
270-
<td>{snapshotNumber}</td>
271-
<td>{snapshotQuota}</td>
272-
<td>{modificationTime|date_tostring}</td>
273-
<td>{permission|helper_to_permission}</td>
274-
<td>{owner}</td>
275-
<td>{group}</td>
276-
</tr>
277-
{/SnapshottableDirectories}
278-
</table>
279-
</small>
280-
281-
<div class="page-header"><h1><small>Snapshots: {@size key=Snapshots}{/size}</small></div>
282-
283-
<small>
284-
<table class="table">
285-
<thead>
269+
<tbody>
270+
{#SnapshottableDirectories}
286271
<tr>
287-
<th>Snapshot ID</th>
288-
<th>Snapshot Directory</th>
289-
<th>Modification Time</th>
290-
<th>Status</th>
272+
<td class="details-control"></td>
273+
<td ng-value="{path}">{path}</td>
274+
<td ng-value="{snapshotNumber}">{snapshotNumber}</td>
275+
<td ng-value="{snapshotQuota}">{snapshotQuota}</td>
276+
<td ng-value="{modificationTime}">{modificationTime|date_tostring}</td>
277+
<td>{permission|helper_to_permission}</td>
278+
<td ng-value="{owner}">{owner}</td>
279+
<td ng-value="{group}">{group}</td>
291280
</tr>
292-
</thead>
293-
{#Snapshots}
294-
<tr>
295-
<td>{snapshotID}</td>
296-
<td>{snapshotDirectory}</td>
297-
<td>{modificationTime|date_tostring}</td>
298-
<td>{status}</td>
299-
</tr>
300-
{/Snapshots}
281+
{/SnapshottableDirectories}
282+
</tbody>
301283
</table>
302284
</small>
285+
303286
</script>
304287

305288
<script type="text/x-dust-template" id="tmpl-datanode">

hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/hdfs/dfshealth.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,127 @@
429429
dust.render('snapshot-info', resp.beans[0], function(err, out) {
430430
$('#tab-snapshot').html(out);
431431
$('#ui-tabs a[href="#tab-snapshot"]').tab('show');
432+
433+
// Build a map to store snapshottable directory -> snapshots
434+
var snapshots = 'Snapshots' in resp.beans[0] ? resp.beans[0].Snapshots : [];
435+
var snapshotsMap = snapshots.reduce(function(result, snapshot) {
436+
var rootPath = snapshot.snapshotDirectory.substr(0, snapshot.snapshotDirectory.indexOf(".snapshot") -1 );
437+
if (rootPath in result) {
438+
var arr = result[rootPath];
439+
arr.push(snapshot);
440+
result[rootPath] = arr;
441+
} else {
442+
result[rootPath] = [snapshot];
443+
}
444+
return result;
445+
}, {});
446+
447+
var table = $('#table-snapshots').DataTable( {
448+
'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
449+
'columns': [
450+
{ 'orderable': false, 'searchable': false, 'data': null, 'defaultContent': "" },
451+
{ 'data': 'path', 'orderDataType': 'ng-value', 'searchable': true , 'type': 'string', 'defaultContent': "" },
452+
{ 'data': 'snapshotNumber', 'orderDataType': 'ng-value', 'searchable': false , 'type': 'num', 'defaultContent': 0 },
453+
{ 'data': 'snapshotQuota', 'orderDataType': 'ng-value', 'searchable': false , 'type': 'num', 'defaultContent': 0 },
454+
{ 'data': 'modificationTime', 'orderDataType': 'ng-value', 'searchable': false , 'type': 'string', 'defaultContent': "" },
455+
{ 'data': 'permission', 'orderable': false, 'searchable': false , 'type': 'string', 'defaultContent': "" },
456+
{ 'data': 'owner', 'orderDataType': 'ng-value', 'searchable': true , 'type': 'string', 'defaultContent': "" },
457+
{ 'data': 'group', 'orderDataType': 'ng-value', 'searchable': true , 'type': 'string', 'defaultContent': "" }
458+
],
459+
'order': [[ 1, 'asc' ]]
460+
});
461+
// Add event listener for opening and closing details
462+
$('#table-snapshots tbody').on('click', 'td.details-control', function () {
463+
var tr = $(this).closest('tr');
464+
var row = table.row( tr );
465+
466+
if ( row.child.isShown() ) {
467+
// This row is already open - close it
468+
row.child.hide();
469+
tr.removeClass('shown');
470+
}
471+
else {
472+
// Open this row
473+
row.child( formatExpandedRow(row.data(), snapshotsMap) ).show();
474+
var tableId = getSubTableId(row.data());
475+
if (!$.fn.dataTable.isDataTable('#'+tableId)) {
476+
$('#' + tableId).DataTable({
477+
'lengthMenu': [[25, 50, 100, -1], [25, 50, 100, "All"]],
478+
'columns': [
479+
{
480+
'orderDataType': 'ng-value',
481+
'searchable': true,
482+
'type': 'num',
483+
'defaultContent': 0
484+
},
485+
{
486+
'orderDataType': 'ng-value',
487+
'searchable': true,
488+
'type': 'string',
489+
'defaultContent': ""
490+
},
491+
{
492+
'orderDataType': 'ng-value',
493+
'searchable': true,
494+
'type': 'string',
495+
'defaultContent': ""
496+
},
497+
{
498+
'orderDataType': 'ng-value',
499+
'searchable': true,
500+
'type': 'string',
501+
'defaultContent': ""
502+
}
503+
],
504+
'order': [[0, 'asc']]
505+
});
506+
}
507+
tr.addClass('shown');
508+
}
509+
});
432510
});
433511
})).fail(ajax_error_handler);
434512
}
435513

514+
function getSubTableId(row) {
515+
var path = row.path;
516+
// replace all "/" with "-"
517+
path = path.replace(/\//g, '-');
518+
return "table-snapshots"+path;
519+
}
520+
521+
function formatExpandedRow (row, snapshotsMap) {
522+
// `row` is the original data object for the row
523+
var tableId = getSubTableId(row);
524+
var path = row.path;
525+
var snapshots = snapshotsMap[path];
526+
if (!snapshots || snapshots.length === 0) {
527+
return 'No snapshots found for this path';
528+
}
529+
var tbody = snapshots.reduce(function(result, snapshot) {
530+
var html = '<tr>'+
531+
'<td ng-value="'+snapshot.snapshotID+'">'+ snapshot.snapshotID +'</td>'+
532+
'<td ng-value="'+snapshot.snapshotDirectory+'">'+ snapshot.snapshotDirectory +'</td>'+
533+
'<td ng-value="'+snapshot.modificationTime+'">'+ moment(Number(snapshot.modificationTime)).format('ddd MMM DD HH:mm:ss ZZ YYYY') +'</td>'+
534+
'<td ng-value="'+snapshot.status+'">'+ snapshot.status +'</td>'+
535+
'</tr>';
536+
return result + html;
537+
}, "");
538+
return '<table class="table sub-table" id='+ tableId +'>'+
539+
'<thead>'+
540+
'<tr>'+
541+
'<th>Snapshot ID</th>'+
542+
'<th>Snapshot Directory</th>'+
543+
'<th>Modification Time</th>' +
544+
'<th>Status</th>' +
545+
'</tr>'+
546+
'</thead>'+
547+
'<tbody>'+
548+
tbody +
549+
'</tbody>'+
550+
'</table>';
551+
}
552+
436553
function load_page() {
437554
var hash = window.location.hash;
438555
switch(hash) {

hadoop-hdfs-project/hadoop-hdfs/src/main/webapps/static/hadoop.css

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,4 +369,35 @@ header.bs-docs-nav, header.bs-docs-nav .navbar-brand {
369369
.bar text {
370370
fill: #fff;
371371
font: 10px sans-serif;
372-
}
372+
}
373+
374+
td.details-control:before {
375+
color: #5fa341;
376+
content: "\2b";
377+
cursor: pointer;
378+
font-size: 1.5em;
379+
line-height: 1em;
380+
}
381+
382+
tr.shown td.details-control:before {
383+
color: #c7254e;
384+
content: "\2212";
385+
cursor: pointer;
386+
font-size: 1.5em;
387+
line-height: 1em;
388+
}
389+
390+
#table-snapshots_wrapper {
391+
margin-top: 20px;
392+
}
393+
394+
table#table-snapshots>tbody>tr>td>.dataTables_wrapper {
395+
padding-left: 50px;
396+
padding-right: 10px;
397+
padding-top: 10px;
398+
background-color: #ddd;
399+
}
400+
401+
.snapshot-stats>h2 {
402+
margin: 0;
403+
}

0 commit comments

Comments
 (0)